diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 9d9141559..7f2bbca40 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" @@ -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 @@ -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/Kotatogram - name: Check. if: env.ONLY_CACHE == 'false' diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 896e1061c..88a17b074 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 1dbe5b461..9769045bf 100644 --- a/.github/workflows/snap.yml +++ b/.github/workflows/snap.yml @@ -23,33 +23,40 @@ jobs: steps: - name: Clone. - uses: actions/checkout@v1 + uses: actions/checkout@v2 with: submodules: recursive - 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 diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 1cb303ffa..6ca279d72 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" @@ -350,6 +345,7 @@ jobs: - name: Move artifact. if: env.UPLOAD_ARTIFACT == 'true' + shell: cmd run: | cd %REPO_NAME%\out\Debug mkdir artifact diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 0923dbb19..fec2f5e4a 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 ) @@ -134,6 +135,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 @@ -146,6 +149,10 @@ 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/peers/add_participants_box.cpp boxes/peers/add_participants_box.h boxes/peers/edit_contact_box.cpp @@ -274,6 +281,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 @@ -326,6 +335,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 @@ -440,6 +451,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 @@ -833,6 +846,8 @@ PRIVATE settings/settings_codes.h settings/settings_common.cpp settings/settings_common.h + settings/settings_folders.cpp + settings/settings_folders.h settings/settings_information.cpp settings/settings_information.h settings/settings_intro.cpp @@ -915,6 +930,10 @@ PRIVATE ui/countryinput.h ui/empty_userpic.cpp 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 @@ -943,6 +962,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/art/dice_idle.tgs b/Telegram/Resources/art/dice_idle.tgs new file mode 100644 index 000000000..aad33bffe Binary files /dev/null and b/Telegram/Resources/art/dice_idle.tgs differ diff --git a/Telegram/Resources/icons/filters/filters_all.png b/Telegram/Resources/icons/filters/filters_all.png new file mode 100644 index 000000000..9cf16f5e0 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_all.png differ diff --git a/Telegram/Resources/icons/filters/filters_all@2x.png b/Telegram/Resources/icons/filters/filters_all@2x.png new file mode 100644 index 000000000..fc14b63b6 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_all@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_all@3x.png b/Telegram/Resources/icons/filters/filters_all@3x.png new file mode 100644 index 000000000..e1cc8aa77 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_all@3x.png differ 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 000000000..c2a0fb691 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_all_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_all_active@2x.png b/Telegram/Resources/icons/filters/filters_all_active@2x.png new file mode 100644 index 000000000..fa027ea7f Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_all_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_all_active@3x.png b/Telegram/Resources/icons/filters/filters_all_active@3x.png new file mode 100644 index 000000000..99d77e8c1 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_all_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_bots.png b/Telegram/Resources/icons/filters/filters_bots.png new file mode 100644 index 000000000..590b15279 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_bots.png differ diff --git a/Telegram/Resources/icons/filters/filters_bots@2x.png b/Telegram/Resources/icons/filters/filters_bots@2x.png new file mode 100644 index 000000000..c152af138 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_bots@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_bots@3x.png b/Telegram/Resources/icons/filters/filters_bots@3x.png new file mode 100644 index 000000000..867e63a7d Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_bots@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_bots_active.png b/Telegram/Resources/icons/filters/filters_bots_active.png new file mode 100644 index 000000000..3342d8671 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_bots_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_bots_active@2x.png b/Telegram/Resources/icons/filters/filters_bots_active@2x.png new file mode 100644 index 000000000..6788eb795 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_bots_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_bots_active@3x.png b/Telegram/Resources/icons/filters/filters_bots_active@3x.png new file mode 100644 index 000000000..7bf3a689d Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_bots_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_channels.png b/Telegram/Resources/icons/filters/filters_channels.png new file mode 100644 index 000000000..be77932b8 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_channels.png differ 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 000000000..697e07daf Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_channels@2x.png differ 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 000000000..3dc154a45 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_channels@3x.png differ 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 000000000..202ce0368 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_channels_active.png differ 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 000000000..64306644f Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_channels_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_channels_active@3x.png b/Telegram/Resources/icons/filters/filters_channels_active@3x.png new file mode 100644 index 000000000..c5428670e Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_channels_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_custom.png b/Telegram/Resources/icons/filters/filters_custom.png new file mode 100644 index 000000000..499de7caa Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_custom.png differ diff --git a/Telegram/Resources/icons/filters/filters_custom@2x.png b/Telegram/Resources/icons/filters/filters_custom@2x.png new file mode 100644 index 000000000..2d8ef3270 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_custom@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_custom@3x.png b/Telegram/Resources/icons/filters/filters_custom@3x.png new file mode 100644 index 000000000..4e0b2a126 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_custom@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_custom_active.png b/Telegram/Resources/icons/filters/filters_custom_active.png new file mode 100644 index 000000000..740235201 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_custom_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_custom_active@2x.png b/Telegram/Resources/icons/filters/filters_custom_active@2x.png new file mode 100644 index 000000000..4a25cbaef Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_custom_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_custom_active@3x.png b/Telegram/Resources/icons/filters/filters_custom_active@3x.png new file mode 100644 index 000000000..5e696988c Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_custom_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_groups.png b/Telegram/Resources/icons/filters/filters_groups.png new file mode 100644 index 000000000..24d62f890 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_groups.png differ diff --git a/Telegram/Resources/icons/filters/filters_groups@2x.png b/Telegram/Resources/icons/filters/filters_groups@2x.png new file mode 100644 index 000000000..00e515474 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_groups@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_groups@3x.png b/Telegram/Resources/icons/filters/filters_groups@3x.png new file mode 100644 index 000000000..9f3ff16e5 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_groups@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_groups_active.png b/Telegram/Resources/icons/filters/filters_groups_active.png new file mode 100644 index 000000000..a5a143946 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_groups_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_groups_active@2x.png b/Telegram/Resources/icons/filters/filters_groups_active@2x.png new file mode 100644 index 000000000..6f87b49e4 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_groups_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_groups_active@3x.png b/Telegram/Resources/icons/filters/filters_groups_active@3x.png new file mode 100644 index 000000000..342825ff2 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_groups_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_private.png b/Telegram/Resources/icons/filters/filters_private.png new file mode 100644 index 000000000..68924026e Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_private.png differ diff --git a/Telegram/Resources/icons/filters/filters_private@2x.png b/Telegram/Resources/icons/filters/filters_private@2x.png new file mode 100644 index 000000000..c39dc8d74 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_private@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_private@3x.png b/Telegram/Resources/icons/filters/filters_private@3x.png new file mode 100644 index 000000000..42ce50320 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_private@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_private_active.png b/Telegram/Resources/icons/filters/filters_private_active.png new file mode 100644 index 000000000..912ea878d Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_private_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_private_active@2x.png b/Telegram/Resources/icons/filters/filters_private_active@2x.png new file mode 100644 index 000000000..c83948ad4 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_private_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_private_active@3x.png b/Telegram/Resources/icons/filters/filters_private_active@3x.png new file mode 100644 index 000000000..2bf50ef6e Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_private_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_setup.png b/Telegram/Resources/icons/filters/filters_setup.png new file mode 100644 index 000000000..49602582f Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_setup.png differ diff --git a/Telegram/Resources/icons/filters/filters_setup@2x.png b/Telegram/Resources/icons/filters/filters_setup@2x.png new file mode 100644 index 000000000..519f77fef Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_setup@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_setup@3x.png b/Telegram/Resources/icons/filters/filters_setup@3x.png new file mode 100644 index 000000000..87ab4420c Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_setup@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_archived.png b/Telegram/Resources/icons/filters/filters_type_archived.png new file mode 100644 index 000000000..790d50335 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_archived.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_archived@2x.png b/Telegram/Resources/icons/filters/filters_type_archived@2x.png new file mode 100644 index 000000000..342c6cb55 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_archived@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_archived@3x.png b/Telegram/Resources/icons/filters/filters_type_archived@3x.png new file mode 100644 index 000000000..f20b8b610 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_archived@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_bots.png b/Telegram/Resources/icons/filters/filters_type_bots.png new file mode 100644 index 000000000..34d2ed00a Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_bots.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_bots@2x.png b/Telegram/Resources/icons/filters/filters_type_bots@2x.png new file mode 100644 index 000000000..1f37ff794 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_bots@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_bots@3x.png b/Telegram/Resources/icons/filters/filters_type_bots@3x.png new file mode 100644 index 000000000..afc773a3c Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_bots@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_channels.png b/Telegram/Resources/icons/filters/filters_type_channels.png new file mode 100644 index 000000000..e43f40012 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_channels.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_channels@2x.png b/Telegram/Resources/icons/filters/filters_type_channels@2x.png new file mode 100644 index 000000000..cf3804750 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_channels@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_channels@3x.png b/Telegram/Resources/icons/filters/filters_type_channels@3x.png new file mode 100644 index 000000000..3e8d01368 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_channels@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_contacts.png b/Telegram/Resources/icons/filters/filters_type_contacts.png new file mode 100644 index 000000000..1cfb2a575 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_contacts.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_contacts@2x.png b/Telegram/Resources/icons/filters/filters_type_contacts@2x.png new file mode 100644 index 000000000..6c81c14ea Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_contacts@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_contacts@3x.png b/Telegram/Resources/icons/filters/filters_type_contacts@3x.png new file mode 100644 index 000000000..f3ac2f12a Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_contacts@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_groups.png b/Telegram/Resources/icons/filters/filters_type_groups.png new file mode 100644 index 000000000..c66addebf Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_groups.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_groups@2x.png b/Telegram/Resources/icons/filters/filters_type_groups@2x.png new file mode 100644 index 000000000..114156e54 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_groups@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_groups@3x.png b/Telegram/Resources/icons/filters/filters_type_groups@3x.png new file mode 100644 index 000000000..fcaffe853 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_groups@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_muted.png b/Telegram/Resources/icons/filters/filters_type_muted.png new file mode 100644 index 000000000..92e3b1adc Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_muted.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_muted@2x.png b/Telegram/Resources/icons/filters/filters_type_muted@2x.png new file mode 100644 index 000000000..1dd51e26c Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_muted@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_muted@3x.png b/Telegram/Resources/icons/filters/filters_type_muted@3x.png new file mode 100644 index 000000000..0f0f81dbb Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_muted@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_noncontacts.png b/Telegram/Resources/icons/filters/filters_type_noncontacts.png new file mode 100644 index 000000000..213633ac0 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_noncontacts.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_noncontacts@2x.png b/Telegram/Resources/icons/filters/filters_type_noncontacts@2x.png new file mode 100644 index 000000000..f1f7695b6 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_noncontacts@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_noncontacts@3x.png b/Telegram/Resources/icons/filters/filters_type_noncontacts@3x.png new file mode 100644 index 000000000..f5a58c269 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_noncontacts@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_read.png b/Telegram/Resources/icons/filters/filters_type_read.png new file mode 100644 index 000000000..689da4ca7 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_read.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_read@2x.png b/Telegram/Resources/icons/filters/filters_type_read@2x.png new file mode 100644 index 000000000..f874a3c28 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_read@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_type_read@3x.png b/Telegram/Resources/icons/filters/filters_type_read@3x.png new file mode 100644 index 000000000..b2a8feea1 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_type_read@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_unmuted.png b/Telegram/Resources/icons/filters/filters_unmuted.png new file mode 100644 index 000000000..819d3f7ec Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_unmuted.png differ diff --git a/Telegram/Resources/icons/filters/filters_unmuted@2x.png b/Telegram/Resources/icons/filters/filters_unmuted@2x.png new file mode 100644 index 000000000..3244112e8 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_unmuted@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_unmuted@3x.png b/Telegram/Resources/icons/filters/filters_unmuted@3x.png new file mode 100644 index 000000000..113455c3e Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_unmuted@3x.png differ 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 000000000..39da6995d Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_unmuted_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_unmuted_active@2x.png b/Telegram/Resources/icons/filters/filters_unmuted_active@2x.png new file mode 100644 index 000000000..1613a7ef3 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_unmuted_active@2x.png differ 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 000000000..1cb402100 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_unmuted_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_unread.png b/Telegram/Resources/icons/filters/filters_unread.png new file mode 100644 index 000000000..1ed90a352 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_unread.png differ diff --git a/Telegram/Resources/icons/filters/filters_unread@2x.png b/Telegram/Resources/icons/filters/filters_unread@2x.png new file mode 100644 index 000000000..907daf84a Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_unread@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_unread@3x.png b/Telegram/Resources/icons/filters/filters_unread@3x.png new file mode 100644 index 000000000..c6577a20c Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_unread@3x.png differ diff --git a/Telegram/Resources/icons/filters/filters_unread_active.png b/Telegram/Resources/icons/filters/filters_unread_active.png new file mode 100644 index 000000000..b7169765c Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_unread_active.png differ diff --git a/Telegram/Resources/icons/filters/filters_unread_active@2x.png b/Telegram/Resources/icons/filters/filters_unread_active@2x.png new file mode 100644 index 000000000..38bcba696 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_unread_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/filters_unread_active@3x.png b/Telegram/Resources/icons/filters/filters_unread_active@3x.png new file mode 100644 index 000000000..99bd33737 Binary files /dev/null and b/Telegram/Resources/icons/filters/filters_unread_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_cat.png b/Telegram/Resources/icons/filters/folders_cat.png new file mode 100644 index 000000000..67748a1f6 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_cat.png differ 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 000000000..aa08a4b4c Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_cat@2x.png differ 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 000000000..ef978ea92 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_cat@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_cat_active.png b/Telegram/Resources/icons/filters/folders_cat_active.png new file mode 100644 index 000000000..6f9e33981 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_cat_active.png differ 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 000000000..012424ade Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_cat_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/folders_cat_active@3x.png b/Telegram/Resources/icons/filters/folders_cat_active@3x.png new file mode 100644 index 000000000..13ed57f1d Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_cat_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_crown.png b/Telegram/Resources/icons/filters/folders_crown.png new file mode 100644 index 000000000..ab4a48567 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_crown.png differ 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 000000000..a091297e7 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_crown@2x.png differ 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 000000000..b7d609191 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_crown@3x.png differ 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 000000000..f14e27d9a Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_crown_active.png differ diff --git a/Telegram/Resources/icons/filters/folders_crown_active@2x.png b/Telegram/Resources/icons/filters/folders_crown_active@2x.png new file mode 100644 index 000000000..be26b69d7 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_crown_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/folders_crown_active@3x.png b/Telegram/Resources/icons/filters/folders_crown_active@3x.png new file mode 100644 index 000000000..0399765e7 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_crown_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_favorite.png b/Telegram/Resources/icons/filters/folders_favorite.png new file mode 100644 index 000000000..400fb9612 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_favorite.png differ diff --git a/Telegram/Resources/icons/filters/folders_favorite@2x.png b/Telegram/Resources/icons/filters/folders_favorite@2x.png new file mode 100644 index 000000000..36102a2b5 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_favorite@2x.png differ 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 000000000..117bd86c5 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_favorite@3x.png differ 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 000000000..e45aa53a4 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_favorite_active.png differ diff --git a/Telegram/Resources/icons/filters/folders_favorite_active@2x.png b/Telegram/Resources/icons/filters/folders_favorite_active@2x.png new file mode 100644 index 000000000..faebedbba Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_favorite_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/folders_favorite_active@3x.png b/Telegram/Resources/icons/filters/folders_favorite_active@3x.png new file mode 100644 index 000000000..88d4504ac Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_favorite_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_flower.png b/Telegram/Resources/icons/filters/folders_flower.png new file mode 100644 index 000000000..3c452c82a Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_flower.png differ diff --git a/Telegram/Resources/icons/filters/folders_flower@2x.png b/Telegram/Resources/icons/filters/folders_flower@2x.png new file mode 100644 index 000000000..e6ad0af47 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_flower@2x.png differ diff --git a/Telegram/Resources/icons/filters/folders_flower@3x.png b/Telegram/Resources/icons/filters/folders_flower@3x.png new file mode 100644 index 000000000..a143804cc Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_flower@3x.png differ 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 000000000..598554c47 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_flower_active.png differ diff --git a/Telegram/Resources/icons/filters/folders_flower_active@2x.png b/Telegram/Resources/icons/filters/folders_flower_active@2x.png new file mode 100644 index 000000000..d4d897d0b Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_flower_active@2x.png differ 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 000000000..ecb2169eb Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_flower_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_game.png b/Telegram/Resources/icons/filters/folders_game.png new file mode 100644 index 000000000..69119b7d9 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_game.png differ 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 000000000..09fd05fe4 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_game@2x.png differ diff --git a/Telegram/Resources/icons/filters/folders_game@3x.png b/Telegram/Resources/icons/filters/folders_game@3x.png new file mode 100644 index 000000000..84e31f533 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_game@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_game_active.png b/Telegram/Resources/icons/filters/folders_game_active.png new file mode 100644 index 000000000..37ed6f20a Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_game_active.png differ diff --git a/Telegram/Resources/icons/filters/folders_game_active@2x.png b/Telegram/Resources/icons/filters/folders_game_active@2x.png new file mode 100644 index 000000000..5e6d6edc1 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_game_active@2x.png differ 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 000000000..594930468 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_game_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_home.png b/Telegram/Resources/icons/filters/folders_home.png new file mode 100644 index 000000000..59ef27c92 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_home.png differ 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 000000000..4ec5954c7 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_home@2x.png differ 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 000000000..52e147732 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_home@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_home_active.png b/Telegram/Resources/icons/filters/folders_home_active.png new file mode 100644 index 000000000..cfd99be56 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_home_active.png differ diff --git a/Telegram/Resources/icons/filters/folders_home_active@2x.png b/Telegram/Resources/icons/filters/folders_home_active@2x.png new file mode 100644 index 000000000..55aee99ed Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_home_active@2x.png differ 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 000000000..6d148e115 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_home_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_love.png b/Telegram/Resources/icons/filters/folders_love.png new file mode 100644 index 000000000..701cd807f Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_love.png differ diff --git a/Telegram/Resources/icons/filters/folders_love@2x.png b/Telegram/Resources/icons/filters/folders_love@2x.png new file mode 100644 index 000000000..27e5a1dc9 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_love@2x.png differ 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 000000000..41b44363e Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_love@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_love_active.png b/Telegram/Resources/icons/filters/folders_love_active.png new file mode 100644 index 000000000..cea3b50dc Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_love_active.png differ 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 000000000..55997b62d Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_love_active@2x.png differ 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 000000000..b1477710a Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_love_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_mask.png b/Telegram/Resources/icons/filters/folders_mask.png new file mode 100644 index 000000000..86dc6c336 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_mask.png differ diff --git a/Telegram/Resources/icons/filters/folders_mask@2x.png b/Telegram/Resources/icons/filters/folders_mask@2x.png new file mode 100644 index 000000000..42fa98a5d Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_mask@2x.png differ diff --git a/Telegram/Resources/icons/filters/folders_mask@3x.png b/Telegram/Resources/icons/filters/folders_mask@3x.png new file mode 100644 index 000000000..14eb2ded4 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_mask@3x.png differ 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 000000000..837922a11 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_mask_active.png differ 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 000000000..9d7918a6a Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_mask_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/folders_mask_active@3x.png b/Telegram/Resources/icons/filters/folders_mask_active@3x.png new file mode 100644 index 000000000..9c36ab932 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_mask_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_party.png b/Telegram/Resources/icons/filters/folders_party.png new file mode 100644 index 000000000..144a8600d Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_party.png differ 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 000000000..4c4d99218 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_party@2x.png differ 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 000000000..652338b07 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_party@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_party_active.png b/Telegram/Resources/icons/filters/folders_party_active.png new file mode 100644 index 000000000..16ab0fcfb Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_party_active.png differ 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 000000000..e12213328 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_party_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/folders_party_active@3x.png b/Telegram/Resources/icons/filters/folders_party_active@3x.png new file mode 100644 index 000000000..123d39f2a Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_party_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_sport.png b/Telegram/Resources/icons/filters/folders_sport.png new file mode 100644 index 000000000..b59aa62a3 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_sport.png differ diff --git a/Telegram/Resources/icons/filters/folders_sport@2x.png b/Telegram/Resources/icons/filters/folders_sport@2x.png new file mode 100644 index 000000000..16d7bdf57 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_sport@2x.png differ 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 000000000..422af43e6 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_sport@3x.png differ 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 000000000..c3581bc34 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_sport_active.png differ diff --git a/Telegram/Resources/icons/filters/folders_sport_active@2x.png b/Telegram/Resources/icons/filters/folders_sport_active@2x.png new file mode 100644 index 000000000..50138d104 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_sport_active@2x.png differ 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 000000000..f4bdb1f09 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_sport_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_study.png b/Telegram/Resources/icons/filters/folders_study.png new file mode 100644 index 000000000..dbbb4cc2f Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_study.png differ diff --git a/Telegram/Resources/icons/filters/folders_study@2x.png b/Telegram/Resources/icons/filters/folders_study@2x.png new file mode 100644 index 000000000..96b281491 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_study@2x.png differ 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 000000000..c76dc5cf2 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_study@3x.png differ 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 000000000..379d32854 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_study_active.png differ 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 000000000..4ba6d66b9 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_study_active@2x.png differ 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 000000000..dff4822fe Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_study_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_trade.png b/Telegram/Resources/icons/filters/folders_trade.png new file mode 100644 index 000000000..62cbae9a8 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_trade.png differ diff --git a/Telegram/Resources/icons/filters/folders_trade@2x.png b/Telegram/Resources/icons/filters/folders_trade@2x.png new file mode 100644 index 000000000..db435b6eb Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_trade@2x.png differ 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 000000000..c854a9497 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_trade@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_travel.png b/Telegram/Resources/icons/filters/folders_travel.png new file mode 100644 index 000000000..886fcaf53 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_travel.png differ diff --git a/Telegram/Resources/icons/filters/folders_travel@2x.png b/Telegram/Resources/icons/filters/folders_travel@2x.png new file mode 100644 index 000000000..e5df48b95 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_travel@2x.png differ 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 000000000..0897aeee3 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_travel@3x.png differ 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 000000000..7774ca025 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_travel_active.png differ 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 000000000..059b6b0ff Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_travel_active@2x.png differ diff --git a/Telegram/Resources/icons/filters/folders_travel_active@3x.png b/Telegram/Resources/icons/filters/folders_travel_active@3x.png new file mode 100644 index 000000000..2a5b35492 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_travel_active@3x.png differ diff --git a/Telegram/Resources/icons/filters/folders_work.png b/Telegram/Resources/icons/filters/folders_work.png new file mode 100644 index 000000000..9be9132ee Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_work.png differ 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 000000000..a70a41ac1 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_work@2x.png differ 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 000000000..91d9b99cb Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_work@3x.png differ 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 000000000..26fceb1a0 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_work_active.png differ diff --git a/Telegram/Resources/icons/filters/folders_work_active@2x.png b/Telegram/Resources/icons/filters/folders_work_active@2x.png new file mode 100644 index 000000000..2dbd8b3f0 Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_work_active@2x.png differ 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 000000000..c4391725c Binary files /dev/null and b/Telegram/Resources/icons/filters/folders_work_active@3x.png differ diff --git a/Telegram/Resources/icons/info_media_delete.png b/Telegram/Resources/icons/info_media_delete.png index 0898e3791..5702d982e 100644 Binary files a/Telegram/Resources/icons/info_media_delete.png and b/Telegram/Resources/icons/info_media_delete.png differ diff --git a/Telegram/Resources/icons/info_media_delete@2x.png b/Telegram/Resources/icons/info_media_delete@2x.png index 3b3b3fc33..c33801c8f 100644 Binary files a/Telegram/Resources/icons/info_media_delete@2x.png and b/Telegram/Resources/icons/info_media_delete@2x.png differ diff --git a/Telegram/Resources/icons/info_media_delete@3x.png b/Telegram/Resources/icons/info_media_delete@3x.png index 425d12bdb..d4a97c6ae 100644 Binary files a/Telegram/Resources/icons/info_media_delete@3x.png and b/Telegram/Resources/icons/info_media_delete@3x.png differ diff --git a/Telegram/Resources/icons/info_media_forward.png b/Telegram/Resources/icons/info_media_forward.png index c1cff1fc5..8726f1802 100644 Binary files a/Telegram/Resources/icons/info_media_forward.png and b/Telegram/Resources/icons/info_media_forward.png differ diff --git a/Telegram/Resources/icons/info_media_forward@2x.png b/Telegram/Resources/icons/info_media_forward@2x.png index 8f879ebd0..a141387a5 100644 Binary files a/Telegram/Resources/icons/info_media_forward@2x.png and b/Telegram/Resources/icons/info_media_forward@2x.png differ diff --git a/Telegram/Resources/icons/info_media_forward@3x.png b/Telegram/Resources/icons/info_media_forward@3x.png index b623b6a86..89dbab915 100644 Binary files a/Telegram/Resources/icons/info_media_forward@3x.png and b/Telegram/Resources/icons/info_media_forward@3x.png differ diff --git a/Telegram/Resources/icons/settings_folders.png b/Telegram/Resources/icons/settings_folders.png new file mode 100644 index 000000000..bb941b3e2 Binary files /dev/null and b/Telegram/Resources/icons/settings_folders.png differ diff --git a/Telegram/Resources/icons/settings_folders@2x.png b/Telegram/Resources/icons/settings_folders@2x.png new file mode 100644 index 000000000..468d20aee Binary files /dev/null and b/Telegram/Resources/icons/settings_folders@2x.png differ diff --git a/Telegram/Resources/icons/settings_folders@3x.png b/Telegram/Resources/icons/settings_folders@3x.png new file mode 100644 index 000000000..9300c2dc6 Binary files /dev/null and b/Telegram/Resources/icons/settings_folders@3x.png differ diff --git a/Telegram/Resources/icons/stickers_remove.png b/Telegram/Resources/icons/stickers_remove.png deleted file mode 100644 index 8a98e3293..000000000 Binary files a/Telegram/Resources/icons/stickers_remove.png and /dev/null differ diff --git a/Telegram/Resources/icons/stickers_remove@2x.png b/Telegram/Resources/icons/stickers_remove@2x.png deleted file mode 100644 index d9f22acd2..000000000 Binary files a/Telegram/Resources/icons/stickers_remove@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/stickers_remove@3x.png b/Telegram/Resources/icons/stickers_remove@3x.png deleted file mode 100644 index ce5bd3a25..000000000 Binary files a/Telegram/Resources/icons/stickers_remove@3x.png and /dev/null differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 67046818f..0bfba6cd6 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 belong to this folder."; "lng_contacts_loading" = "Loading..."; "lng_contacts_not_found" = "No contacts found"; "lng_dlg_search_for_messages" = "Search for messages"; @@ -344,6 +345,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"; @@ -1839,6 +1842,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_filter_all_admins" = "All users and admins"; "lng_admin_log_about" = "What is this?"; "lng_admin_log_about_text" = "This is a list of all service actions taken by the group's members and admins in the last 48 hours."; +"lng_admin_log_about_text_channel" = "This is a list of all service actions taken by the channel's admins in the last 48 hours."; "lng_admin_log_no_results_title" = "No actions found"; "lng_admin_log_no_results_text" = "No recent actions that match your query have been found."; "lng_admin_log_no_results_search_text" = "No recent actions that contain '{query}' have been found."; @@ -2228,6 +2232,62 @@ 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" = "Edit"; +"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" = "Create new folder"; +"lng_filters_about" = "Create folders for different groups of chats and quickly switch between them."; +"lng_filters_recommended" = "Recommended folders"; +"lng_filters_recommended_add" = "Add"; +"lng_filters_restore" = "Undo"; +"lng_filters_new" = "New Folder"; +"lng_filters_edit" = "Edit Folder"; +"lng_filters_new_name" = "Folder name"; +"lng_filters_add_chats" = "Add chats"; +"lng_filters_remove_chats" = "Remove chats"; +"lng_filters_include" = "Included Chats"; +"lng_filters_include_about" = "Choose chats and types of chats that will appear in this folder."; +"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_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"; +"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_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."; +"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" = "Archived"; +"lng_filters_type_no_muted" = "Muted"; +"lng_filters_type_no_read" = "Read"; +"lng_filters_icon_header" = "Choose an icon"; +"lng_filters_error_pinned_max" = "Sorry, you can't pin any more chats to the top."; +"lng_filters_context_edit" = "Edit Folder"; +"lng_filters_context_remove" = "Remove"; +"lng_filters_remove_sure" = "This will remove the folder, your chats will not be deleted."; +"lng_filters_remove_yes" = "Remove"; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; @@ -2400,4 +2460,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "ktg_settings_recent_stickers_limit#other" = "Recent stickers: show {count} stickers"; "ktg_settings_recent_stickers_limit_none" = "Recent stickers: hide all"; +"ktg_filters_default" = "Default folder"; +"ktg_filters_context_edit_all" = "Edit folders"; +"ktg_filters_context_make_default" = "Make folder default"; + // Keys finished diff --git a/Telegram/Resources/langs/rewrites/ru.json b/Telegram/Resources/langs/rewrites/ru.json index 09ab40c1a..ef61e45ee 100644 --- a/Telegram/Resources/langs/rewrites/ru.json +++ b/Telegram/Resources/langs/rewrites/ru.json @@ -97,5 +97,8 @@ "many": "Недавние стикеры: показывать {count} стикеров", "other": "Недавние стикеры: показывать {count} стикеров" }, - "ktg_settings_recent_stickers_limit_none": "Недавние стикеры: скрыть все" + "ktg_settings_recent_stickers_limit_none": "Недавние стикеры: скрыть все", + "ktg_filters_default": "Папка по умолчанию", + "ktg_filters_context_edit_all": "Изменить папки", + "ktg_filters_context_make_default": "Сделать папкой по умолчанию" } diff --git a/Telegram/Resources/night-green.tdesktop-theme b/Telegram/Resources/night-green.tdesktop-theme index 2a47a1c14..6158491ac 100644 Binary files a/Telegram/Resources/night-green.tdesktop-theme and b/Telegram/Resources/night-green.tdesktop-theme differ diff --git a/Telegram/Resources/night.tdesktop-theme b/Telegram/Resources/night.tdesktop-theme index c9d188ec1..6a557e631 100644 Binary files a/Telegram/Resources/night.tdesktop-theme and b/Telegram/Resources/night.tdesktop-theme differ diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc index daacdb128..32cc459e5 100644 --- a/Telegram/Resources/qrc/telegram/telegram.qrc +++ b/Telegram/Resources/qrc/telegram/telegram.qrc @@ -57,6 +57,7 @@ ../../art/logo_256_no_margin_red.png ../../art/logo_256_no_margin_old.png ../../art/sunrise.jpg + ../../art/dice_idle.tgs ../../day-blue.tdesktop-theme ../../night.tdesktop-theme ../../night-green.tdesktop-theme 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/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index ab2fefff9..5db8923b4 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -9,7 +9,7 @@ + Version="1.9.22.0" /> Telegram Desktop Telegram FZ-LLC 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..59c29f72b --- /dev/null +++ b/Telegram/SourceFiles/api/api_chat_filters.h @@ -0,0 +1,20 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Main { +class Session; +} // namespace Main + +namespace Api { + +void SaveNewFilterPinned( + not_null session, + FilterId filterId); + +} // namespace Api 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/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/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 5dbea9760..5da6934f7 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" @@ -25,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" @@ -247,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(); }); } @@ -483,7 +492,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); @@ -512,7 +523,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( @@ -853,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; @@ -885,6 +894,25 @@ void ApiWrap::refreshDialogsLoadBlocked() { && (_dialogsLoadState->offsetDate <= _dialogsLoadTill); } +void ApiWrap::requestMoreDialogsIfNeeded() { + const auto dialogsReady = !_dialogsLoadState + || _dialogsLoadState->listReceived; + if (_session->data().chatsFilters().loadNextExceptions(dialogsReady)) { + return; + } else 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, @@ -4793,7 +4821,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/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/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)) { 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 f1fc45e13..00a664ef6 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/confirm_box.cpp b/Telegram/SourceFiles/boxes/confirm_box.cpp index c580d3716..4839c5269 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.cpp +++ b/Telegram/SourceFiles/boxes/confirm_box.cpp @@ -43,13 +43,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include -TextParseOptions _confirmBoxTextOptions = { - TextParseLinks | TextParseMultiline | TextParseMarkdown | TextParseRichText, // flags +namespace { + +TextParseOptions kInformBoxTextOptions = { + (TextParseLinks + | TextParseMultiline + | TextParseMarkdown + | TextParseRichText), // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir }; +TextParseOptions kMarkedTextBoxOptions = { + (TextParseLinks + | TextParseMultiline + | TextParseMarkdown + | TextParseRichText + | TextParseMentions + | TextParseHashtags), // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir +}; + +} // namespace + ConfirmBox::ConfirmBox( QWidget*, const QString &text, @@ -185,11 +204,11 @@ void ConfirmBox::init(const QString &text) { _text.setText( st::boxLabelStyle, text, - _informative ? _confirmBoxTextOptions : _textPlainOptions); + _informative ? kInformBoxTextOptions : _textPlainOptions); } void ConfirmBox::init(const TextWithEntities &text) { - _text.setMarkedText(st::boxLabelStyle, text, _confirmBoxTextOptions); + _text.setMarkedText(st::boxLabelStyle, text, kMarkedTextBoxOptions); } void ConfirmBox::prepare() { @@ -326,7 +345,16 @@ InformBox::InformBox(QWidget*, const TextWithEntities &text, const QString &done MaxInviteBox::MaxInviteBox(QWidget*, not_null channel) : BoxContent() , _channel(channel) -, _text(st::boxLabelStyle, tr::lng_participant_invite_sorry(tr::now, lt_count, Global::ChatSizeMax()), _confirmBoxTextOptions, st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right()) { +, _text( + st::boxLabelStyle, + tr::lng_participant_invite_sorry( + tr::now, + lt_count, + Global::ChatSizeMax()), + kInformBoxTextOptions, + (st::boxWidth + - st::boxPadding.left() + - st::defaultBox.buttonPadding.right())) { } void MaxInviteBox::prepare() { 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 new file mode 100644 index 000000000..0955fd11d --- /dev/null +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -0,0 +1,702 @@ +/* +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 "chat_helpers/emoji_suggestions_widget.h" +#include "ui/layers/generic_box.h" +#include "ui/text/text_utilities.h" +#include "ui/widgets/checkbox.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 "core/kotato_settings.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" +#include "window/window_session_controller.h" +#include "window/window_controller.h" +#include "apiwrap.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 = 12; + +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::NoRead, + Flag::NoArchived, +}; + +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 refresh(); + void removeFlag(Flag flag); + void removePeer(not_null history); + + std::vector _removeFlag; + std::vector _removePeer; + + rpl::event_stream _flagRemoved; + rpl::event_stream> _peerRemoved; + +}; + +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(); + const auto preview = content->add(object_ptr( + content, + rules.flags() & flags, + (rules.*peers)())); + + preview->flagRemoved( + ) | rpl::start_with_next([=](Flag flag) { + const auto rules = data->current(); + auto computed = Data::ChatFilter( + rules.id(), + rules.title(), + rules.iconEmoji(), + (rules.flags() & ~flag), + rules.always(), + rules.pinned(), + rules.never()); + updateDefaultTitle(computed); + *data = std::move(computed); + }, preview->lifetime()); + + preview->peerRemoved( + ) | rpl::start_with_next([=](not_null history) { + 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); + auto computed = Data::ChatFilter( + rules.id(), + rules.title(), + rules.iconEmoji(), + rules.flags(), + std::move(always), + std::move(pinned), + std::move(never)); + updateDefaultTitle(computed); + *data = std::move(computed); + }, 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; +} + +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) { + PaintFilterChatsTypeIcon( + p, + flag, + iconLeft, + top + iconTop, + width(), + st.photoSize); + + p.setPen(st::contactsNameFg); + p.drawTextLeft( + nameLeft, + top + nameTop, + width(), + FilterChatsTypeName(flag)); + top += st.height; + } + for (const auto &[history, button] : _removePeer) { + 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; + } +} + +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 updateDefaultTitle, + 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, + 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) { + return window->session().data().history(peer); + }); + auto changed = base::flat_set>{ + histories.begin(), + histories.end() + }; + auto removeFrom = include ? rules.never() : rules.always(); + for (const auto &history : changed) { + removeFrom.remove(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)); + auto computed = Data::ChatFilter( + 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)); + updateDefaultTitle(computed); + *data = computed; + refresh(); + box->closeBox(); + })); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + }; + window->window().show( + Box( + std::move(controller), + std::move(initBox)), + Ui::LayerOption::KeepOther); +} + +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); +} + +[[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( + 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()); + 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::windowFilterNameInput, + tr::lng_filters_new_name(), + 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 }); + 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, + box, + content, + name, + data); + + 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(); + }); + + const auto isCurrent = filter.id() == cDefaultFilterId(); + const auto checkboxDefault = content->add( + object_ptr( + box, + tr::ktg_filters_default(tr::now), + (creating ? false : isCurrent), + st::defaultBoxCheckbox), + style::margins( + st::boxPadding.left(), + st::boxPadding.bottom(), + st::boxPadding.right(), + st::boxPadding.bottom())); + + AddSkip(content); + AddDivider(content); + AddSkip(content); + AddSubsectionTitle(content, tr::lng_filters_include()); + + const auto include = SetupChatsPreview( + content, + data, + updateDefaultTitle, + kTypes, + &Data::ChatFilter::always); + + const auto includeAdd = AddButton( + content, + tr::lng_filters_add_chats() | Ui::Text::ToUpper(), + st::settingsUpdate); + + AddSkip(content); + content->add( + object_ptr( + content, + tr::lng_filters_include_about(), + st::boxDividerLabel), + st::windowFilterAboutPadding); + AddDivider(content); + AddSkip(content); + + AddSubsectionTitle(content, tr::lng_filters_exclude()); + + const auto exclude = SetupChatsPreview( + content, + data, + updateDefaultTitle, + kExcludeTypes, + &Data::ChatFilter::never); + + const auto excludeAdd = AddButton( + content, + tr::lng_filters_remove_chats() | Ui::Text::ToUpper(), + st::settingsUpdate); + + AddSkip(content); + content->add( + object_ptr( + content, + tr::lng_filters_exclude_about(), + st::boxDividerLabel), + st::windowFilterAboutPadding); + + const auto refreshPreviews = [=] { + 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, + updateDefaultTitle, + refreshPreviews); + }); + excludeAdd->setClickedCallback([=] { + EditExceptions( + window, + box, + kExcludeTypes, + data, + updateDefaultTitle, + refreshPreviews); + }); + + const auto save = [=] { + const auto title = name->getLastText().trimmed(); + const auto rules = data->current(); + const auto checked = checkboxDefault && checkboxDefault->checked(); + const auto result = Data::ChatFilter( + rules.id(), + title, + rules.iconEmoji(), + rules.flags(), + rules.always(), + rules.pinned(), + rules.never(), + checked); + if (title.isEmpty()) { + name->showError(); + return; + } else if (!(rules.flags() & kTypes) && rules.always().empty()) { + window->window().showToast(tr::lng_filters_empty(tr::now)); + return; + } else if ((rules.flags() == (kTypes | Flag::NoArchived)) + && rules.always().empty() + && rules.never().empty()) { + window->window().showToast(tr::lng_filters_default(tr::now)); + return; + } + box->closeBox(); + + doneCallback(result); + }; + box->addButton( + creating ? tr::lng_filters_create_button() : tr::lng_settings_save(), + save); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + +void EditExistingFilter( + not_null window, + FilterId id) { + const auto session = &window->session(); + const auto &list = session->data().chatsFilters().list(); + const auto i = ranges::find(list, id, &Data::ChatFilter::id); + if (i == end(list)) { + return; + } + const auto doneCallback = [=](const Data::ChatFilter &result) { + Expects(id == result.id()); + + const auto tl = result.tl(); + session->data().chatsFilters().apply(MTP_updateDialogFilter( + MTP_flags(MTPDupdateDialogFilter::Flag::f_filter), + MTP_int(id), + tl)); + session->api().request(MTPmessages_UpdateDialogFilter( + MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter), + MTP_int(id), + tl + )).send(); + const auto isCurrentDefault = result.id() == cDefaultFilterId(); + if ((isCurrentDefault && !result.isDefault()) + || (!isCurrentDefault && result.isDefault())) { + cSetDefaultFilterId(result.isDefault() ? result.id() : 0); + KotatoSettings::Write(); + } + }; + window->window().show(Box( + EditFilterBox, + window, + *i, + crl::guard(session, doneCallback))); +} 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..43b97adb1 --- /dev/null +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.h @@ -0,0 +1,28 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/layers/generic_box.h" + +namespace 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); + +void EditExistingFilter( + not_null window, + FilterId id); 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..bc783f194 --- /dev/null +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp @@ -0,0 +1,504 @@ +/* +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" +#include "ui/widgets/labels.h" +#include "ui/wrap/vertical_layout.h" +#include "base/object_ptr.h" +#include "styles/style_window.h" +#include "styles/style_boxes.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::NoRead, + Flag::NoArchived, +}; + +struct RowSelectionChange { + not_null row; + bool checked = false; +}; + +class TypeRow final : public PeerListRow { +public: + explicit TypeRow(Flag flag); + + QString generateName() override; + QString generateShortName() override; + PaintRoundImageCallback generatePaintUserpicCallback() override; + +private: + [[nodiscard]] Flag flag() const; + +}; + +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; + 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 selectedChanges() const; + [[nodiscard]] auto rowSelectionChanges() const + -> rpl::producer; + +private: + [[nodiscard]] std::unique_ptr createRow(Flag flag) const; + [[nodiscard]] Flags collectSelectedOptions() const; + + const not_null _session; + Flags _options; + + rpl::event_stream<> _selectionChanged; + rpl::event_stream _rowSelectionChanges; + +}; + +[[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(); +} + +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 { + 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) { +} + +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) { + 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::selectedChanges() const { + return _rowSelectionChanges.events( + ) | rpl::map([=] { + return collectSelectedOptions(); + }); +} + +auto TypeController::rowSelectionChanges() const +-> rpl::producer { + return _rowSelectionChanges.events(); +} + +} // 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); + switch (cUserpicCornersType()) { + case 0: + p.drawRoundedRect( + rect, + 0, 0); + break; + + case 1: + p.drawRoundedRect( + rect, + st::buttonRadius, st::buttonRadius); + break; + + case 2: + p.drawRoundedRect( + rect, + st::dateRadius, st::dateRadius); + break; + + default: + p.drawEllipse(rect); + } + icon.paintInCenter(p, rect); +} + +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) +, _options(options) +, _selected(selected) { +} + +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(); +} + +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()); + + 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(); +} + +object_ptr EditFilterChatsListController::prepareTypesList() { + auto result = object_ptr((QWidget*)nullptr); + const auto container = result.data(); + 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(), + _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); + this->delegate()->peerListSetForeignRowChecked( + row, + true, + anim::type::instant); + } + } + } + container->add(object_ptr( + container, + st::membersMarginBottom)); + container->add(CreateSectionSubtitle( + container, + tr::lng_filters_edit_chats())); + + 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, + anim::type::normal); + }, _lifetime); + + _deselectOption = [=](PeerListRowId itemId) { + if (const auto row = delegate->peerListFindRow(itemId)) { + delegate->peerListSetRowChecked(row, false); + } + }; + + return result; +} + +auto EditFilterChatsListController::createRow(not_null history) +-> std::unique_ptr { + return history->inChatList() + ? std::make_unique(history) + : nullptr; +} + +void EditFilterChatsListController::updateTitle() { + 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)); +} 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..e993cafb7 --- /dev/null +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.h @@ -0,0 +1,77 @@ +/* +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 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; + using Flags = Data::ChatFilter::Flags; + + EditFilterChatsListController( + not_null navigation, + rpl::producer title, + Flags options, + Flags selected, + const base::flat_set> &peers); + + [[nodiscard]] Main::Session &session() const override; + [[nodiscard]] Flags chosenOptions() const { + return _selected; + } + + 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; + std::unique_ptr createRow(not_null history) override; + [[nodiscard]] object_ptr prepareTypesList(); + + void updateTitle(); + + const not_null _navigation; + rpl::producer _title; + base::flat_set> _peers; + 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 6434c8c8e..1e1f12c9e 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -34,10 +34,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); @@ -76,9 +75,12 @@ 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, PeerListRow::SetStyle::Animated); + content()->changeCheckState(row, false, anim::type::normal); update(); } _controller->itemDeselectedHook(peer); @@ -186,21 +188,35 @@ 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); // This call deletes row from _searchRows. - _select->entity()->clearQuery(); + //_select->entity()->clearQuery(); } else { // The itemRemovedCallback will call changeCheckState() here. - _select->entity()->removeItem(peer->id); + _select->entity()->removeItem(row->id()); peerListUpdateRow(row); } } +void PeerListBox::peerListSetForeignRowChecked( + not_null row, + bool checked, + anim::type animated) { + if (checked) { + addSelectItem(row, animated); + + // 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); } @@ -296,38 +312,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() { @@ -343,13 +383,16 @@ 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; } -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) @@ -360,6 +403,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(); } @@ -376,7 +428,7 @@ void PeerListRow::clearCustomStatus() { } void PeerListRow::refreshStatus() { - if (!_initialized || _statusType == StatusType::Custom) { + if (!_initialized || special() || _statusType == StatusType::Custom) { return; } _statusType = StatusType::LastSeen; @@ -420,11 +472,31 @@ 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() { + const auto saved = _isSavedMessagesChat; + const auto peer = this->peer(); + 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 PeerListRow::invalidatePixmapsCache() { if (_checkbox) { @@ -433,7 +505,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( @@ -505,10 +579,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); + } else if (const auto callback = generatePaintUserpicCallback()) { + callback(p, x, y, outerWidth, st.photoSize); } } @@ -594,18 +666,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( @@ -672,25 +745,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); } } @@ -706,7 +784,7 @@ bool PeerListContent::addingToSearchIndex() const { } void PeerListContent::addToSearchIndex(not_null row) { - if (row->isSearchResult()) { + if (row->isSearchResult() || row->special()) { return; } @@ -799,8 +877,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), @@ -1143,8 +1223,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); @@ -1155,17 +1238,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); @@ -1231,12 +1314,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); @@ -1347,7 +1435,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(); + } } } } @@ -1360,13 +1451,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()) { @@ -1390,7 +1481,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; } @@ -1399,8 +1490,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 8903aefd0..0490bd320 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,28 @@ 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]] virtual auto generatePaintUserpicCallback() + -> PaintRoundImageCallback; + void setCustomStatus(const QString &status); void clearCustomStatus(); - virtual ~PeerListRow(); - // Box interface. virtual int nameIconWidth() const; virtual void paintNameIcon( @@ -146,19 +163,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(); @@ -200,9 +214,13 @@ protected: return _initialized; } + explicit PeerListRow(PeerListRowId id); + 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, @@ -212,7 +230,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; @@ -253,8 +271,12 @@ 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 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; @@ -264,9 +286,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(); } @@ -279,7 +309,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; }; @@ -353,6 +384,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); @@ -374,8 +411,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() { @@ -478,7 +515,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) { @@ -685,10 +725,12 @@ public: void peerListSetRowChecked( not_null row, bool checked) override { - _content->changeCheckState( - row, - checked, - PeerListRow::SetStyle::Animated); + _content->changeCheckState(row, checked, anim::type::normal); + } + void peerListSetForeignRowChecked( + not_null row, + bool checked, + anim::type animated) override { } int peerListFullRowsCount() override { return _content->fullRowsCount(); @@ -778,7 +820,11 @@ public: void peerListSetRowChecked( not_null row, bool checked) override; - bool peerListIsRowSelected(not_null peer) override; + void peerListSetForeignRowChecked( + not_null row, + bool checked, + anim::type animated) override; + bool peerListIsRowChecked(not_null row) override; int peerListSelectedRowsCount() override; std::vector> peerListCollectSelectedRows() override; void peerListScrollToTop() override; @@ -792,15 +838,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/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) { diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 9719bee0b..3b419e06e 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -457,7 +457,7 @@ void ShareBox::addPeerToMultiSelect(PeerData *peer, bool skipAnimation) { void ShareBox::innerSelectedChanged(PeerData *peer, bool checked) { if (checked) { addPeerToMultiSelect(peer); - _select->clearQuery(); + //_select->clearQuery(); } else { _select->removeItem(peer->id); } @@ -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/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/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 7aa5580d9..b691e5a60 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -41,8 +41,8 @@ stickersRemove: IconButton(defaultIconButton) { width: 40px; height: 40px; - icon: icon {{ "stickers_remove", menuIconFg }}; - iconOver: icon {{ "stickers_remove", menuIconFgOver }}; + icon: icon {{ "info_media_delete", menuIconFg }}; + iconOver: icon {{ "info_media_delete", menuIconFgOver }}; rippleAreaSize: 40px; rippleAreaPosition: point(0px, 0px); @@ -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/chat_helpers/spellchecker_common.cpp b/Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp index 881a495dc..97c6bc973 100644 --- a/Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp +++ b/Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp @@ -31,6 +31,11 @@ using namespace Storage::CloudBlob; constexpr auto kDictExtensions = { "dic", "aff" }; +constexpr auto kExceptions = { + AppFile, + "\xd0\xa2\xd0\xb5\xd0\xbb\xd0\xb5\xd0\xb3\xd1\x80\xd0\xb0\xd0\xbc"_cs, +}; + // 31 - QLocale::English, 91 - QLocale::Portuguese. constexpr auto kLangsForLWC = { 31, 91 }; // 225 - QLocale::UnitesStates, 30 - QLocale::Brazil. @@ -167,6 +172,19 @@ void DownloadDictionaryInBackground( BackgroundLoaderChanged.fire_copy(id); } +void AddExceptions() { + const auto exceptions = ranges::view::all( + kExceptions + ) | ranges::views::transform([](const auto &word) { + return word.utf16(); + }) | ranges::views::filter([](const auto &word) { + return !(Platform::Spellchecker::IsWordInDictionary(word) + || Spellchecker::IsWordSkippable(&word)); + }) | ranges::to_vector; + + ranges::for_each(exceptions, Platform::Spellchecker::AddWord); +} + } // namespace DictLoaderPtr GlobalLoader() { @@ -358,17 +376,34 @@ void Start(not_null session) { } }); const auto settings = &session->settings(); - const auto guard = gsl::finally([=]{ - if (settings->spellcheckerEnabled()) { - Platform::Spellchecker::UpdateLanguages( - settings->dictionariesEnabled()); - } + const auto onEnabled = [=](auto enabled) { + Platform::Spellchecker::UpdateLanguages( + enabled + ? settings->dictionariesEnabled() + : std::vector()); + }; + + const auto guard = gsl::finally([=] { + onEnabled(settings->spellcheckerEnabled()); }); if (Platform::Spellchecker::IsSystemSpellchecker()) { + + const auto scriptsLifetime = + session->lifetime().make_state(); + + Spellchecker::SupportedScriptsChanged( + ) | rpl::start_with_next([=] { + AddExceptions(); + scriptsLifetime->destroy(); + }, *scriptsLifetime); + return; } + Spellchecker::SupportedScriptsChanged( + ) | rpl::start_with_next(AddExceptions, session->lifetime()); + Spellchecker::SetWorkingDirPath(DictionariesPath()); settings->dictionariesEnabledChanges( @@ -377,12 +412,7 @@ void Start(not_null session) { }, session->lifetime()); settings->spellcheckerEnabledChanges( - ) | rpl::start_with_next([=](auto enabled) { - Platform::Spellchecker::UpdateLanguages( - enabled - ? settings->dictionariesEnabled() - : std::vector()); - }, session->lifetime()); + ) | rpl::start_with_next(onEnabled, session->lifetime()); const auto method = QGuiApplication::inputMethod(); 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/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/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index f1cb2a906..f537fe070 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -545,13 +545,6 @@ void Application::switchDebugMode() { } } -void Application::switchWorkMode() { - Global::SetDialogsModeEnabled(!Global::DialogsModeEnabled()); - Global::SetDialogsMode(Dialogs::Mode::All); - 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 206543601..1c3350cc6 100644 --- a/Telegram/SourceFiles/core/application.h +++ b/Telegram/SourceFiles/core/application.h @@ -230,7 +230,6 @@ public: void handleAppDeactivated(); void switchDebugMode(); - void switchWorkMode(); void switchTestMode(); void writeInstallBetaVersionsSetting(); diff --git a/Telegram/SourceFiles/core/changelogs.cpp b/Telegram/SourceFiles/core/changelogs.cpp index e0bfcf8b7..76c890ca4 100644 --- a/Telegram/SourceFiles/core/changelogs.cpp +++ b/Telegram/SourceFiles/core/changelogs.cpp @@ -49,6 +49,11 @@ std::map BetaLogs() { "\xE2\x80\xA2 Fix blurred thumbnails in albums with video files.\n" "\xE2\x80\xA2 Fix a possible crash in animated stickers rendering." + }, + { + 1009022, + "\xE2\x80\xA2 Organize chats into Chat Folders " + "if you have too many chats.\n" } }; }; diff --git a/Telegram/SourceFiles/core/file_utilities.cpp b/Telegram/SourceFiles/core/file_utilities.cpp index 3be9ae66a..e70eb1ab3 100644 --- a/Telegram/SourceFiles/core/file_utilities.cpp +++ b/Telegram/SourceFiles/core/file_utilities.cpp @@ -323,9 +323,12 @@ bool GetDefault( } QString file; + const auto resolvedParent = (parent && parent->window()->isVisible()) + ? parent->window() + : Core::App().getFileDialogParent(); Core::App().notifyFileDialogShown(true); if (type == Type::ReadFiles) { - files = QFileDialog::getOpenFileNames(Core::App().getFileDialogParent(), caption, startFile, filter); + files = QFileDialog::getOpenFileNames(resolvedParent, caption, startFile, filter); QString path = files.isEmpty() ? QString() : QFileInfo(files.back()).absoluteDir().absolutePath(); if (!path.isEmpty() && path != cDialogLastPath()) { cSetDialogLastPath(path); @@ -333,11 +336,11 @@ bool GetDefault( } return !files.isEmpty(); } else if (type == Type::ReadFolder) { - file = QFileDialog::getExistingDirectory(Core::App().getFileDialogParent(), caption, startFile); + file = QFileDialog::getExistingDirectory(resolvedParent, caption, startFile); } else if (type == Type::WriteFile) { - file = QFileDialog::getSaveFileName(Core::App().getFileDialogParent(), caption, startFile, filter); + file = QFileDialog::getSaveFileName(resolvedParent, caption, startFile, filter); } else { - file = QFileDialog::getOpenFileName(Core::App().getFileDialogParent(), caption, startFile, filter); + file = QFileDialog::getOpenFileName(resolvedParent, caption, startFile, filter); } Core::App().notifyFileDialogShown(false); diff --git a/Telegram/SourceFiles/core/kotato_settings.cpp b/Telegram/SourceFiles/core/kotato_settings.cpp index 1ab5e2484..4d2a55083 100644 --- a/Telegram/SourceFiles/core/kotato_settings.cpp +++ b/Telegram/SourceFiles/core/kotato_settings.cpp @@ -333,6 +333,24 @@ bool Manager::readCustomFile() { cSetCustomAppIcon(v); } }); + + ReadObjectOption(settings, "folders", [&](auto o) { + ReadIntOption(o, "default", [&](auto v) { + cSetDefaultFilterId(v); + }); + + ReadBoolOption(o, "count_unmuted_only", [&](auto v) { + cSetUnmutedFilterCounterOnly(v); + }); + + ReadBoolOption(o, "hide_edit_button", [&](auto v) { + cSetHideFilterEditButton(v); + }); + + ReadBoolOption(o, "hide_names", [&](auto v) { + cSetHideFilterNames(v); + }); + }); return true; } @@ -378,6 +396,14 @@ void Manager::writeDefaultFile() { settings.insert(qsl("always_show_top_userpic"), cShowTopBarUserpic()); settings.insert(qsl("custom_app_icon"), cCustomAppIcon()); + auto settingsFolders = QJsonObject(); + settingsFolders.insert(qsl("default"), cDefaultFilterId()); + settingsFolders.insert(qsl("count_unmuted_only"), cUnmutedFilterCounterOnly()); + settingsFolders.insert(qsl("hide_edit_button"), cHideFilterEditButton()); + settingsFolders.insert(qsl("hide_names"), cHideFilterNames()); + + settings.insert(qsl("folders"), settingsFolders); + auto settingsScales = QJsonArray(); settings.insert(qsl("scales"), settingsScales); @@ -442,6 +468,14 @@ void Manager::writeCurrentSettings() { settings.insert(qsl("userpic_corner_type"), cUserpicCornersType()); settings.insert(qsl("always_show_top_userpic"), cShowTopBarUserpic()); settings.insert(qsl("custom_app_icon"), cCustomAppIcon()); + + auto settingsFolders = QJsonObject(); + settingsFolders.insert(qsl("default"), cDefaultFilterId()); + settingsFolders.insert(qsl("count_unmuted_only"), cUnmutedFilterCounterOnly()); + settingsFolders.insert(qsl("hide_edit_button"), cHideFilterEditButton()); + settingsFolders.insert(qsl("hide_names"), cHideFilterNames()); + + settings.insert(qsl("folders"), settingsFolders); auto settingsScales = QJsonArray(); auto currentScales = cInterfaceScales(); diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index fdb9a0198..c4e944267 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/share_box.h" #include "boxes/connection_box.h" #include "boxes/sticker_set_box.h" +#include "boxes/sessions_box.h" #include "passport/passport_form_controller.h" #include "window/window_session_controller.h" #include "data/data_session.h" @@ -27,6 +28,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_cloud_themes.h" #include "data/data_channel.h" #include "media/player/media_player_instance.h" +#include "window/window_session_controller.h" +#include "settings/settings_common.h" #include "mainwindow.h" #include "mainwidget.h" #include "main/main_session.h" @@ -333,6 +336,31 @@ bool ResolvePrivatePost( return true; } +bool ResolveSettings( + Main::Session *session, + const Match &match, + const QVariant &context) { + const auto section = match->captured(1).mid(1).toLower(); + if (!session) { + if (section.isEmpty()) { + App::wnd()->showSettings(); + return true; + } + return false; + } + if (section == qstr("devices")) { + Ui::show(Box(session)); + return true; + } + const auto type = (section == qstr("folders")) + ? ::Settings::Type::Folders + : (section == qstr("kotato")) + ? ::Settings::Type::Kotato + : ::Settings::Type::Main; + App::wnd()->sessionController()->showSettings(type); + return true; +} + bool HandleUnknown( Main::Session *session, const Match &match, @@ -454,6 +482,10 @@ const std::vector &LocalUrlHandlers() { qsl("^privatepost/?\\?(.+)(#|$)"), ResolvePrivatePost }, + { + qsl("^settings(/folders|/devices|/kotato)?$"), + ResolveSettings + }, { qsl("^([^\\?]+)(\\?|#|$)"), HandleUnknown diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 8a9ca7ad5..856ca8458 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,9 +22,9 @@ constexpr auto AppId = "{C4A4AE8F-B9F7-4CC7-8A6C-BF7EEE87ACA5}"_cs; constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs; constexpr auto AppName = "Kotatogram Desktop"_cs; constexpr auto AppFile = "Kotatogram"_cs; -constexpr auto AppVersion = 1009021; -constexpr auto AppVersionStr = "1.9.21"; -constexpr auto AppBetaVersion = false; +constexpr auto AppVersion = 1009022; +constexpr auto AppVersionStr = "1.9.22"; +constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppKotatoVersion = 1002000; constexpr auto AppKotatoVersionStr = "1.2"; diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 08a7956f0..acd139787 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -88,6 +88,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 33a8a2d44..2ab4f92e4 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.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), diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp new file mode 100644 index 000000000..ad9606429 --- /dev/null +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -0,0 +1,627 @@ +/* +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" +#include "data/data_session.h" +#include "data/data_folder.h" +#include "data/data_histories.h" +#include "dialogs/dialogs_main_list.h" +#include "ui/ui_utility.h" +#include "main/main_session.h" +#include "apiwrap.h" + +namespace Data { +namespace { + +constexpr auto kRefreshSuggestedTimeout = 7200 * crl::time(1000); +constexpr auto kLoadExceptionsAfter = 100; +constexpr auto kLoadExceptionsPerRequest = 100; + +} // namespace + +ChatFilter::ChatFilter(FilterId id) : _id(id) { +} + +ChatFilter::ChatFilter( + FilterId id, + const QString &title, + const QString &iconEmoji, + Flags flags, + base::flat_set> always, + std::vector> pinned, + base::flat_set> never, + bool isDefault) +: _id(id) +, _title(title) +, _iconEmoji(iconEmoji) +, _always(std::move(always)) +, _pinned(std::move(pinned)) +, _never(std::move(never)) +, _flags(flags) +, _isDefault(isDefault) { +} + +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::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::NoArchived : 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 MTPDinputPeerSelf &data) { + return (PeerData*)owner->session().user(); + }, [&](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::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()), + qs(data.vemoticon().value_or_empty()), + flags, + std::move(list), + std::move(pinned), + { never.begin(), never.end() }, + (data.vid().v == cDefaultFilterId())); + }); +} + +MTPDialogFilter ChatFilter::tl(FilterId replaceId) 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::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::NoArchived) + ? TLFlag::f_exclude_archived + : TLFlag(0)); + 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()); + for (const auto history : _never) { + never.push_back(history->peer->input); + } + return MTP_dialogFilter( + MTP_flags(flags | TLFlag::f_emoticon), + MTP_int(replaceId ? replaceId : _id), + MTP_string(_title), + MTP_string(_iconEmoji), + MTP_vector(pinned), + MTP_vector(include), + MTP_vector(never)); +} + +FilterId ChatFilter::id() const { + return _id; +} + +QString ChatFilter::title() const { + return _title; +} + +bool ChatFilter::isDefault() const { + return _isDefault; +} + +QString ChatFilter::iconEmoji() const { + return _iconEmoji; +} + +ChatFilter::Flags ChatFilter::flags() const { + return _flags; +} + +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; +} + +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 + : user->isContact() + ? Flag::Contacts + : Flag::NonContacts; + } else if (const auto chat = peer->asChat()) { + return Flag::Groups; + } else if (const auto channel = peer->asChannel()) { + if (channel->isBroadcast()) { + return Flag::Channels; + } else { + return Flag::Groups; + } + } else { + Unexpected("Peer type in ChatFilter::contains."); + } + }(); + if (_never.contains(history)) { + return false; + } + return false + || ((_flags & flag) + && (!(_flags & Flag::NoMuted) + || !history->mute() + || (history->hasUnreadMentions() + && history->folderKnown() + && !history->folder())) + && (!(_flags & Flag::NoRead) + || history->unreadCount() + || history->unreadMark() + || history->hasUnreadMentions() + || history->fakeUnreadWhileOpened()) + && (!(_flags & Flag::NoArchived) + || (history->folderKnown() && !history->folder()))) + || _always.contains(history); +} + +ChatFilters::ChatFilters(not_null owner) : _owner(owner) { + //using Flag = ChatFilter::Flag; + //const auto all = Flag::Contacts + // | Flag::NonContacts + // | Flag::Groups + // | Flag::Channels + // | Flag::Bots + // | Flag::NoArchived; + //_list.push_back( + // ChatFilter(1, "Unmuted", all | Flag::NoMuted, {}, {})); + //_list.push_back( + // ChatFilter(2, "Unread", all | Flag::NoRead, {}, {})); + load(); +} + +ChatFilters::~ChatFilters() = default; + +not_null ChatFilters::chatsList(FilterId filterId) { + auto &pointer = _chatsLists[filterId]; + if (!pointer) { + pointer = std::make_unique( + filterId, + rpl::single(ChatFilter::kPinnedLimit)); + } + return pointer.get(); +} + +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 || !_loaded) { + _loaded = true; + _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) { + Expects(filter.id() == updated.id()); + + const auto id = filter.id(); + const auto exceptionsChanged = filter.always() != updated.always(); + const auto rulesChanged = exceptionsChanged + || (filter.flags() != updated.flags()) + || (filter.never() != updated.never()); + const auto pinnedChanged = (filter.pinned() != updated.pinned()); + if (!rulesChanged + && !pinnedChanged + && filter.title() == updated.title() + && filter.iconEmoji() == updated.iconEmoji()) { + return false; + } + if (rulesChanged) { + 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(id, filterList); + } else { + history->removeFromChatList(id, filterList); + } + } + }; + const auto feedList = [&](not_null list) { + for (const auto &entry : *list->indexed()) { + if (const auto history = entry->history()) { + feedHistory(history); + } + } + }; + feedList(_owner->chatsList()); + if (const auto folder = _owner->folderLoaded(Data::Folder::kId)) { + feedList(folder->chatsList()); + } + if (exceptionsChanged && !updated.always().empty()) { + _exceptionsToLoad.push_back(id); + Ui::PostponeCall(&_owner->session(), [=] { + _owner->session().api().requestMoreDialogsIfNeeded(); + }); + } + } + if (pinnedChanged) { + const auto filterList = _owner->chatsFilters().chatsList(id); + filterList->pinned()->applyList(updated.pinned()); + } + 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 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->iconEmoji(), + i->flags(), + std::move(always), + std::move(pinned), + i->never(), + (id == cDefaultFilterId()))); + 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)) { + return true; + } + } + return false; +} + +const std::vector &ChatFilters::list() const { + return _list; +} + +rpl::producer<> ChatFilters::changed() const { + return _listChanged.events(); +} + +bool ChatFilters::loadNextExceptions(bool chatsListLoaded) { + if (_exceptionsLoadRequestId) { + return true; + } else if (!chatsListLoaded + && (_owner->chatsList()->fullSize().current() + < kLoadExceptionsAfter)) { + return false; + } + auto inputs = QVector(); + const auto collectExceptions = [&](FilterId id) { + auto result = QVector(); + const auto i = ranges::find(_list, id, &ChatFilter::id); + if (i != end(_list)) { + result.reserve(i->always().size()); + for (const auto history : i->always()) { + if (!history->folderKnown()) { + inputs.push_back( + MTP_inputDialogPeer(history->peer->input)); + } + } + } + return result; + }; + while (!_exceptionsToLoad.empty()) { + const auto id = _exceptionsToLoad.front(); + const auto exceptions = collectExceptions(id); + if (inputs.size() + exceptions.size() > kLoadExceptionsPerRequest) { + Assert(!inputs.isEmpty()); + break; + } + _exceptionsToLoad.pop_front(); + inputs.append(exceptions); + } + if (inputs.isEmpty()) { + return false; + } + const auto api = &_owner->session().api(); + _exceptionsLoadRequestId = api->request(MTPmessages_GetPeerDialogs( + MTP_vector(inputs) + )).done([=](const MTPmessages_PeerDialogs &result) { + _exceptionsLoadRequestId = 0; + _owner->session().data().histories().applyPeerDialogs(result); + _owner->session().api().requestMoreDialogsIfNeeded(); + }).fail([=](const RPCError &error) { + _exceptionsLoadRequestId = 0; + _owner->session().api().requestMoreDialogsIfNeeded(); + }).send(); + return true; +} + +void ChatFilters::refreshHistory(not_null history) { + _refreshHistoryRequests.fire_copy(history); +} + +auto ChatFilters::refreshHistoryRequests() const +-> rpl::producer> { + return _refreshHistoryRequests.events(); +} + +void ChatFilters::requestSuggested() { + if (_suggestedRequestId) { + return; + } + if (_suggestedLastReceived > 0 + && crl::now() - _suggestedLastReceived < kRefreshSuggestedTimeout) { + return; + } + const auto api = &_owner->session().api(); + _suggestedRequestId = api->request(MTPmessages_GetSuggestedDialogFilters( + )).done([=](const MTPVector &data) { + _suggestedRequestId = 0; + _suggestedLastReceived = crl::now(); + + _suggested = ranges::view::all( + data.v + ) | ranges::view::transform([&](const MTPDialogFilterSuggested &f) { + return f.match([&](const MTPDdialogFilterSuggested &data) { + return SuggestedFilter{ + Data::ChatFilter::FromTL(data.vfilter(), _owner), + qs(data.vdescription()) + }; + }); + }) | ranges::to_vector; + + _suggestedUpdated.fire({}); + }).fail([=](const RPCError &error) { + _suggestedRequestId = 0; + _suggestedLastReceived = crl::now() + kRefreshSuggestedTimeout / 2; + + _suggestedUpdated.fire({}); + }).send(); +} + +bool ChatFilters::suggestedLoaded() const { + return (_suggestedLastReceived > 0); +} + +const std::vector &ChatFilters::suggestedFilters() const { + return _suggested; +} + +rpl::producer<> ChatFilters::suggestedUpdated() const { + return _suggestedUpdated.events(); +} + +} // 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..fa591c16e --- /dev/null +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -0,0 +1,160 @@ +/* +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 Dialogs { +class MainList; +class Key; +} // namespace Dialogs + +namespace Data { + +class Session; + +class ChatFilter final { +public: + enum class Flag : uchar { + Contacts = 0x01, + NonContacts = 0x02, + Groups = 0x04, + Channels = 0x08, + Bots = 0x10, + NoMuted = 0x20, + NoRead = 0x40, + NoArchived = 0x80, + }; + friend constexpr inline bool is_flag_type(Flag) { return true; }; + using Flags = base::flags; + + static constexpr int kPinnedLimit = 100; + + ChatFilter() = default; + ChatFilter(FilterId id); + ChatFilter( + FilterId id, + const QString &title, + const QString &iconEmoji, + Flags flags, + base::flat_set> always, + std::vector> pinned, + base::flat_set> never, + bool isDefault = false); + + [[nodiscard]] static ChatFilter FromTL( + const MTPDialogFilter &data, + not_null owner); + [[nodiscard]] MTPDialogFilter tl(FilterId replaceId = 0) const; + + [[nodiscard]] FilterId id() const; + [[nodiscard]] QString title() const; + [[nodiscard]] bool isDefault() const; + [[nodiscard]] QString iconEmoji() 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; + +private: + FilterId _id = 0; + QString _title; + QString _iconEmoji; + base::flat_set> _always; + std::vector> _pinned; + base::flat_set> _never; + Flags _flags; + bool _isDefault = false; + +}; + +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()); +} + +inline bool operator!=(const ChatFilter &a, const ChatFilter &b) { + return !(a == b); +} + +struct SuggestedFilter { + ChatFilter filter; + QString description; +}; + +class ChatFilters final { +public: + explicit ChatFilters(not_null owner); + ~ChatFilters(); + + 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; + + bool loadNextExceptions(bool chatsListLoaded); + + void refreshHistory(not_null history); + [[nodiscard]] auto refreshHistoryRequests() const + -> rpl::producer>; + + [[nodiscard]] not_null chatsList(FilterId filterId); + + const ChatFilter &applyUpdatedPinned( + FilterId id, + const std::vector &dialogs); + void saveOrder( + const std::vector &order, + mtpRequestId after = 0); + + [[nodiscard]] bool archiveNeeded() const; + + void requestSuggested(); + [[nodiscard]] bool suggestedLoaded() const; + [[nodiscard]] auto suggestedFilters() const + -> const std::vector &; + [[nodiscard]] rpl::producer<> suggestedUpdated() const; + +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; + + std::vector _list; + base::flat_map> _chatsLists; + rpl::event_stream<> _listChanged; + rpl::event_stream> _refreshHistoryRequests; + mtpRequestId _loadRequestId = 0; + mtpRequestId _saveOrderRequestId = 0; + mtpRequestId _saveOrderAfterId = 0; + bool _loaded = false; + + mtpRequestId _suggestedRequestId = 0; + std::vector _suggested; + rpl::event_stream<> _suggestedUpdated; + crl::time _suggestedLastReceived = 0; + + std::deque _exceptionsToLoad; + mtpRequestId _exceptionsLoadRequestId = 0; + +}; + +} // namespace Data 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/data/data_folder.cpp b/Telegram/SourceFiles/data/data_folder.cpp index 99175a4c2..a999fb9cf 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(); @@ -67,11 +67,38 @@ Folder::Folder(not_null owner, FolderId id) for (const auto history : _lastHistories) { if (history->peer == update.peer) { ++_chatListViewVersion; - updateChatListEntry(); + updateChatListEntryPostponed(); return; } } }, _lifetime); + + _chatsList.setAllAreMuted(true); + + _chatsList.unreadStateChanges( + ) | rpl::filter([=] { + return inChatList(); + }) | rpl::start_with_next([=](const Dialogs::UnreadState &old) { + ++_chatListViewVersion; + notifyUnreadStateChange(old); + updateChatListEntryPostponed(); + }, _lifetime); + + _chatsList.fullSize().changes( + ) | rpl::start_with_next([=] { + updateChatListEntryPostponed(); + }, _lifetime); +} + +void Folder::updateChatListEntryPostponed() { + if (_updateChatListEntryPostponed) { + return; + } + _updateChatListEntryPostponed = true; + Ui::PostponeCall(this, [=] { + updateChatListEntry(); + _updateChatListEntryPostponed = false; + }); } FolderId Folder::id() const { @@ -108,7 +135,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 { @@ -313,29 +340,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; } @@ -355,7 +359,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( @@ -371,87 +375,12 @@ 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) { LOG(("API Error: Nested folders detected.")); } - 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(); - } + owner().setChatPinned(this, FilterId(), data.is_pinned()); } // #feed @@ -463,10 +392,6 @@ void Folder::unreadEntryChanged( // return _unreadPosition.changes(); //} -bool Folder::toImportant() const { - return false; -} - int Folder::fixedOnTopIndex() const { return kArchiveFixOnTopIndex; } @@ -483,6 +408,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 26a9b8d58..eece5182f 100644 --- a/Telegram/SourceFiles/data/data_folder.h +++ b/Telegram/SourceFiles/data/data_folder.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_entry.h" #include "dialogs/dialogs_main_list.h" #include "data/data_messages.h" +#include "base/weak_ptr.h" class ChannelData; @@ -24,7 +25,7 @@ class Folder; //MessagePosition FeedPositionFromMTP(const MTPFeedPosition &position); // #feed -class Folder final : public Dialogs::Entry { +class Folder final : public Dialogs::Entry, public base::has_weak_ptr { public: static constexpr auto kId = 1; @@ -45,20 +46,9 @@ 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; - bool toImportant() const override; bool shouldBeInChatList() const override; int chatListUnreadCount() const override; bool chatListUnreadMark() const override; @@ -86,11 +76,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; @@ -100,7 +85,7 @@ private: void computeChatListMessage(); void reorderLastHistories(); - void finalizeCloudUnread(); + void updateChatListEntryPostponed(); void paintUserpic( Painter &p, @@ -117,11 +102,10 @@ private: base::flat_set _nameWords; base::flat_set _nameFirstLetters; - Dialogs::UnreadState _cloudUnread; - int _cloudChatsListSize = 0; std::vector> _lastHistories; HistoryItem *_chatListMessage = nullptr; uint32 _chatListViewVersion = 0; + bool _updateChatListEntryPostponed = false; //rpl::variable _unreadPosition; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/data/data_histories.h b/Telegram/SourceFiles/data/data_histories.h index 61079634d..ba9f6459d 100644 --- a/Telegram/SourceFiles/data/data_histories.h +++ b/Telegram/SourceFiles/data/data_histories.h @@ -39,6 +39,8 @@ public: [[nodiscard]] History *find(PeerId peerId); [[nodiscard]] not_null findOrCreate(PeerId peerId); + void applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs); + void unloadAll(); void clearAll(); @@ -108,7 +110,6 @@ private: void postponeRequestDialogEntries(); void sendDialogRequests(); - void applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs); const not_null _owner; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index e0f6bd6f7..9fd380bbe 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,50 @@ 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; + parent()->history()->owner().requestItemRepaint(parent()); + return true; +} + +std::unique_ptr MediaDice::createView( + not_null message, + not_null realParent) { + return std::make_unique( + message, + std::make_unique(message, this)); +} + } // 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/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 7fa9f0a06..b3cf55211 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" @@ -183,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(); }) @@ -192,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)) @@ -212,6 +214,11 @@ Session::Session(not_null session) setupChannelLeavingViewer(); setupPeerNameViewer(); setupUserIsContactViewer(); + + _chatsList.unreadStateChanges( + ) | rpl::start_with_next([] { + Notify::unreadCounterUpdated(); + }, _lifetime); } void Session::clear() { @@ -228,6 +235,12 @@ void Session::clear() { cSetRecentStickers(RecentStickerPack()); App::clearMousedItems(); _histories->clearAll(); + _webpages.clear(); + _locations.clear(); + _polls.clear(); + _games.clear(); + _documents.clear(); + _photos.clear(); } not_null Session::peer(PeerId id) { @@ -519,11 +532,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 &) { @@ -566,10 +575,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(); @@ -605,11 +610,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); @@ -644,15 +646,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); @@ -665,7 +664,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); @@ -787,7 +786,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() @@ -845,7 +844,7 @@ void Session::chatsListChanged(Data::Folder *folder) { void Session::chatsListDone(Data::Folder *folder) { if (folder) { - folder->setChatsListLoaded(); + folder->chatsList()->setLoaded(); } else { _chatsList.setLoaded(); } @@ -1466,11 +1465,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(); } @@ -1518,7 +1522,7 @@ void Session::applyDialogs( }); } if (requestFolder && count) { - requestFolder->setCloudChatsListSize(*count); + requestFolder->chatsList()->setCloudListSize(*count); } } @@ -1556,19 +1560,39 @@ 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 { + 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 - ? Global::PinnedDialogsInFolderMax() - : Global::PinnedDialogsCountMax(); +int Session::pinnedChatsLimit( + Data::Folder *folder, + FilterId filterId) const { + 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( - 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) { @@ -1576,12 +1600,16 @@ void Session::clearPinnedChats(Data::Folder *folder) { } 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(); } @@ -2028,35 +2056,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() @@ -3355,31 +3354,50 @@ not_null Session::contactsNoChatsList() { return &_contactsNoChatsList; } -auto Session::refreshChatListEntry(Dialogs::Key key) +auto Session::refreshChatListEntry( + Dialogs::Key key, + FilterId filterIdForResult) -> RefreshChatListEntryResult { + Expects(key.entry()->folderKnown()); + using namespace Dialogs; const auto entry = key.entry(); - auto result = RefreshChatListEntryResult(); - result.changed = !entry->inChatList(); - if (result.changed) { - const auto mainRow = entry->addToChatList(Mode::All); + 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, mainList); _contactsNoChatsList.del(key, mainRow); } else { - result.moved = entry->adjustByPosInChatList(Mode::All); + mainListResult.moved = entry->adjustByPosInChatList(0, mainList); } - if (Global::DialogsModeEnabled()) { - if (entry->toImportant()) { - result.importantChanged = !entry->inChatList(Mode::Important); - if (result.importantChanged) { - entry->addToChatList(Mode::Important); + 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 (filter.contains(history)) { + filterResult.changed = !entry->inChatList(id); + if (filterResult.changed) { + entry->addToChatList(id, filterList); } else { - result.importantMoved = entry->adjustByPosInChatList( - Mode::Important); + filterResult.moved = entry->adjustByPosInChatList( + id, + filterList); } - } else if (entry->inChatList(Mode::Important)) { - entry->removeFromChatList(Mode::Important); - result.importantChanged = true; + } else if (entry->inChatList(id)) { + entry->removeFromChatList(id, filterList); + filterResult.changed = true; + } + if (id == filterIdForResult) { + result = filterResult; } } return result; @@ -3389,9 +3407,17 @@ 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); + if (!entry->inChatList()) { + return; + } + Assert(entry->folderKnown()); + const auto mainList = chatsList(entry->folder()); + entry->removeFromChatList(0, mainList); + for (const auto &filter : _chatsFilters->list()) { + 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 f7d7a95f7..4b1eb9025 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -58,6 +58,7 @@ class Folder; class LocationPoint; class WallPaper; class ScheduledMessages; +class ChatFilters; class CloudThemes; class Streaming; class MediaRotation; @@ -85,6 +86,9 @@ public: [[nodiscard]] const Groups &groups() const { return _groups; } + [[nodiscard]] ChatFilters &chatsFilters() const { + return *_chatsFilters; + } [[nodiscard]] ScheduledMessages &scheduledMessages() const { return *_scheduledMessages; } @@ -358,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); + Data::Folder *folder, + FilterId filterId) const; + void setChatPinned( + const Dialogs::Key &key, + FilterId filterId, + bool pinned); void clearPinnedChats(Data::Folder *folder); void applyPinnedChats( Data::Folder *folder, const QVector &list); void reorderTwoPinnedChats( + FilterId filterId, const Dialogs::Key &key1, const Dialogs::Key &key2); @@ -445,11 +454,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); @@ -621,19 +625,20 @@ 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 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 { @@ -975,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/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 2b452b3e4..dd523426d 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -128,16 +128,18 @@ using UserId = int32; using ChatId = int32; using ChannelId = int32; using FolderId = int32; +using FilterId = int32; 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/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 9b67c3ee5..96c759506 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_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp index d56ae68d9..fa0919e67 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" @@ -54,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); } } @@ -92,25 +108,49 @@ void Entry::updateChatListSortPosition() { updateChatListEntry(); return; } + _sortKeyByDate = DialogPosFromDate(adjustedChatListTimeId()); const auto fixedIndex = fixedOnTopIndex(); _sortKeyInChatList = fixedIndex ? FixedOnTopDialogPos(fixedIndex) - : isPinnedDialog() - ? PinnedDialogPos(_pinnedIndex) - : DialogPosFromDate(adjustedChatListTimeId()); + : computeSortPosition(0); if (needUpdateInChatList()) { setChatListExistence(true); } else { - _sortKeyInChatList = 0; + _sortKeyInChatList = _sortKeyByDate = 0; } } +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()); } 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) { @@ -131,25 +171,35 @@ 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, + not_null list) { + const auto links = chatListLinks(filterId); + Assert(links != nullptr); + const auto from = links->main->pos(); + list->indexed()->adjustByDate(*links); + const auto to = links->main->pos(); return { from, to }; } @@ -161,60 +211,54 @@ 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, + not_null list) { + if (const auto main = maybeMainChatListLink(filterId)) { + return main; } - return mainChatListLink(list); + return _chatListLinks.emplace( + filterId, + list->addEntry(_key) + ).first->second.main; } -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, + not_null list) { + const auto i = _chatListLinks.find(filterId); + if (i == end(_chatListLinks)) { + return; } + _chatListLinks.erase(i); + list->removeEntry(_key); } -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 +267,4 @@ void Entry::updateChatListEntry() const { } } -not_null Entry::myChatsList(Mode list) const { - return owner().chatsList(folder())->indexed(list); -} - } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h index ed0ec0191..b9e21a369 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.h +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h @@ -24,17 +24,17 @@ namespace Dialogs { class Row; class IndexedList; -using RowsByLetter = base::flat_map>; +class MainList; -enum class SortMode { - Date = 0x00, - Name = 0x01, - Add = 0x02, +struct RowsByLetter { + not_null main; + base::flat_map> letters; }; -enum class Mode { - All = 0x00, - Important = 0x01, +enum class SortMode { + Date = 0x00, + Name = 0x01, + Add = 0x02, }; struct PositionChange { @@ -94,32 +94,40 @@ 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, + not_null list); + [[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, + not_null list); + void removeFromChatList( + FilterId filterId, + not_null list); + void removeChatListEntryByLetter(FilterId filterId, QChar letter); void addChatListEntryByLetter( - Mode list, + FilterId filterId, 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; + [[nodiscard]] uint64 sortKeyInChatList(FilterId filterId) const { + return filterId + ? computeSortPosition(filterId) + : _sortKeyInChatList; } void updateChatListSortPosition(); void setChatListTimeId(TimeId date); @@ -131,7 +139,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; @@ -174,6 +181,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(); @@ -184,23 +192,25 @@ protected: }); } + [[nodiscard]] int lookupPinnedIndex(FilterId filterId) const; + private: virtual void changedChatListPinHook(); - - void notifyUnreadStateChange(const UnreadState &wasState); + void pinnedIndexChanged(int was, int now); + [[nodiscard]] uint64 computeSortPosition(FilterId filterId) const; void setChatListExistence(bool exists); - RowsByLetter &chatListLinks(Mode list); - const RowsByLetter &chatListLinks(Mode list) const; - Row *mainChatListLink(Mode list) const; - - not_null myChatsList(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 _owner; Dialogs::Key _key; - RowsByLetter _chatListLinks[2]; + base::flat_map _chatListLinks; uint64 _sortKeyInChatList = 0; - int _pinnedIndex = 0; + uint64 _sortKeyByDate = 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 b5bb69044..19a4957d1 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp @@ -13,23 +13,25 @@ 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) { - 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, _filterId).first; } + result.letters.emplace(ch, j->second.addToEnd(key)); } return result; } @@ -43,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); } @@ -51,13 +53,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); } } } @@ -81,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()); } @@ -95,19 +95,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); } } @@ -141,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); } @@ -149,7 +149,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 +168,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); @@ -177,11 +177,11 @@ 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) { - history->addChatListEntryByLetter(list, ch, row); + history->addChatListEntryByLetter(filterId, ch, row); } } } @@ -250,8 +250,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..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); @@ -26,14 +26,14 @@ 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( - 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,11 +76,12 @@ private: Key key, const base::flat_set &oldChars); void adjustNames( - Mode list, + FilterId filterId, not_null history, 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 bdafcd740..15ac44be0 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" @@ -43,6 +44,8 @@ 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 "boxes/filters/edit_filter_box.h" +#include "api/api_chat_filters.h" #include "facades.h" #include "styles/style_dialogs.h" #include "styles/style_chat_helpers.h" @@ -73,12 +76,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; @@ -89,7 +94,7 @@ int PinnedDialogsCount(not_null list) { } // namespace struct InnerWidget::CollapsedRow { - explicit CollapsedRow(Data::Folder *folder = nullptr) : folder(folder) { + CollapsedRow(Data::Folder *folder) : folder(folder) { } Data::Folder *folder = nullptr; @@ -119,6 +124,7 @@ InnerWidget::InnerWidget( return pinnedShiftAnimationCallback(now); }) , _addContactLnk(this, tr::lng_add_contact_button(tr::now)) +, _editFilterLnk(this, tr::lng_filters_context_edit(tr::now)) , _cancelSearchInChat(this, st::dialogsCancelSearchInPeer) , _cancelSearchFromUser(this, st::dialogsCancelSearchInPeer) { @@ -126,11 +132,8 @@ InnerWidget::InnerWidget( setAttribute(Qt::WA_OpaquePaintEvent, true); #endif // OS_MAC_OLD - _mode = Global::DialogsModeEnabled() - ? Global::DialogsMode() - : Dialogs::Mode::All; - _addContactLnk->addClickHandler([] { App::wnd()->onShowAddContact(); }); + _editFilterLnk->addClickHandler([=] { editOpenedFilter(); }); _cancelSearchInChat->setClickedCallback([=] { cancelSearchInChat(); }); _cancelSearchInChat->hide(); _cancelSearchFromUser->setClickedCallback([=] { @@ -200,7 +203,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()); @@ -210,6 +216,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()); + DialogListLinesChanges( ) | rpl::start_with_next([=] { refresh(); @@ -261,6 +275,11 @@ InnerWidget::InnerWidget( updateDialogRow(next); }, lifetime()); + _controller->activeChatsFilter( + ) | rpl::start_with_next([=](FilterId filterId) { + switchToFilter(filterId); + }, lifetime()); + refreshWithCollapsedRows(true); setupShortcuts(); @@ -293,9 +312,6 @@ void InnerWidget::refreshWithCollapsedRows(bool toTop) { _collapsedSelected = -1; _collapsedRows.clear(); - if (!_openedFolder && Global::DialogsModeEnabled()) { - _collapsedRows.push_back(std::make_unique()); - } const auto list = shownDialogs(); const auto archive = !list->empty() ? (*list->begin())->folder() @@ -309,8 +325,9 @@ 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)); } } else { _skipTopDialogs = 0; @@ -381,7 +398,6 @@ void InnerWidget::changeOpenedFolder(Data::Folder *folder) { //const auto lastMousePosition = _lastMousePosition; clearSelection(); _openedFolder = folder; - _mode = _openedFolder ? Mode::All : Global::DialogsMode(); refreshWithCollapsedRows(true); // This doesn't work, because we clear selection in leaveEvent on hide. //if (mouseSelection && lastMousePosition) { @@ -442,6 +458,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { Layout::RowPainter::paint( p, row, + _filterId, fullWidth, isActive, isSelected, @@ -496,11 +513,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) { @@ -567,6 +589,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { Layout::RowPainter::paint( p, _filterResults[from], + _filterId, fullWidth, active, selected, @@ -693,20 +716,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() - : (_mode == Dialogs::Mode::Important) - ? (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) - ? session().data().unreadOnlyMutedBadge() - : 0; + const auto text = row->folder->chatListName(); + const auto unread = row->folder->chatListUnreadCount(); Layout::PaintCollapsedRow( p, row->row, @@ -1080,11 +1097,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 * DialogsRowHeight()), + e->pos() - QPoint(0, filteredOffset() + _filteredPressed * st::dialogsRowHeight), QSize(width(), DialogsRowHeight()), - [=] { repaintDialogRow(list, row); }); + [=] { repaintDialogRow(filterId, row); }); } else if (base::in_range(_peerSearchPressed, 0, _peerSearchResults.size())) { auto &result = _peerSearchResults[_peerSearchPressed]; auto row = &result->row; @@ -1099,7 +1116,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { }); } if (anim::Disabled() - && (!_pressed || !_pressed->entry()->isPinnedDialog())) { + && (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) { mousePressReleased(e->globalPos(), e->button()); } } @@ -1115,7 +1132,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() @@ -1127,14 +1146,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; @@ -1145,7 +1164,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. } @@ -1154,7 +1175,11 @@ void InnerWidget::savePinnedOrder() { return; // Something has changed in the set of pinned chats. } } - session().api().savePinnedOrder(_openedFolder); + if (_filterId) { + Api::SaveNewFilterPinned(&session(), _filterId); + } else { + session().api().savePinnedOrder(_openedFolder); + } } void InnerWidget::finishReorderPinned() { @@ -1186,7 +1211,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(); @@ -1211,6 +1236,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(); @@ -1219,7 +1245,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; @@ -1228,7 +1254,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; @@ -1415,6 +1441,7 @@ void InnerWidget::setSearchedPressed(int pressed) { void InnerWidget::resizeEvent(QResizeEvent *e) { _addContactLnk->move((width() - _addContactLnk->width()) / 2, (st::noContactsHeight + st::noContactsFont->height) / 2); + _editFilterLnk->move((width() - _editFilterLnk->width()) / 2, (st::noContactsHeight + st::noContactsFont->height) / 2); const auto widthForCancelButton = qMax(width(), st::columnMinimalWidthLeft); const auto left = widthForCancelButton - st::dialogsSearchInSkip - _cancelSearchInChat->width(); const auto top = (st::dialogsSearchInHeight - st::dialogsCancelSearchInPeer.height) / 2; @@ -1465,17 +1492,12 @@ void InnerWidget::refreshDialog(Key key) { } } - const auto result = session().data().refreshChatListEntry(key); - const auto changed = (_mode == Mode::Important) - ? result.importantChanged - : result.changed; - const auto moved = (_mode == Mode::Important) - ? result.importantMoved - : result.moved; - + const auto result = session().data().refreshChatListEntry( + key, + _filterId); const auto rowHeight = 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)) { @@ -1483,7 +1505,7 @@ void InnerWidget::refreshDialog(Key key) { emit dialogMoved(from, to); } - if (changed) { + if (result.changed) { refresh(); } else if (_state == WidgetState::Default && from != to) { update( @@ -1542,17 +1564,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(), 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( @@ -1676,10 +1698,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()); @@ -1711,7 +1733,9 @@ void InnerWidget::updateSelectedRow(Key key) { } not_null InnerWidget::shownDialogs() const { - return session().data().chatsList(_openedFolder)->indexed(_mode); + return _filterId + ? session().data().chatsFilters().chatsList(_filterId)->indexed() + : session().data().chatsList(_openedFolder)->indexed(); } void InnerWidget::leaveEventHook(QEvent *e) { @@ -1724,6 +1748,10 @@ void InnerWidget::dragLeft() { clearSelection(); } +FilterId InnerWidget::filterId() const { + return _filterId; +} + void InnerWidget::clearSelection() { _mouseSelection = false; _lastMousePosition = std::nullopt; @@ -1751,7 +1779,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(); @@ -1810,6 +1840,7 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { Window::FillPeerMenu( _controller, history->peer, + _filterId, [&](const QString &text, Fn callback) { return _menu->addAction(text, std::move(callback)); }, @@ -2167,13 +2198,6 @@ void InnerWidget::peerSearchReceived( refresh(); } -void InnerWidget::notify_historyMuteUpdated(History *history) { - if (!Global::DialogsModeEnabled() || !history->inChatList()) { - return; - } - refreshDialog(history); -} - Data::Folder *InnerWidget::shownFolder() const { return _openedFolder; } @@ -2196,25 +2220,33 @@ bool InnerWidget::needCollapsedRowsRefresh() const { : (collapsedHasArchive || _skipTopDialogs != 0); } +void InnerWidget::editOpenedFilter() { + if (_filterId > 0) { + EditExistingFilter(_controller, _filterId); + } +} + 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()); + _editFilterLnk->setVisible((_filterId > 0) + && (_state == WidgetState::Default) + && list->empty() + && session().data().chatsList()->loaded()); 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() * DialogsRowHeight(); - if (!_addContactLnk->isHidden()) _addContactLnk->hide(); + h = dialogsOffset() + list->size() * DialogsRowHeight(); } } else if (_state == WidgetState::Filtered) { - if (!_addContactLnk->isHidden()) _addContactLnk->hide(); if (_waitingForSearch) { h = searchedOffset() + (_searchResults.size() * DialogsRowHeight()) + ((_searchResults.empty() && !_searchInChat) ? -st::searchedBarHeight : 0); } else { @@ -2270,9 +2302,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(); @@ -2456,22 +2486,23 @@ void InnerWidget::scrollToEntry(const RowDescriptor &entry) { void InnerWidget::selectSkipPage(int32 pixels, int32 direction) { clearMouseSelection(); + const auto list = shownDialogs(); int toSkip = pixels / int(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()) { @@ -2494,12 +2525,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() * DialogsRowHeight(); + auto otherStart = list->size() * DialogsRowHeight(); if (yFrom < otherStart) { - for (auto i = shownDialogs()->cfind(yFrom, DialogsRowHeight()), end = shownDialogs()->cend(); i != end; ++i) { + for (auto i = list->cfind(yFrom, DialogsRowHeight()), end = list->cend(); i != end; ++i) { if (((*i)->pos() * DialogsRowHeight()) >= yTo) { break; } @@ -2553,25 +2585,31 @@ bool InnerWidget::chooseCollapsedRow() { return false; } const auto &row = _collapsedRows[_collapsedSelected]; - if (row->folder) { - _controller->openFolder(row->folder); - } else { - switchImportantChats(); - } + Assert(row->folder != nullptr); + _controller->openFolder(row->folder); return true; } -void InnerWidget::switchImportantChats() { - clearSelection(); - if (Global::DialogsMode() == Mode::All) { - Global::SetDialogsMode(Mode::Important); - } else { - Global::SetDialogsMode(Mode::All); +void InnerWidget::switchToFilter(FilterId filterId) { + const auto found = ranges::contains( + session().data().chatsFilters().list(), + filterId, + &Data::ChatFilter::id); + if (!found) { + filterId = 0; + } + if (_filterId == filterId) { + emit mustScrollTo(0, 0); + return; + } + if (_openedFolder) { + _filterId = filterId; + } else { + clearSelection(); + stopReorderPinned(); + _filterId = filterId; + refreshWithCollapsedRows(true); } - _mode = Global::DialogsMode(); - Local::writeUserSettings(); - refreshWithCollapsedRows(true); - _collapsedSelected = 0; } bool InnerWidget::chooseHashtag() { @@ -2664,9 +2702,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)); @@ -2741,9 +2780,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)); @@ -2806,8 +2846,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)); @@ -2831,8 +2872,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)); @@ -3012,11 +3054,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(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; } diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 0015d4501..0669ecee5 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; @@ -81,6 +80,8 @@ public: const QVector &my, const QVector &result); + [[nodiscard]] FilterId filterId() const; + void clearSelection(); void changeOpenedFolder(Data::Folder *folder); @@ -89,7 +90,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(); @@ -127,8 +128,6 @@ public: rpl::producer chosenRow() const; - void notify_historyMuteUpdated(History *history); - ~InnerWidget(); public slots: @@ -173,11 +172,12 @@ private: void dialogRowReplaced(Row *oldRow, Row *newRow); + void editOpenedFilter(); void repaintCollapsedFolderRow(not_null folder); void refreshWithCollapsedRows(bool toTop = false); bool needCollapsedRowsRefresh() const; bool chooseCollapsedRow(); - void switchImportantChats(); + void switchToFilter(FilterId filterId); bool chooseHashtag(); ChosenRow computeChosenRow() const; bool isSearchResultActive( @@ -310,7 +310,7 @@ private: not_null _controller; - Mode _mode = Mode(); + FilterId _filterId = 0; bool _mouseSelection = false; std::optional _lastMousePosition; Qt::MouseButton _pressButton = Qt::LeftButton; @@ -377,6 +377,7 @@ private: WidgetState _state = WidgetState::Default; object_ptr _addContactLnk; + object_ptr _editFilterLnk; object_ptr _cancelSearchInChat; object_ptr _cancelSearchFromUser; diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp index c3137bdff..a7b3678d2 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp @@ -223,6 +223,7 @@ void paintOneLineRow( not_null row, not_null entry, Dialogs::Key chat, + FilterId filterId, PeerData *from, const HiddenSenderInfo *hiddenSenderInfo, HistoryItem *item, @@ -321,7 +322,7 @@ void paintOneLineRow( && !item->isEmpty()) { const auto nameWithoutCounterWidth = paintItemCallback(nameleft, (flags & Flag::SearchResult ? namewidth : rectForName.width())); rectForName.setWidth(nameWithoutCounterWidth - st::dialogsPadding.x()); - } else if (entry->isPinnedDialog() && !entry->fixedOnTopIndex()) { + } else 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(), st::dialogsPadding.y(), fullWidth); rectForName.setWidth(rectForName.width() - icon.width() - st::dialogsUnreadPadding); @@ -391,6 +392,7 @@ void paintRow( not_null row, not_null entry, Dialogs::Key chat, + FilterId filterId, PeerData *from, const HiddenSenderInfo *hiddenSenderInfo, HistoryItem *item, @@ -495,7 +497,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; @@ -522,7 +524,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; @@ -539,7 +541,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); @@ -792,6 +794,7 @@ void paintUnreadCount( void RowPainter::paint( Painter &p, not_null row, + FilterId filterId, int fullWidth, bool active, bool selected, @@ -851,8 +854,8 @@ void RowPainter::paint( const auto displayPinnedIcon = !displayUnreadCounter && !displayMentionBadge && !displayUnreadMark - && entry->isPinnedDialog() - && !entry->fixedOnTopIndex(); + && entry->isPinnedDialog(filterId) + && (filterId || !entry->fixedOnTopIndex()); const auto from = history ? (history->peer->migrateTo() @@ -939,6 +942,7 @@ void RowPainter::paint( row, entry, row->key(), + filterId, from, nullptr, item, @@ -955,6 +959,7 @@ void RowPainter::paint( row, entry, row->key(), + filterId, from, nullptr, item, @@ -1086,6 +1091,7 @@ void RowPainter::paint( row, history, history, + FilterId(), from, hiddenSenderInfo, item, @@ -1102,6 +1108,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 c8b03e792..2ef8f29e9 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, + 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 37302ace8..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 { @@ -84,18 +86,18 @@ void List::adjustByName(not_null row) { void List::adjustByDate(not_null row) { Expects(_sortMode == SortMode::Date); - const auto key = row->sortKey(); + 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() <= 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() >= 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 f0827f9b7..168dcb7e2 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp @@ -12,10 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Dialogs { -MainList::MainList(rpl::producer pinnedLimit) -: _all(SortMode::Date) -, _important(SortMode::Date) -, _pinned(1) { +MainList::MainList(FilterId filterId, rpl::producer pinnedLimit) +: _filterId(filterId) +, _all(SortMode::Date, filterId) +, _pinned(filterId, 1) { _unreadState.known = true; std::move( @@ -29,8 +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(Mode::All, peer, oldLetters); - _important.peerNameChanged(Mode::Important, peer, oldLetters); + _all.peerNameChanged(_filterId, peer, oldLetters); }, _lifetime); } @@ -43,41 +42,162 @@ bool MainList::loaded() const { } void MainList::setLoaded(bool loaded) { + if (_loaded == loaded) { + return; + } + const auto recomputer = gsl::finally([&] { + recomputeFullListSize(); + }); + const auto notifier = unreadStateChangeNotifier(true); _loaded = loaded; } +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 recomputer = gsl::finally([&] { + recomputeFullListSize(); + }); + const auto notifier = unreadStateChangeNotifier(true); _all.clear(); - _important.clear(); _unreadState = UnreadState(); + _cloudUnreadState = UnreadState(); + _unreadState.known = true; + _cloudUnreadState.known = true; + _cloudListSize = 0; +} + +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 useClouded = _cloudUnreadState.known && !loaded(); + const auto updateCloudUnread = _cloudUnreadState.known && wasState.known; + const auto notify = !useClouded || wasState.known; + 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 = !_cloudUnreadState.known || loaded() || state.known; + 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; } -not_null MainList::indexed(Mode list) { - return (list == Mode::All) ? &_all : &_important; +rpl::producer MainList::unreadStateChanges() const { + return _unreadStateChanges.events(); } -not_null MainList::indexed(Mode list) const { - return (list == Mode::All) ? &_all : &_important; +not_null MainList::indexed() { + return &_all; +} + +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..c9ac27480 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.h @@ -14,33 +14,60 @@ namespace Dialogs { class MainList final { public: - explicit MainList(rpl::producer pinnedLimit); + MainList(FilterId filterId, rpl::producer pinnedLimit); 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); - UnreadState unreadState() const; + void updateCloudUnread(const MTPDdialogFolder &data); + [[nodiscard]] bool cloudUnreadKnown() const; + [[nodiscard]] UnreadState unreadState() const; + [[nodiscard]] rpl::producer unreadStateChanges() const; - not_null indexed(Mode list = Mode::All); - not_null indexed(Mode list = Mode::All) 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; - IndexedList _important; 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_pinned_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp index 6b6e1bdd7..969bfa64c 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,15 +57,15 @@ 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); } } } @@ -83,18 +86,30 @@ 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]; + _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 +117,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..cbdb0994b 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); @@ -33,6 +35,7 @@ public: 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 +46,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 2abd24270..687bee7f7 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); @@ -229,8 +229,8 @@ Row::Row(Key key, int pos) : _id(key), _pos(pos) { } } -uint64 Row::sortKey() const { - return _id.entry()->sortKeyInChatList(); +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 e8caf1d63..b43b07af2 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(FilterId filterId) const; void validateListEntryCache() const; const Ui::Text::String &listEntryCache() const { diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 58ec287fa..a5997befe 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,7 +260,18 @@ Widget::Widget( Core::App().lockByPasscode(); _lockUnlock->setIconOverride(nullptr); }); - _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())); @@ -279,7 +291,7 @@ Widget::Widget( onSearchMore(); } else { const auto folder = _inner->shownFolder(); - if (!folder || !folder->chatsListLoaded()) { + if (!folder || !folder->chatsList()->loaded()) { session().api().requestDialogs(folder); } } @@ -528,9 +540,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) { @@ -672,16 +684,19 @@ void Widget::animationCallback() { void Widget::escape() { if (controller()->openedFolder().current()) { controller()->closeFolder(); - } else if (!onCancelSearch() - || (!_searchInChat && !App::main()->selectingPeer())) { - emit cancelled(); + } else if (!onCancelSearch()) { + if (controller()->activeChatEntryCurrent().key) { + emit cancelled(); + } else if (controller()->activeChatsFilterCurrent() != cDefaultFilterId()) { + controller()->setActiveChatsFilter(cDefaultFilterId()); + } + } else if (!_searchInChat && !App::main()->selectingPeer()) { + if (controller()->activeChatEntryCurrent().key) { + emit cancelled(); + } } } -void Widget::notify_historyMuteUpdated(History *history) { - _inner->notify_historyMuteUpdated(history); -} - void Widget::refreshLoadMoreButton(bool mayBlock, bool isBlocked) { if (!mayBlock) { _loadMoreChats.destroy(); @@ -1504,7 +1519,7 @@ void Widget::updateControlsGeometry() { } auto smallLayoutWidth = (st::dialogsPadding.x() + (DialogListLines() == 1 ? st::dialogsUnreadHeight : 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() ? 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; @@ -1518,6 +1533,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 710f32c66..fc81317e8 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(); @@ -86,8 +85,6 @@ public: bool wheelEventFromFloatPlayer(QEvent *e) override; QRect rectForFloatPlayer() const override; - void notify_historyMuteUpdated(History *history); - ~Widget(); signals: @@ -181,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/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 b5a6ee341..aa63def02 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/facades.cpp b/Telegram/SourceFiles/facades.cpp index d7fc90e74..4267df373 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -281,10 +281,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(); } @@ -318,8 +314,7 @@ struct Data { bool AdaptiveForWide = true; base::Observable AdaptiveChanged; - bool DialogsModeEnabled = false; - Dialogs::Mode DialogsMode = Dialogs::Mode::All; + bool DialogsFiltersEnabled = false; bool ModerateModeEnabled = false; bool ScreenIsLocked = false; @@ -448,8 +443,7 @@ 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, bool, ModerateModeEnabled); DefineVar(Global, bool, ScreenIsLocked); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 5514a7e1f..b45e225a8 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; @@ -94,7 +90,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 { @@ -155,8 +150,7 @@ DeclareVar(Adaptive::ChatLayout, AdaptiveChatLayout); DeclareVar(bool, AdaptiveForWide); DeclareRefVar(base::Observable, AdaptiveChanged); -DeclareVar(bool, DialogsModeEnabled); -DeclareVar(Dialogs::Mode, DialogsMode); +DeclareVar(bool, DialogsFiltersEnabled); DeclareVar(bool, ModerateModeEnabled); DeclareVar(bool, ScreenIsLocked); diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp index 809131b3d..8c12454ff 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp @@ -291,7 +291,11 @@ Widget::Widget( connect(_scroll, &Ui::ScrollArea::scrolled, this, [this] { onScroll(); }); - _whatIsThis->setClickedCallback([=] { Ui::show(Box(tr::lng_admin_log_about_text(tr::now))); }); + _whatIsThis->setClickedCallback([=] { + Ui::show(Box(channel->isMegagroup() + ? tr::lng_admin_log_about_text(tr::now) + : tr::lng_admin_log_about_text_channel(tr::now))); + }); setupShortcuts(); } diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 7b0767027..d1d0bcb43 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" @@ -838,8 +839,8 @@ void History::setUnreadMentionsCount(int count) { } _unreadMentionsCount = count; const auto has = (count > 0); - if (has != had && Global::DialogsModeEnabled()) { - Notify::historyMuteUpdated(this); + if (has != had) { + owner().chatsFilters().refreshHistory(this); updateChatListEntry(); } } @@ -1647,7 +1648,8 @@ std::optional History::countStillUnreadLocal(MsgId readTillId) const { for (const auto &block : blocks) { for (const auto &message : block->messages) { const auto item = message->data(); - if (item->out() || !IsServerMsgId(item->id)) { + if (!IsServerMsgId(item->id) + || (item->out() && !item->isFromScheduled())) { continue; } else if (item->id > readTillId) { break; @@ -1780,8 +1782,16 @@ void History::setUnreadCount(int newUnreadCount) { if (_unreadCount == newUnreadCount) { return; } + 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 (newUnreadCount == 1) { @@ -1801,9 +1811,6 @@ void History::setUnreadCount(int newUnreadCount) { } else if (!_firstUnreadView && !_unreadBarView && loadedAtBottom()) { calculateFirstUnreadMessage(); } - Notify::peerUpdatedDelayed( - peer, - Notify::PeerUpdate::Flag::UnreadViewChanged); } void History::setUnreadMark(bool unread) { @@ -1814,22 +1821,40 @@ 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) { - updateChatListEntry(); - } - Notify::peerUpdatedDelayed( - peer, - Notify::PeerUpdate::Flag::UnreadViewChanged); } 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; } @@ -1838,18 +1863,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()) { - Notify::historyMuteUpdated(this); - updateChatListEntry(); - } - Notify::peerUpdatedDelayed( - peer, - Notify::PeerUpdate::Flag::NotificationsEnabled); return true; } @@ -1912,21 +1937,22 @@ void History::clearFolder() { } void History::setFolderPointer(Data::Folder *folder) { - using Mode = Dialogs::Mode; - if (_folder == folder) { return; } - if (isPinnedDialog()) { - owner().setChatPinned(this, false); + if (isPinnedDialog(FilterId())) { + owner().setChatPinned(this, FilterId(), false); } + auto &filters = owner().chatsFilters(); 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); + 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); @@ -1935,9 +1961,12 @@ void History::setFolderPointer(Data::Folder *folder) { was->unregisterOne(this); } if (wasInList) { - addToChatList(Mode::All); - if (wasInImportant) { - addToChatList(Mode::Important); + addToChatList(0, owner().chatsList(folder)); + for (const auto &filter : filters.list()) { + if (filter.contains(this)) { + const auto id = filter.id(); + addToChatList(id, filters.chatsList(id)); + } } owner().chatsListChanged(was); owner().chatsListChanged(folder); @@ -1958,7 +1987,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 { @@ -2566,7 +2595,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; } @@ -2585,7 +2614,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()) { @@ -2602,10 +2631,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..c41087a92 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 { @@ -201,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); @@ -331,7 +334,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; @@ -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_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index e4f8ec9f6..3bdad6c22 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/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 7a8e95457..f8dc4428a 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::Good; }, [](const MTPDmessageMediaUnsupported &) { return Result::Unsupported; }); @@ -768,7 +770,9 @@ bool HistoryItem::showNotification() const { if (channel && !channel->amIn()) { return false; } - return (out() || _history->peer->isSelf()) ? isFromScheduled() : unread(); + return (out() || _history->peer->isSelf()) + ? isFromScheduled() + : unread(); } void HistoryItem::markClientSideAsRead() { diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 7e2974d66..c461982b4 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 std::make_unique(item, media.vvalue().v); }, [](const MTPDmessageMediaEmpty &) -> Result { return nullptr; }, [](const MTPDmessageMediaUnsupported &) -> Result { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 6396fd98b..939a5a75e 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" @@ -1500,6 +1501,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(); @@ -1869,6 +1873,7 @@ void HistoryWidget::showHistory( && (!_history->loadedAtTop() || !_migrated->loadedAtBottom())) { _migrated->clear(History::ClearType::Unload); } + _history->setFakeUnreadWhileOpened(true); _topBar->setActiveChat( _history, 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 f0741d4a0..e2e1ce4df 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -234,6 +234,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/history/view/media/history_view_dice.cpp b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp new file mode 100644 index 000000000..f753d434a --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp @@ -0,0 +1,67 @@ +/* +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/history_item_components.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, not_null dice) +: _parent(parent) +, _dice(dice) +, _start(parent, Lookup(parent, 0)) { + _showLastFrame = _parent->data()->Has(); + if (_showLastFrame) { + _drawingEnd = true; + } else { + _start.setDiceIndex(0); + } +} + +Dice::~Dice() = default; + +QSize Dice::size() { + return _start.size(); +} + +void Dice::draw(Painter &p, const QRect &r, bool selected) { + if (const auto value = _end ? 0 : _dice->diceValue()) { + if (const auto document = Lookup(_parent, value)) { + _end.emplace(_parent, document); + _end->setDiceIndex(value); + _end->initSize(); + } + } + if (!_end) { + _drawingEnd = false; + } + if (_drawingEnd) { + _end->draw(p, r, selected); + } else { + _start.draw(p, r, selected); + 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..d8967412f --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.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 "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, not_null dice); + ~Dice(); + + QSize size() override; + void draw(Painter &p, const QRect &r, bool selected) override; + + void clearStickerLoopPlayed() override { + } + void unloadHeavyPart() override { + _start.unloadHeavyPart(); + if (_end) { + _end->unloadHeavyPart(); + } + } + bool hidesForwardedInfo() override { + return false; + } + +private: + const not_null _parent; + const not_null _dice; + std::optional _end; + Sticker _start; + mutable bool _showLastFrame = false; + mutable bool _drawingEnd = false; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 2159b0700..4e38f4d19 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -41,6 +41,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)); @@ -48,6 +49,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 { @@ -281,7 +287,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 @@ -885,7 +891,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) { @@ -1454,7 +1460,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/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index 5e246181a..306f3281c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -20,9 +20,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_history.h" namespace HistoryView { - namespace { - constexpr auto kMaxUnwrappedForwardedBarLines = 4; + +constexpr auto kMaxForwardedBarLines = 4; + } // namespace UnwrappedMedia::Content::~Content() = default; @@ -50,15 +51,16 @@ QSize UnwrappedMedia::countOptimalSize() { const auto item = _parent->data(); const auto via = item->Get(); const auto reply = item->Get(); - auto forwarded = item->Get(); + const auto forwarded = getDisplayedForwardedInfo(); if (forwarded) { - forwarded->create(); + forwarded->create(via); } - maxWidth += additionalWidth(via, reply, forwarded); - if (const auto surrounding = surroundingHeight(via, reply, forwarded)) { + 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); @@ -74,7 +76,7 @@ QSize UnwrappedMedia::countCurrentSize(int newWidth) { const auto infoWidth = _parent->infoWidth() + 2 * st::msgDateImgPadding.x(); const auto via = item->Get(); const auto reply = item->Get(); - const auto forwarded = item->Get(); + 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(); @@ -114,7 +116,7 @@ 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 = item->Get(); + const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo(); auto usex = 0; auto usew = maxWidth(); if (!inWebPage) { @@ -141,33 +143,33 @@ void UnwrappedMedia::draw( } } -int UnwrappedMedia::surroundingHeight( +UnwrappedMedia::SurroundingInfo UnwrappedMedia::surroundingInfo( const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded, - const int innerw) const { + int outerw) const { if (!via && !reply && !forwarded) { - return 0; + return {}; } - auto result = st::msgReplyPadding.top() + st::msgReplyPadding.bottom(); + 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) { - if (innerw) { - auto forwardedHeightReal = forwarded->text.countHeight(innerw); - auto forwardedHeight = qMin(forwardedHeightReal, kMaxUnwrappedForwardedBarLines * st::msgServiceNameFont->height); - result += forwardedHeight; - } else { - result += st::msgServiceNameFont->height; - } - result += (!via && reply ? st::msgReplyPadding.top() : 0); - } - if (via) { - result += st::msgServiceNameFont->height + 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( @@ -191,9 +193,9 @@ void UnwrappedMedia::drawSurrounding( InfoDisplayType::Background); } auto replyRight = 0; - int rectw = width() - inner.width() - st::msgReplyPadding.left(); - auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right()); - if (const auto recth = surroundingHeight(via, reply, forwarded, innerw)) { + 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; @@ -202,17 +204,11 @@ void UnwrappedMedia::drawSurrounding( p.setPen(st::msgServiceFg); rectx += st::msgReplyPadding.left(); rectw -= st::msgReplyPadding.left() + st::msgReplyPadding.right(); - auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0; - auto forwardedHeight = qMin(forwardedHeightReal, kMaxUnwrappedForwardedBarLines * st::msgServiceNameFont->height); if (forwarded) { p.setTextPalette(st::serviceTextPalette); - auto breakEverywhere = (forwardedHeightReal > forwardedHeight); - forwarded->text.drawElided(p, rectx, recty + st::msgReplyPadding.top(), rectw, kMaxUnwrappedForwardedBarLines, style::al_left, 0, -1, 0, breakEverywhere); + forwarded->text.drawElided(p, rectx, recty + st::msgReplyPadding.top(), rectw, kMaxForwardedBarLines, style::al_left, 0, -1, 0, surrounding.forwardedBreakEverywhere); p.restoreTextPalette(); - int skip = forwardedHeight + (!via && reply ? st::msgReplyPadding.top() : 0); - recty += skip; - } - if (via) { + } 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); @@ -247,7 +243,7 @@ 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 : item->Get(); + const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo(); auto usex = 0; auto usew = maxWidth(); if (!inWebPage) { @@ -287,7 +283,7 @@ 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 : item->Get(); + const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo(); auto usex = 0; auto usew = maxWidth(); if (!inWebPage) { @@ -310,39 +306,36 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { if (_parent->media() == this) { auto replyRight = 0; - int rectw = width() - inner.width() - st::msgReplyPadding.left(); - auto innerw = rectw - (st::msgReplyPadding.left() + st::msgReplyPadding.right()); - if (auto recth = surroundingHeight(via, reply, forwarded, innerw)) { + 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 (forwarded) { - auto forwardedHeightReal = forwarded ? forwarded->text.countHeight(innerw) : 0; - auto forwardedHeight = qMin(forwardedHeightReal, kMaxUnwrappedForwardedBarLines * st::msgServiceNameFont->height); - if (QRect(rectx, recty, rectw, st::msgReplyPadding.top() + forwardedHeight).contains(point)) { - auto breakEverywhere = (forwardedHeightReal > forwardedHeight); + if (QRect(rectx, recty, rectw, st::msgReplyPadding.top() + surrounding.forwardedHeight).contains(point)) { auto textRequest = request.forText(); - if (breakEverywhere) { + 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 (breakEverywhere) { + if (surrounding.forwardedBreakEverywhere) { result.cursor = CursorState::Forwarded; } else { result.cursor = CursorState::None; } return result; } - recty += forwardedHeight; - recth -= forwardedHeight; - } - if (via) { + 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; @@ -441,18 +434,27 @@ bool UnwrappedMedia::needInfoDisplay() const { && _content->alwaysShowOutTimestamp()); } -int UnwrappedMedia::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) 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) { accumulate_max(result, st::msgReplyPadding.left() + reply->replyToWidth()); } - if (forwarded) { - accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + forwarded->text.maxWidth() + st::msgReplyPadding.right()); - } 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 64476880e..a1e3a8e23 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 false; + } [[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,11 +87,20 @@ 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 HistoryMessageForwarded *forwarded, - const int innerw = 0) const; + int outerw) const; void drawSurrounding( Painter &p, const QRect &inner, @@ -112,6 +124,8 @@ private: int replyRight, int fullRight) const; + const HistoryMessageForwarded *getDisplayedForwardedInfo() const; + std::unique_ptr _content; QSize _contentSize; diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp index 4df1fc3f1..e88d2d020 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp @@ -57,28 +57,36 @@ bool Sticker::isEmojiSticker() const { return (_parent->data()->media() == nullptr); } -QSize Sticker::size() { +void Sticker::initSize() { _size = _document->dimensions; const auto maxHeight = int(st::maxStickerSize / 256.0 * StickerHeight()); - if (isEmojiSticker()) { + if (isEmojiSticker() || _diceIndex >= 0) { constexpr auto kIdealStickerSize = 512; const auto zoom = GetEmojiStickerZoom(&_document->session()); const auto convert = [&](int size) { return int(size * maxHeight * zoom / kIdealStickerSize); }; _size = QSize(convert(_size.width()), convert(_size.height())); + [[maybe_unused]] bool result = readyToDrawLottie(); } else { _size = DownscaledSize( _size, { st::maxStickerSize, maxHeight }); } +} + +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(); @@ -86,10 +94,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); } } @@ -97,24 +109,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) { @@ -189,6 +228,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/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/info.style b/Telegram/SourceFiles/info/info.style index 5bb793f21..ff2ccf09a 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -169,7 +169,7 @@ infoTopBarForward: IconButton(infoTopBarBack) { width: 46px; icon: icon {{ "info_media_forward", boxTitleCloseFg }}; iconOver: icon {{ "info_media_forward", boxTitleCloseFgOver }}; - iconPosition: point(6px, -1px); + iconPosition: point(10px, -1px); rippleAreaPosition: point(1px, 6px); } infoTopBarDelete: IconButton(infoTopBarForward) { @@ -245,7 +245,7 @@ infoLayerTopBarForward: IconButton(infoLayerTopBarBack) { width: 45px; icon: icon {{ "info_media_forward", boxTitleCloseFg }}; iconOver: icon {{ "info_media_forward", boxTitleCloseFgOver }}; - iconPosition: point(6px, -1px); + iconPosition: point(11px, -1px); rippleAreaPosition: point(1px, 6px); } infoLayerTopBarDelete: IconButton(infoLayerTopBarForward) { diff --git a/Telegram/SourceFiles/info/info_top_bar.cpp b/Telegram/SourceFiles/info/info_top_bar.cpp index 059a9b5ee..209ec23a8 100644 --- a/Telegram/SourceFiles/info/info_top_bar.cpp +++ b/Telegram/SourceFiles/info/info_top_bar.cpp @@ -629,6 +629,8 @@ rpl::producer TitleValue( return tr::lng_settings_advanced(); case Section::SettingsType::Chat: return tr::lng_settings_section_chat_settings(); + case Section::SettingsType::Folders: + return tr::lng_filters_title(); case Section::SettingsType::Calls: return tr::lng_settings_section_call_settings(); case Section::SettingsType::Kotato: diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index e1f53ce41..bc063e28d 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -410,7 +410,8 @@ void WrapWidget::createTopBar() { // addProfileNotificationsButton(); } else if (section.type() == Section::Type::Settings && (section.settingsType() == Section::SettingsType::Main - || section.settingsType() == Section::SettingsType::Chat)) { + || section.settingsType() == Section::SettingsType::Chat + || section.settingsType() == Section::SettingsType::Kotato)) { addTopBarMenuButton(); } else if (section.type() == Section::Type::Settings && section.settingsType() == Section::SettingsType::Information) { @@ -581,6 +582,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/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/info/settings/info_settings_widget.cpp b/Telegram/SourceFiles/info/settings/info_settings_widget.cpp index 689164a8b..553a2345d 100644 --- a/Telegram/SourceFiles/info/settings/info_settings_widget.cpp +++ b/Telegram/SourceFiles/info/settings/info_settings_widget.cpp @@ -43,7 +43,8 @@ Widget::Widget( : ContentWidget(parent, controller) , _self(controller->key().settingsSelf()) , _type(controller->section().settingsType()) -, _inner(setInnerWidget(::Settings::CreateSection( +, _inner(setInnerWidget( + ::Settings::CreateSection( _type, this, controller->parentController()))) { @@ -55,6 +56,8 @@ Widget::Widget( 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..d5be88734 100644 --- a/Telegram/SourceFiles/info/settings/info_settings_widget.h +++ b/Telegram/SourceFiles/info/settings/info_settings_widget.h @@ -52,6 +52,7 @@ public: Widget( QWidget *parent, not_null controller); + ~Widget(); not_null self() const; 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/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/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/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 9d70faaee..24b65d514 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" @@ -787,10 +788,6 @@ void MainWidget::notify_showScheduledButtonChanged() { _history->notify_showScheduledButtonChanged(); } -void MainWidget::notify_historyMuteUpdated(History *history) { - _dialogs->notify_historyMuteUpdated(history); -} - MsgId MainWidget::highlightedOriginalId() const { return _history->highlightOrigId(); } @@ -1907,8 +1904,8 @@ void MainWidget::showNewSection( using Column = Window::Column; auto saveInStack = (params.way == SectionShow::Way::Forward); - auto thirdSectionTop = getThirdSectionTop(); - auto newThirdGeometry = QRect( + const auto thirdSectionTop = getThirdSectionTop(); + const auto newThirdGeometry = QRect( width() - st::columnMinimalWidthThird, thirdSectionTop, st::columnMinimalWidthThird, @@ -1920,9 +1917,10 @@ void MainWidget::showNewSection( Column::Third, newThirdGeometry) : nullptr; + const auto layerRect = parentWidget()->rect(); if (newThirdSection) { saveInStack = false; - } else if (auto layer = memento.createLayer(_controller, rect())) { + } else if (auto layer = memento.createLayer(_controller, layerRect)) { if (params.activation != anim::activation::background) { Ui::hideLayer(anim::type::instant); } @@ -2191,9 +2189,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) { @@ -4063,6 +4061,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/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index ea8963730..397fe4fef 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(); @@ -289,7 +288,6 @@ public: bool notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo); void notify_userIsBotChanged(UserData *bot); void notify_showScheduledButtonChanged(); - void notify_historyMuteUpdated(History *history); MsgId highlightedOriginalId() const; diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index f7b30c82c..fdc97fe4f 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -834,7 +834,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/mainwindow.h b/Telegram/SourceFiles/mainwindow.h index 2e923dfc8..0ba8edc38 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/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(), diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 3112daa7c..0194b2d0c 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -1378,6 +1378,7 @@ void OverlayWidget::onShowInFolder() { auto filepath = _doc->filepath(DocumentData::FilePathResolve::Checked); if (!filepath.isEmpty()) { File::ShowInFolder(filepath); + close(); } } diff --git a/Telegram/SourceFiles/mtproto/session_private.cpp b/Telegram/SourceFiles/mtproto/session_private.cpp index 1cba199f1..69f6370a9 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)); diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 73caee832..d959046a1 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -97,7 +97,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; @@ -177,7 +178,7 @@ QIcon TrayIconGen(int counter, bool muted) { || iconThemeName != TrayIconThemeName || iconName != TrayIconName) { if (!iconName.isEmpty()) { - if(systemIcon.isNull()) { + if (systemIcon.isNull()) { systemIcon = QIcon::fromTheme(iconName); } @@ -489,11 +490,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; @@ -502,7 +504,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; } @@ -514,7 +517,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; } @@ -549,6 +553,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()) { @@ -576,7 +584,7 @@ void MainWindow::onSNIOwnerChanged( cSetSupportTray(trayAvailable); - if(cSupportTray()) { + if (cSupportTray()) { psSetupTrayIcon(); } else { LOG(("System tray is not available.")); @@ -597,7 +605,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 diff --git a/Telegram/SourceFiles/platform/mac/mac_touchbar.mm b/Telegram/SourceFiles/platform/mac/mac_touchbar.mm index 279a4e5df..0ddeb5d60 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; diff --git a/Telegram/SourceFiles/profile/profile_block_group_members.cpp b/Telegram/SourceFiles/profile/profile_block_group_members.cpp index fe728c61f..d50819d5d 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 diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index 4b60a46d4..cd3c433e0 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -319,3 +319,8 @@ rpl::producer RecentStickersLimitChanges() { int gUserpicCornersType = 3; bool gShowTopBarUserpic = false; int gCustomAppIcon = 0; + +int gDefaultFilterId = 0; +bool gUnmutedFilterCounterOnly = false; +bool gHideFilterEditButton = false; +bool gHideFilterNames = false; diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h index bedeba54c..0f8caa183 100644 --- a/Telegram/SourceFiles/settings.h +++ b/Telegram/SourceFiles/settings.h @@ -249,3 +249,8 @@ void SetRecentStickersLimit(int limit); DeclareSetting(int, UserpicCornersType); DeclareSetting(bool, ShowTopBarUserpic); DeclareSetting(int, CustomAppIcon); + +DeclareSetting(int, DefaultFilterId); +DeclareSetting(bool, UnmutedFilterCounterOnly); +DeclareSetting(bool, HideFilterEditButton); +DeclareSetting(bool, HideFilterNames); diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 28b5654cb..f2e2ab207 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 }}; @@ -218,6 +219,9 @@ settingsAccentColorSize: 24px; settingsAccentColorSkip: 4px; settingsAccentColorLine: 3px; +settingsFilterIconSkip: 68px; +settingsFilterIconLeft: 17px; + dictionariesSectionButton: SettingsButton(settingsUpdateToggle) { font: font(14px semibold); } diff --git a/Telegram/SourceFiles/settings/settings_codes.cpp b/Telegram/SourceFiles/settings/settings_codes.cpp index e50471fa8..bb965a89d 100644 --- a/Telegram/SourceFiles/settings/settings_codes.cpp +++ b/Telegram/SourceFiles/settings/settings_codes.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_specific.h" #include "ui/toast/toast.h" #include "mainwidget.h" +#include "mainwindow.h" #include "data/data_session.h" #include "storage/localstorage.h" #include "boxes/confirm_box.h" @@ -22,14 +23,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/update_checker.h" #include "window/themes/window_theme.h" #include "window/themes/window_theme_editor.h" +#include "window/window_session_controller.h" #include "media/audio/media_audio_track.h" +#include "settings/settings_common.h" #include "facades.h" namespace Settings { +namespace { + +using SessionController = Window::SessionController; auto GenerateCodes() { - auto codes = std::map>(); - codes.emplace(qsl("debugmode"), [](::Main::Session *session) { + auto codes = std::map>(); + codes.emplace(qsl("debugmode"), [](SessionController *window) { QString text = Logs::DebugEnabled() ? qsl("Do you want to disable DEBUG logs?") : qsl("Do you want to enable DEBUG logs?\n\n" @@ -38,24 +44,24 @@ auto GenerateCodes() { Core::App().switchDebugMode(); })); }); - codes.emplace(qsl("viewlogs"), [](::Main::Session *session) { + codes.emplace(qsl("viewlogs"), [](SessionController *window) { File::ShowInFolder(cWorkingDir() + "log.txt"); }); - codes.emplace(qsl("testmode"), [](::Main::Session *session) { + codes.emplace(qsl("testmode"), [](SessionController *window) { auto text = cTestMode() ? qsl("Do you want to disable TEST mode?") : qsl("Do you want to enable TEST mode?\n\nYou will be switched to test cloud."); Ui::show(Box(text, [] { Core::App().switchTestMode(); })); }); if (!Core::UpdaterDisabled()) { - codes.emplace(qsl("testupdate"), [](::Main::Session *session) { + codes.emplace(qsl("testupdate"), [](SessionController *window) { Core::UpdateChecker().test(); }); } - codes.emplace(qsl("loadlang"), [](::Main::Session *session) { + codes.emplace(qsl("loadlang"), [](SessionController *window) { Lang::CurrentCloudManager().switchToLanguage({ qsl("#custom") }); }); - codes.emplace(qsl("debugfiles"), [](::Main::Session *session) { + codes.emplace(qsl("debugfiles"), [](SessionController *window) { if (!Logs::DebugEnabled()) { return; } @@ -66,16 +72,10 @@ auto GenerateCodes() { } Ui::show(Box(DebugLogging::FileLoader() ? qsl("Enabled file download logging") : qsl("Disabled file download logging"))); }); - codes.emplace(qsl("crashplease"), [](::Main::Session *session) { + codes.emplace(qsl("crashplease"), [](SessionController *window) { Unexpected("Crashed in Settings!"); }); - codes.emplace(qsl("workmode"), [](::Main::Session *session) { - auto text = Global::DialogsModeEnabled() ? qsl("Disable work mode?") : qsl("Enable work mode?"); - Ui::show(Box(text, [] { - Core::App().switchWorkMode(); - })); - }); - codes.emplace(qsl("moderate"), [](::Main::Session *session) { + codes.emplace(qsl("moderate"), [](SessionController *window) { auto text = Global::ModerateModeEnabled() ? qsl("Disable moderate mode?") : qsl("Enable moderate mode?"); Ui::show(Box(text, [] { Global::SetModerateModeEnabled(!Global::ModerateModeEnabled()); @@ -83,19 +83,19 @@ auto GenerateCodes() { Ui::hideLayer(); })); }); - codes.emplace(qsl("getdifference"), [](::Main::Session *session) { + codes.emplace(qsl("getdifference"), [](SessionController *window) { if (auto main = App::main()) { main->getDifference(); } }); - codes.emplace(qsl("loadcolors"), [](::Main::Session *session) { + codes.emplace(qsl("loadcolors"), [](SessionController *window) { FileDialog::GetOpenPath(Core::App().getFileDialogParent(), "Open palette file", "Palette (*.tdesktop-palette)", [](const FileDialog::OpenResult &result) { if (!result.paths.isEmpty()) { Window::Theme::Apply(result.paths.front()); } }); }); - codes.emplace(qsl("videoplayer"), [](::Main::Session *session) { + codes.emplace(qsl("videoplayer"), [](SessionController *window) { auto text = cUseExternalVideoPlayer() ? qsl("Use internal video player?") : qsl("Use external video player?"); Ui::show(Box(text, [] { cSetUseExternalVideoPlayer(!cUseExternalVideoPlayer()); @@ -103,7 +103,7 @@ auto GenerateCodes() { Ui::hideLayer(); })); }); - codes.emplace(qsl("endpoints"), [](::Main::Session *session) { + codes.emplace(qsl("endpoints"), [](SessionController *window) { FileDialog::GetOpenPath(Core::App().getFileDialogParent(), "Open DC endpoints", "DC Endpoints (*.tdesktop-endpoints)", [](const FileDialog::OpenResult &result) { if (!result.paths.isEmpty()) { if (!Core::App().dcOptions()->loadFromFile(result.paths.front())) { @@ -112,14 +112,19 @@ auto GenerateCodes() { } }); }); + codes.emplace(qsl("folders"), [](SessionController *window) { + if (window) { + window->showSettings(Settings::Type::Folders); + } + }); #ifndef TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME - codes.emplace(qsl("registertg"), [](::Main::Session *session) { + codes.emplace(qsl("registertg"), [](SessionController *window) { Platform::RegisterCustomScheme(true); Ui::Toast::Show("Forced custom scheme register."); }); #endif // !TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME - codes.emplace(qsl("export"), [](::Main::Session *session) { - session->data().startExport(); + codes.emplace(qsl("export"), [](SessionController *window) { + window->session().data().startExport(); }); auto audioFilters = qsl("Audio files (*.wav *.mp3);;") + FileDialog::AllFilesFilter(); @@ -132,28 +137,31 @@ auto GenerateCodes() { qsl("call_end"), }; for (auto &key : audioKeys) { - codes.emplace(key, [=](::Main::Session *session) { - if (!session) { + codes.emplace(key, [=](SessionController *window) { + if (!window) { return; } - FileDialog::GetOpenPath(Core::App().getFileDialogParent(), "Open audio file", audioFilters, crl::guard(session, [=](const FileDialog::OpenResult &result) { + FileDialog::GetOpenPath(Core::App().getFileDialogParent(), "Open audio file", audioFilters, crl::guard(&window->session(), [=](const FileDialog::OpenResult &result) { if (Main::Session::Exists() && !result.paths.isEmpty()) { auto track = Media::Audio::Current().createTrack(); track->fillFromFile(result.paths.front()); if (track->failed()) { - Ui::show(Box("Could not audio :( Errors in 'log.txt'.")); + Ui::show(Box( + "Could not audio :( Errors in 'log.txt'.")); } else { - session->settings().setSoundOverride(key, result.paths.front()); + window->session().settings().setSoundOverride( + key, + result.paths.front()); Local::writeUserSettings(); } } })); }); } - codes.emplace(qsl("sounds_reset"), [](::Main::Session *session) { - if (session) { - session->settings().clearSoundOverrides(); + codes.emplace(qsl("sounds_reset"), [](SessionController *window) { + if (window) { + window->session().settings().clearSoundOverrides(); Local::writeUserSettings(); Ui::show(Box("All sound overrides were reset.")); } @@ -162,7 +170,9 @@ auto GenerateCodes() { return codes; } -void CodesFeedString(::Main::Session *session, const QString &text) { +} // namespace + +void CodesFeedString(SessionController *window, const QString &text) { static const auto codes = GenerateCodes(); static auto secret = QString(); @@ -173,7 +183,7 @@ void CodesFeedString(::Main::Session *session, const QString &text) { auto found = false; for (const auto &[key, method] : codes) { if (piece == key) { - method(session); + method(window); from = size; found = true; break; diff --git a/Telegram/SourceFiles/settings/settings_codes.h b/Telegram/SourceFiles/settings/settings_codes.h index 850daed45..a9e13582f 100644 --- a/Telegram/SourceFiles/settings/settings_codes.h +++ b/Telegram/SourceFiles/settings/settings_codes.h @@ -7,12 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -namespace Main { -class Session; +namespace Window { +class SessionController; } // namespace Main namespace Settings { -void CodesFeedString(::Main::Session *session, const QString &text); +void CodesFeedString(Window::SessionController *window, const QString &text); } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_common.cpp b/Telegram/SourceFiles/settings/settings_common.cpp index dab5c00ca..b5a66d55e 100644 --- a/Telegram/SourceFiles/settings/settings_common.cpp +++ b/Telegram/SourceFiles/settings/settings_common.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_main.h" #include "settings/settings_notifications.h" #include "settings/settings_privacy_security.h" +#include "settings/settings_folders.h" #include "settings/settings_calls.h" #include "settings/settings_kotato.h" #include "ui/wrap/padding_wrap.h" @@ -49,6 +50,8 @@ object_ptr
CreateSection( return object_ptr(parent, controller); case Type::Advanced: return object_ptr(parent, controller); + case Type::Folders: + return object_ptr(parent, controller); case Type::Chat: return object_ptr(parent, controller); case Type::Calls: @@ -85,21 +88,19 @@ void AddDividerText( st::settingsDividerLabelPadding)); } -not_null AddButton( - not_null container, +object_ptr