diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ad5ccedc52a7c9..7a65b483fb3f26 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -28,9 +28,10 @@ jobs: run: | sudo apt update curl -sSL https://install.python-poetry.org | python3 - + echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin - name: Free up some disk space. - uses: jlumbroso/free-disk-space@76866dbe54312617f00798d1762df7f43def6e5c + uses: jlumbroso/free-disk-space@f68fdb76e2ea636224182cfb7377ff9a1708f9b8 - name: Docker image build. run: | @@ -40,6 +41,4 @@ jobs: - name: Push the Docker image. if: ${{ github.ref_name == github.event.repository.default_branch }} - run: | - echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin - docker push $IMAGE_TAG + run: docker push $IMAGE_TAG diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 162b6224df110b..fe6d3752c8b8b6 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -41,17 +41,8 @@ on: jobs: linux: - name: CentOS 7 + name: Rocky Linux 8 runs-on: ubuntu-latest - container: - image: ghcr.io/${{ github.repository }}/centos_env - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - defaults: - run: - shell: scl enable rh-python38 -- scl enable llvm-toolset-7.0 -- scl enable devtoolset-10 -- bash --noprofile --norc -eo pipefail {0} strategy: matrix: @@ -75,12 +66,13 @@ jobs: - name: First set up. run: | - gcc --version - ln -s /usr/src/Libraries + echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin + docker pull ghcr.io/$GITHUB_REPOSITORY/centos_env + docker tag ghcr.io/$GITHUB_REPOSITORY/centos_env tdesktop:centos_env - name: Telegram Desktop build. run: | - cd $REPO_NAME/Telegram + cd $REPO_NAME DEFINE="" if [ -n "${{ matrix.defines }}" ]; then @@ -91,18 +83,21 @@ jobs: echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV fi - ./configure.sh \ + docker run --rm \ + -v $PWD:/usr/src/tdesktop \ + -e CONFIG=Debug \ + tdesktop:centos_env \ + /usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \ -D CMAKE_C_FLAGS_DEBUG="" \ -D CMAKE_CXX_FLAGS_DEBUG="" \ -D CMAKE_C_FLAGS="-Werror" \ -D CMAKE_CXX_FLAGS="-Werror" \ -D CMAKE_EXE_LINKER_FLAGS="-s" \ -D TDESKTOP_API_TEST=ON \ + -D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \ $DEFINE - cmake --build ../out --config Debug --parallel - - name: Check. run: | filePath="$REPO_NAME/out/Debug/Telegram" @@ -121,7 +116,7 @@ jobs: run: | cd $REPO_NAME/out/Debug mkdir artifact - mv Telegram artifact/ + mv {Telegram,Updater} artifact/ - uses: actions/upload-artifact@master if: env.UPLOAD_ARTIFACT == 'true' name: Upload artifact. diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 96bfe6e1322f0f..8e99d7162db3af 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -40,7 +40,7 @@ jobs: macos: name: MacOS - runs-on: macos-12 + runs-on: macos-13 strategy: matrix: @@ -115,6 +115,7 @@ jobs: -D CMAKE_CXX_FLAGS="-Werror" \ -D CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO \ -D TDESKTOP_API_TEST=ON \ + -D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \ $DEFINE diff --git a/.github/workflows/mac_packaged.yml b/.github/workflows/mac_packaged.yml new file mode 100644 index 00000000000000..51ba9917a1fb77 --- /dev/null +++ b/.github/workflows/mac_packaged.yml @@ -0,0 +1,161 @@ +name: MacOS Packaged. + +on: + push: + paths-ignore: + - 'docs/**' + - '**.md' + - 'changelog.txt' + - 'LEGAL' + - 'LICENSE' + - '.github/**' + - '!.github/workflows/mac_packaged.yml' + - 'lib/xdg/**' + - 'snap/**' + - 'Telegram/build/**' + - 'Telegram/Resources/uwp/**' + - 'Telegram/Resources/winrc/**' + - 'Telegram/SourceFiles/platform/win/**' + - 'Telegram/SourceFiles/platform/linux/**' + - 'Telegram/configure.bat' + pull_request: + paths-ignore: + - 'docs/**' + - '**.md' + - 'changelog.txt' + - 'LEGAL' + - 'LICENSE' + - '.github/**' + - '!.github/workflows/mac_packaged.yml' + - 'lib/xdg/**' + - 'snap/**' + - 'Telegram/build/**' + - 'Telegram/Resources/uwp/**' + - 'Telegram/Resources/winrc/**' + - 'Telegram/SourceFiles/platform/win/**' + - 'Telegram/SourceFiles/platform/linux/**' + - 'Telegram/configure.bat' + +jobs: + + macos: + name: MacOS + runs-on: macos-13 + + strategy: + matrix: + defines: + - "" + + env: + GIT: "https://github.com" + OPENALDIR: "/usr/local/opt/openal-soft" + UPLOAD_ARTIFACT: "false" + ONLY_CACHE: "false" + MANUAL_CACHING: "1" + AUTO_CACHING: "1" + + steps: + - name: Get repository name. + run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV + + - name: Clone. + uses: actions/checkout@v3.1.0 + with: + submodules: recursive + path: ${{ env.REPO_NAME }} + + - name: First set up. + run: | + brew update + brew upgrade || true + brew install autoconf automake boost cmake ffmpeg openal-soft openssl opus ninja pkg-config python qt yasm xz + sudo xcode-select -s /Applications/Xcode.app/Contents/Developer + + xcodebuild -version > CACHE_KEY.txt + brew list --versions >> CACHE_KEY.txt + echo $MANUAL_CACHING >> CACHE_KEY.txt + echo "$GITHUB_WORKSPACE" >> CACHE_KEY.txt + if [ "$AUTO_CACHING" = "1" ]; then + thisFile=$REPO_NAME/.github/workflows/mac_packaged.yml + echo `md5 -q $thisFile` >> CACHE_KEY.txt + fi + echo "CACHE_KEY=`md5 -q CACHE_KEY.txt`" >> $GITHUB_ENV + + echo "LibrariesPath=`pwd`" >> $GITHUB_ENV + + curl -o tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master + + - name: RNNoise. + run: | + cd $LibrariesPath + + git clone --depth=1 https://gitlab.xiph.org/xiph/rnnoise.git + cd rnnoise + ./autogen.sh + ./configure --disable-examples --disable-doc + make -j$(sysctl -n hw.logicalcpu) + make install + + - name: WebRTC cache. + id: cache-webrtc + uses: actions/cache@v3.0.11 + with: + path: ${{ env.LibrariesPath }}/tg_owt + key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}-${{ hashFiles('**/tg_owt-version.json') }} + - name: WebRTC. + if: steps.cache-webrtc.outputs.cache-hit != 'true' + run: | + cd $LibrariesPath + + git clone --recursive --depth=1 $GIT/desktop-app/tg_owt.git + cd tg_owt + + cmake -B build . -GNinja -DCMAKE_BUILD_TYPE=Debug + cmake --build build --parallel + + - name: Telegram Desktop build. + if: env.ONLY_CACHE == 'false' + env: + tg_owt_DIR: ${{ env.LibrariesPath }}/tg_owt/build + run: | + cd $REPO_NAME + + DEFINE="" + if [ -n "${{ matrix.defines }}" ]; then + DEFINE="-D ${{ matrix.defines }}=ON" + echo Define from matrix: $DEFINE + echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV + else + echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV + fi + + cmake -Bbuild -GNinja . \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_FIND_FRAMEWORK=LAST \ + -DTDESKTOP_API_TEST=ON \ + -DDESKTOP_APP_USE_PACKAGED_LAZY=ON \ + $DEFINE + + cmake --build build --parallel + + cd build + macdeployqt Telegram.app + codesign --remove-signature Telegram.app + + mkdir dmgsrc + mv Telegram.app dmgsrc + hdiutil create -volname Telegram -srcfolder dmgsrc -ov -format UDZO Telegram.dmg + + - name: Move artifact. + if: env.UPLOAD_ARTIFACT == 'true' + run: | + cd $REPO_NAME/build + mkdir artifact + mv Telegram.dmg artifact/ + - uses: actions/upload-artifact@master + if: env.UPLOAD_ARTIFACT == 'true' + name: Upload artifact. + with: + name: ${{ env.ARTIFACT_NAME }} + path: ${{ env.REPO_NAME }}/build/artifact/ diff --git a/.github/workflows/master_updater.yml b/.github/workflows/master_updater.yml index dc77d50d036c2a..284bb844e40db9 100644 --- a/.github/workflows/master_updater.yml +++ b/.github/workflows/master_updater.yml @@ -11,7 +11,9 @@ jobs: SKIP: "0" to_branch: "master" steps: - - uses: actions/checkout@v3.1.0 + - uses: actions/checkout@v4.1.0 + with: + fetch-depth: 0 if: env.SKIP == '0' - name: Push the code to the master branch. if: env.SKIP == '0' diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml index 4d19e1048d693b..f414a55fd6c584 100644 --- a/.github/workflows/no-response.yml +++ b/.github/workflows/no-response.yml @@ -9,11 +9,38 @@ on: - cron: '0 0 * * *' jobs: - noResponse: + waiting-for-answer: runs-on: ubuntu-latest steps: - uses: lee-dohm/no-response@v0.5.0 with: token: ${{ github.token }} - # Label requiring a response responseRequiredLabel: waiting for answer + + needs-user-action: + runs-on: ubuntu-latest + steps: + - uses: lee-dohm/no-response@v0.5.0 + with: + token: ${{ github.token }} + responseRequiredLabel: needs user action + + cant-reproduce: + if: github.event_name != 'issue_comment' + runs-on: ubuntu-latest + steps: + - uses: lee-dohm/no-response@v0.5.0 + with: + token: ${{ github.token }} + responseRequiredLabel: cant reproduce + closeComment: > + This issue has been automatically closed because no developer succeeded to + reproduce the issue with the given reproduction steps. With only the + information that is currently in the issue, we don't have enough + information to take action. Please reach out if you find what's missing to + reproduce the issue so that we can investigate further. + + + Note that GitHub is a developer communication platform. If you're an ordinary + user seeking for help, get to support crew via `Settings -> Ask question` in + the application. diff --git a/.github/workflows/snap.yml b/.github/workflows/snap.yml index 4191aced9137cd..c2e5dbafe75532 100644 --- a/.github/workflows/snap.yml +++ b/.github/workflows/snap.yml @@ -61,7 +61,7 @@ jobs: sudo snap run lxd waitready - name: Free up some disk space. - uses: jlumbroso/free-disk-space@76866dbe54312617f00798d1762df7f43def6e5c + uses: jlumbroso/free-disk-space@f68fdb76e2ea636224182cfb7377ff9a1708f9b8 - name: Telegram Desktop snap build. run: sg lxd -c 'snap run snapcraft -v' diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 3c86918f22b74f..b42906c7cbe505 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -47,6 +47,7 @@ jobs: strategy: matrix: arch: [Win32, x64] + generator: ["", "Ninja Multi-Config"] env: UPLOAD_ARTIFACT: "false" @@ -109,21 +110,44 @@ jobs: cd %TBUILD% %REPO_NAME%\Telegram\build\prepare\win.bat skip-release silent - - name: Read defines. + - name: Read configuration matrix. shell: bash run: | + ARTIFACT_NAME="Telegram" + + ARCH="" + if [ -n "${{ matrix.arch }}" ]; then + case "${{ matrix.arch }}" in + Win32) ARCH="x86";; + *) ARCH="${{ matrix.arch }}";; + esac + echo "Architecture from matrix: $ARCH" + ARTIFACT_NAME="${ARTIFACT_NAME}_${{ matrix.arch }}" + fi + + GENERATOR="" + if [ -n "${{ matrix.generator }}" ]; then + GENERATOR="-G \"${{ matrix.generator }}\"" + echo "Generator from matrix: $GENERATOR" + ARTIFACT_NAME="${ARTIFACT_NAME}_${{ matrix.generator }}" + fi + echo "TDESKTOP_BUILD_GENERATOR=$GENERATOR" >> $GITHUB_ENV + + [ -n "$GENERATOR" ] && ARCH="" + echo "TDESKTOP_BUILD_ARCH=$ARCH" >> $GITHUB_ENV + DEFINE="" if [ -n "${{ matrix.defines }}" ]; then DEFINE="-D ${{ matrix.defines }}=ON" echo "Define from matrix: $DEFINE" - echo "ARTIFACT_NAME=Telegram_${{ matrix.arch }}_${{ matrix.defines }}" >> $GITHUB_ENV - else - echo "ARTIFACT_NAME=Telegram_${{ matrix.arch }}" >> $GITHUB_ENV + ARTIFACT_NAME="${ARTIFACT_NAME}_${{ matrix.defines }}" fi echo "TDESKTOP_BUILD_DEFINE=$DEFINE" >> $GITHUB_ENV + echo "ARTIFACT_NAME=$ARTIFACT_NAME" >> $GITHUB_ENV + API="-D TDESKTOP_API_TEST=ON" - if [ ${{ github.ref == 'refs/heads/nightly' }} ]; then + if [ $GITHUB_REF == 'refs/heads/nightly' ]; then echo "Use the open credentials." API="-D TDESKTOP_API_ID=611335 -D TDESKTOP_API_HASH=d524b414d21f4d37f08684c1df41ac9c" fi @@ -142,24 +166,26 @@ jobs: cd %TBUILD%\%REPO_NAME%\Telegram call configure.bat ^ - ${{ matrix.arch }} ^ + %TDESKTOP_BUILD_GENERATOR% ^ + %TDESKTOP_BUILD_ARCH% ^ %TDESKTOP_BUILD_API% ^ + -D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^ -D DESKTOP_APP_NO_PDB=ON ^ - %TDESKTOP_BUILD_DEFINE% ^ - -DCMAKE_SYSTEM_VERSION=%SDK% + %TDESKTOP_BUILD_DEFINE% - cd ..\out - msbuild -m Telegram.sln /p:Configuration=Debug,Platform=${{ matrix.arch }},DebugSymbols=false,DebugType=none + cmake --build ..\out --config Debug --parallel - name: Move artifact. - if: (env.UPLOAD_ARTIFACT == 'true') || ${{ github.ref == 'refs/heads/nightly' }} + if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly') run: | + set OUT=%TBUILD%\%REPO_NAME%\out\Debug mkdir artifact - move %TBUILD%\%REPO_NAME%\out\Debug\Telegram.exe artifact/ + move %OUT%\Telegram.exe artifact/ + move %OUT%\Updater.exe artifact/ - uses: actions/upload-artifact@master name: Upload artifact. - if: (env.UPLOAD_ARTIFACT == 'true') || ${{ github.ref == 'refs/heads/nightly' }} + if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly') with: name: ${{ env.ARTIFACT_NAME }} path: artifact\ diff --git a/.gitmodules b/.gitmodules index d3d445255e2c9e..101674a9092b9a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -58,9 +58,6 @@ [submodule "Telegram/ThirdParty/range-v3"] path = Telegram/ThirdParty/range-v3 url = https://github.com/ericniebler/range-v3.git -[submodule "Telegram/ThirdParty/fcitx-qt5"] - path = Telegram/ThirdParty/fcitx-qt5 - url = https://github.com/fcitx/fcitx-qt5.git [submodule "Telegram/ThirdParty/nimf"] path = Telegram/ThirdParty/nimf url = https://github.com/hamonikr/nimf.git @@ -79,9 +76,6 @@ [submodule "Telegram/lib_webview"] path = Telegram/lib_webview url = https://github.com/desktop-app/lib_webview.git -[submodule "Telegram/ThirdParty/jemalloc"] - path = Telegram/ThirdParty/jemalloc - url = https://github.com/jemalloc/jemalloc [submodule "Telegram/ThirdParty/dispatch"] path = Telegram/ThirdParty/dispatch url = https://github.com/apple/swift-corelibs-libdispatch @@ -103,3 +97,6 @@ [submodule "Telegram/ThirdParty/wayland"] path = Telegram/ThirdParty/wayland url = https://github.com/gitlab-freedesktop-mirrors/wayland.git +[submodule "Telegram/ThirdParty/libprisma"] + path = Telegram/ThirdParty/libprisma + url = https://github.com/desktop-app/libprisma.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ec62b1584c4bd..abea2aaf4e766a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,8 +10,9 @@ if (APPLE) else() cmake_minimum_required(VERSION 3.16) endif() -cmake_policy(SET CMP0076 NEW) -cmake_policy(SET CMP0091 NEW) +if (POLICY CMP0149) + cmake_policy(SET CMP0149 NEW) +endif() set_property(GLOBAL PROPERTY USE_FOLDERS ON) @@ -59,9 +60,9 @@ include(cmake/options.cmake) if (NOT DESKTOP_APP_USE_PACKAGED) if (WIN32) - set(qt_version 5.15.10) + set(qt_version 5.15.12) elseif (APPLE) - set(qt_version 6.3.2) + set(qt_version 6.2.7) endif() endif() include(cmake/external/qt/package.cmake) diff --git a/LEGAL b/LEGAL index e7c429315a6a1a..5d0f5e8a7155ed 100644 --- a/LEGAL +++ b/LEGAL @@ -1,7 +1,7 @@ This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. -Copyright (c) 2014-2023 The Telegram Desktop Authors. +Copyright (c) 2014-2024 The Telegram Desktop Authors. Telegram Desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/README.md b/README.md index 3a73a57d92e4f5..b57211f3189d37 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,18 @@ The latest version is available for * [Windows 7 and above (64 bit)](https://telegram.org/dl/desktop/win64) ([portable](https://telegram.org/dl/desktop/win64_portable)) * [Windows 7 and above (32 bit)](https://telegram.org/dl/desktop/win) ([portable](https://telegram.org/dl/desktop/win_portable)) -* [macOS 10.12 and above](https://telegram.org/dl/desktop/mac) +* [macOS 10.13 and above](https://telegram.org/dl/desktop/mac) * [Linux static build for 64 bit](https://telegram.org/dl/desktop/linux) * [Snap](https://snapcraft.io/telegram-desktop) * [Flatpak](https://flathub.org/apps/details/org.telegram.desktop) ## Old system versions +Version **4.9.9** was the last that supports older systems + +* [macOS 10.12](https://updates.tdesktop.com/tmac/tsetup.4.9.9.dmg) +* [Linux with glibc < 2.28 static build](https://updates.tdesktop.com/tlinux/tsetup.4.9.9.tar.xz) + Version **2.4.4** was the last that supports older systems * [OS X 10.10 and 10.11](https://updates.tdesktop.com/tosx/tsetup-osx.2.4.4.dmg) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 8de1d1e8bcc8eb..e1766f5e63d9b7 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -28,6 +28,7 @@ include(cmake/lib_ffmpeg.cmake) include(cmake/lib_stripe.cmake) include(cmake/lib_tgvoip.cmake) include(cmake/lib_tgcalls.cmake) +include(cmake/lib_prisma.cmake) include(cmake/td_export.cmake) include(cmake/td_mtproto.cmake) include(cmake/td_lang.cmake) @@ -129,6 +130,8 @@ PRIVATE api/api_messages_search.h api/api_messages_search_merged.cpp api/api_messages_search_merged.h + api/api_peer_colors.cpp + api/api_peer_colors.h api/api_peer_photo.cpp api/api_peer_photo.h api/api_polls.cpp @@ -151,6 +154,8 @@ PRIVATE api/api_sensitive_content.h api/api_single_message_search.cpp api/api_single_message_search.h + api/api_statistics.cpp + api/api_statistics.h api/api_text_entities.cpp api/api_text_entities.h api/api_toggling_media.cpp @@ -195,6 +200,8 @@ PRIVATE boxes/peers/edit_participant_box.h boxes/peers/edit_participants_box.cpp boxes/peers/edit_participants_box.h + boxes/peers/edit_peer_color_box.cpp + boxes/peers/edit_peer_color_box.h boxes/peers/edit_peer_common.h boxes/peers/edit_peer_info_box.cpp boxes/peers/edit_peer_info_box.h @@ -216,6 +223,8 @@ PRIVATE boxes/peers/peer_short_info_box.h boxes/peers/prepare_short_info_box.cpp boxes/peers/prepare_short_info_box.h + boxes/peers/replace_boost_box.cpp + boxes/peers/replace_boost_box.h boxes/about_box.cpp boxes/about_box.h boxes/about_sponsored_box.cpp @@ -373,16 +382,18 @@ PRIVATE chat_helpers/gifs_list_widget.h chat_helpers/message_field.cpp chat_helpers/message_field.h + chat_helpers/share_message_phrase_factory.cpp + chat_helpers/share_message_phrase_factory.h chat_helpers/spellchecker_common.cpp chat_helpers/spellchecker_common.h + chat_helpers/stickers_dice_pack.cpp + chat_helpers/stickers_dice_pack.h chat_helpers/stickers_emoji_image_loader.cpp chat_helpers/stickers_emoji_image_loader.h chat_helpers/stickers_emoji_pack.cpp chat_helpers/stickers_emoji_pack.h chat_helpers/stickers_gift_box_pack.cpp chat_helpers/stickers_gift_box_pack.h - chat_helpers/stickers_dice_pack.cpp - chat_helpers/stickers_dice_pack.h chat_helpers/stickers_list_footer.cpp chat_helpers/stickers_list_footer.h chat_helpers/stickers_list_widget.cpp @@ -395,6 +406,8 @@ PRIVATE chat_helpers/tabbed_section.h chat_helpers/tabbed_selector.cpp chat_helpers/tabbed_selector.h + chat_helpers/ttl_media_layer_widget.cpp + chat_helpers/ttl_media_layer_widget.h core/application.cpp core/application.h core/base_integration.cpp @@ -413,6 +426,7 @@ PRIVATE core/crash_report_window.h core/crash_reports.cpp core/crash_reports.h + core/deadlock_detector.h core/file_utilities.cpp core/file_utilities.h core/launcher.cpp @@ -449,6 +463,7 @@ PRIVATE data/data_audio_msg_id.h data/data_auto_download.cpp data/data_auto_download.h + data/data_boosts.h data/data_bot_app.cpp data/data_bot_app.h data/data_chat.cpp @@ -500,6 +515,7 @@ PRIVATE data/data_groups.h data/data_histories.cpp data/data_histories.h + data/data_lastseen_status.h data/data_location.cpp data/data_location.h data/data_media_rotation.cpp @@ -537,6 +553,10 @@ PRIVATE data/data_replies_list.h data/data_reply_preview.cpp data/data_reply_preview.h + data/data_saved_messages.cpp + data/data_saved_messages.h + data/data_saved_sublist.cpp + data/data_saved_sublist.h data/data_search_controller.cpp data/data_search_controller.h data/data_send_action.cpp @@ -551,6 +571,7 @@ PRIVATE data/data_sparse_ids.h data/data_sponsored_messages.cpp data/data_sponsored_messages.h + data/data_statistics.h data/data_stories.cpp data/data_stories.h data/data_stories_ids.cpp @@ -591,6 +612,8 @@ PRIVATE dialogs/dialogs_row.h dialogs/dialogs_search_from_controllers.cpp dialogs/dialogs_search_from_controllers.h + dialogs/dialogs_search_tags.cpp + dialogs/dialogs_search_tags.h dialogs/dialogs_widget.cpp dialogs/dialogs_widget.h dialogs/ui/dialogs_layout.cpp @@ -645,14 +668,16 @@ PRIVATE history/view/controls/history_view_compose_controls.h history/view/controls/history_view_compose_search.cpp history/view/controls/history_view_compose_search.h + history/view/controls/history_view_draft_options.cpp + history/view/controls/history_view_draft_options.h history/view/controls/history_view_forward_panel.cpp history/view/controls/history_view_forward_panel.h history/view/controls/history_view_ttl_button.cpp history/view/controls/history_view_ttl_button.h history/view/controls/history_view_voice_record_bar.cpp history/view/controls/history_view_voice_record_bar.h - history/view/controls/history_view_voice_record_button.cpp - history/view/controls/history_view_voice_record_button.h + history/view/controls/history_view_webpage_processor.cpp + history/view/controls/history_view_webpage_processor.h history/view/media/history_view_call.cpp history/view/media/history_view_call.h history/view/media/history_view_contact.cpp @@ -671,6 +696,8 @@ PRIVATE history/view/media/history_view_game.h history/view/media/history_view_gif.cpp history/view/media/history_view_gif.h + history/view/media/history_view_giveaway.cpp + history/view/media/history_view_giveaway.h history/view/media/history_view_invoice.cpp history/view/media/history_view_invoice.h history/view/media/history_view_large_emoji.cpp @@ -695,6 +722,8 @@ PRIVATE history/view/media/history_view_premium_gift.h history/view/media/history_view_service_box.cpp history/view/media/history_view_service_box.h + history/view/media/history_view_similar_channels.cpp + history/view/media/history_view_similar_channels.h history/view/media/history_view_slot_machine.cpp history/view/media/history_view_slot_machine.h history/view/media/history_view_sticker.cpp @@ -722,6 +751,8 @@ PRIVATE history/view/reactions/history_view_reactions_strip.h history/view/reactions/history_view_reactions_tabs.cpp history/view/reactions/history_view_reactions_tabs.h + history/view/history_view_about_view.cpp + history/view/history_view_about_view.h history/view/history_view_bottom_info.cpp history/view/history_view_bottom_info.h history/view/history_view_contact_status.cpp @@ -756,6 +787,8 @@ PRIVATE history/view/history_view_quick_action.h history/view/history_view_replies_section.cpp history/view/history_view_replies_section.h + history/view/history_view_reply.cpp + history/view/history_view_reply.h history/view/history_view_requests_bar.cpp history/view/history_view_requests_bar.h history/view/history_view_schedule_box.cpp @@ -768,8 +801,12 @@ PRIVATE history/view/history_view_service_message.h history/view/history_view_spoiler_click_handler.cpp history/view/history_view_spoiler_click_handler.h + history/view/history_view_sponsored_click_handler.cpp + history/view/history_view_sponsored_click_handler.h history/view/history_view_sticker_toast.cpp history/view/history_view_sticker_toast.h + history/view/history_view_sublist_section.cpp + history/view/history_view_sublist_section.h history/view/history_view_transcribe_button.cpp history/view/history_view_transcribe_button.h history/view/history_view_translate_bar.cpp @@ -810,20 +847,14 @@ PRIVATE history/history_view_highlight_manager.h history/history_widget.cpp history/history_widget.h - info/info_content_widget.cpp - info/info_content_widget.h - info/info_controller.cpp - info/info_controller.h - info/info_layer_widget.cpp - info/info_layer_widget.h - info/info_memento.cpp - info/info_memento.h - info/info_section_widget.cpp - info/info_section_widget.h - info/info_top_bar.cpp - info/info_top_bar.h - info/info_wrap_widget.cpp - info/info_wrap_widget.h + info/boosts/giveaway/giveaway_list_controllers.cpp + info/boosts/giveaway/giveaway_list_controllers.h + info/boosts/create_giveaway_box.cpp + info/boosts/create_giveaway_box.h + info/boosts/info_boosts_inner_widget.cpp + info/boosts/info_boosts_inner_widget.h + info/boosts/info_boosts_widget.cpp + info/boosts/info_boosts_widget.h info/common_groups/info_common_groups_inner_widget.cpp info/common_groups/info_common_groups_inner_widget.h info/common_groups/info_common_groups_widget.cpp @@ -877,8 +908,21 @@ PRIVATE info/profile/info_profile_values.h info/profile/info_profile_widget.cpp info/profile/info_profile_widget.h + info/saved/info_saved_sublists_widget.cpp + info/saved/info_saved_sublists_widget.h info/settings/info_settings_widget.cpp info/settings/info_settings_widget.h + info/similar_channels/info_similar_channels_widget.cpp + info/similar_channels/info_similar_channels_widget.h + info/statistics/info_statistics_common.h + info/statistics/info_statistics_inner_widget.cpp + info/statistics/info_statistics_inner_widget.h + info/statistics/info_statistics_list_controllers.cpp + info/statistics/info_statistics_list_controllers.h + info/statistics/info_statistics_recent_message.cpp + info/statistics/info_statistics_recent_message.h + info/statistics/info_statistics_widget.cpp + info/statistics/info_statistics_widget.h info/stories/info_stories_inner_widget.cpp info/stories/info_stories_inner_widget.h info/stories/info_stories_provider.cpp @@ -897,6 +941,20 @@ PRIVATE info/userpic/info_userpic_emoji_builder_preview.h info/userpic/info_userpic_emoji_builder_widget.cpp info/userpic/info_userpic_emoji_builder_widget.h + info/info_content_widget.cpp + info/info_content_widget.h + info/info_controller.cpp + info/info_controller.h + info/info_layer_widget.cpp + info/info_layer_widget.h + info/info_memento.cpp + info/info_memento.h + info/info_section_widget.cpp + info/info_section_widget.h + info/info_top_bar.cpp + info/info_top_bar.h + info/info_wrap_widget.cpp + info/info_wrap_widget.h inline_bots/bot_attach_web_view.cpp inline_bots/bot_attach_web_view.h inline_bots/inline_bot_layout_internal.cpp @@ -959,6 +1017,7 @@ PRIVATE media/audio/media_audio.h media/audio/media_audio_capture.cpp media/audio/media_audio_capture.h + media/audio/media_audio_capture_common.h media/audio/media_audio_ffmpeg_loader.cpp media/audio/media_audio_ffmpeg_loader.h media/audio/media_audio_loader.cpp @@ -993,6 +1052,8 @@ PRIVATE media/stories/media_stories_recent_views.h media/stories/media_stories_reply.cpp media/stories/media_stories_reply.h + media/stories/media_stories_repost_view.cpp + media/stories/media_stories_repost_view.h media/stories/media_stories_share.cpp media/stories/media_stories_share.h media/stories/media_stories_sibling.cpp @@ -1172,8 +1233,6 @@ PRIVATE platform/mac/touchbar/mac_touchbar_manager.mm platform/mac/touchbar/mac_touchbar_media_view.h platform/mac/touchbar/mac_touchbar_media_view.mm - platform/win/audio_win.cpp - platform/win/audio_win.h platform/win/file_utilities_win.cpp platform/win/file_utilities_win.h platform/win/launcher_win.cpp @@ -1197,7 +1256,6 @@ PRIVATE platform/win/windows_autostart_task.h platform/win/windows_toast_activator.cpp platform/win/windows_toast_activator.h - platform/platform_audio.h platform/platform_file_utilities.h platform/platform_launcher.h platform/platform_integration.cpp @@ -1243,8 +1301,8 @@ PRIVATE settings/settings_calls.h settings/settings_codes.cpp settings/settings_codes.h - settings/settings_common.cpp - settings/settings_common.h + settings/settings_common_session.cpp + settings/settings_common_session.h settings/settings_experimental.cpp settings/settings_experimental.h settings/settings_folders.cpp @@ -1359,6 +1417,8 @@ PRIVATE ui/widgets/level_meter.h ui/countryinput.cpp ui/countryinput.h + ui/dynamic_thumbnails.cpp + ui/dynamic_thumbnails.h ui/filter_icons.cpp ui/filter_icons.h ui/filter_icon_panel.cpp @@ -1404,6 +1464,7 @@ PRIVATE window/window_section_common.h window/window_session_controller.cpp window/window_session_controller.h + window/window_session_controller_link_info.h window/window_slide_animation.cpp window/window_slide_animation.h window/window_top_bar_wrap.h @@ -1474,6 +1535,7 @@ PRIVATE qrc/emoji_5.qrc qrc/emoji_6.qrc qrc/emoji_7.qrc + qrc/emoji_8.qrc qrc/emoji_preview.qrc qrc/telegram/animations.qrc qrc/telegram/export.qrc @@ -1512,7 +1574,39 @@ elseif (APPLE) endif() set(icons_path ${CMAKE_CURRENT_SOURCE_DIR}/Telegram/Images.xcassets) - target_add_resource(Telegram ${icons_path}) + if (CMAKE_GENERATOR STREQUAL Xcode) + target_add_resource(Telegram ${icons_path}) + else() + set(icon_path ${icons_path}/Icon.iconset) + find_program(ICONUTIL iconutil) + find_program(PNG2ICNS png2icns) + if (ICONUTIL) + add_custom_command( + OUTPUT Icon.icns + COMMAND ${ICONUTIL} + ARGS + --convert icns + --output Icon.icns + ${icon_path} + ) + elseif (PNG2ICNS) + add_custom_command( + OUTPUT Icon.icns + COMMAND ${PNG2ICNS} + ARGS + Icon.icns + ${icon_path}/icon_16x16.png + ${icon_path}/icon_32x32.png + ${icon_path}/icon_128x128.png + ${icon_path}/icon_256x256.png + ${icon_path}/icon_512x512.png + ) + endif() + if (ICONUTIL OR PNG2ICNS) + set_source_files_properties(Icon.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + target_add_resource(Telegram Icon.icns) + endif() + endif() set(lang_packs en @@ -1537,25 +1631,19 @@ elseif (APPLE) PRE_LINK COMMAND mkdir -p $/../Resources COMMAND cp ${CMAKE_BINARY_DIR}/lib_ui.rcc $/../Resources + COMMAND cp ${CMAKE_BINARY_DIR}/lib_spellcheck.rcc $/../Resources ) - if (NOT build_macstore) + if (NOT build_macstore AND NOT DESKTOP_APP_DISABLE_CRASH_REPORTS) + if (DESKTOP_APP_MAC_ARCH STREQUAL "x86_64" OR DESKTOP_APP_MAC_ARCH STREQUAL "arm64") + set(crashpad_dir_part ".${DESKTOP_APP_MAC_ARCH}") + else() + set(crashpad_dir_part "") + endif() add_custom_command(TARGET Telegram PRE_LINK - COMMAND mkdir -p $/../Frameworks - COMMAND cp $ $/../Frameworks/ + COMMAND mkdir -p $/../Helpers + COMMAND cp ${libs_loc}/crashpad/out/$,Debug,Release>${crashpad_dir_part}/crashpad_handler $/../Helpers/ ) - if (NOT DESKTOP_APP_DISABLE_CRASH_REPORTS) - if (DESKTOP_APP_MAC_ARCH STREQUAL "x86_64" OR DESKTOP_APP_MAC_ARCH STREQUAL "arm64") - set(crashpad_dir_part ".${DESKTOP_APP_MAC_ARCH}") - else() - set(crashpad_dir_part "") - endif() - add_custom_command(TARGET Telegram - PRE_LINK - COMMAND mkdir -p $/../Helpers - COMMAND cp ${libs_loc}/crashpad/out/$,Debug,Release>${crashpad_dir_part}/crashpad_handler $/../Helpers/ - ) - endif() endif() else() target_link_libraries(Telegram @@ -1604,7 +1692,11 @@ if (build_macstore) COMMAND rm -rf $/../Frameworks/Breakpad.framework/Resources/Inspector ) else() - set(bundle_identifier "com.tdesktop.Telegram$<$:Debug>") + if (CMAKE_GENERATOR STREQUAL Xcode) + set(bundle_identifier "com.tdesktop.Telegram$<$:Debug>") + else() + set(bundle_identifier "com.tdesktop.Telegram") + endif() set(bundle_entitlements "Telegram.entitlements") if (LINUX AND DESKTOP_APP_USE_PACKAGED) set(output_name "telegram-desktop") @@ -1613,6 +1705,12 @@ else() endif() endif() +if (CMAKE_GENERATOR STREQUAL Xcode) + set(bundle_identifier_plist "$(PRODUCT_BUNDLE_IDENTIFIER)") +else() + set(bundle_identifier_plist ${bundle_identifier}) +endif() + set_target_properties(Telegram PROPERTIES OUTPUT_NAME ${output_name} MACOSX_BUNDLE_GUI_IDENTIFIER ${bundle_identifier} @@ -1657,6 +1755,10 @@ endif() set_target_properties(Telegram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder}) if (WIN32) + target_link_libraries(Telegram + PRIVATE + delayimp + ) target_link_options(Telegram PRIVATE /DELAYLOAD:secur32.dll @@ -1686,7 +1788,7 @@ endif() target_prepare_qrc(Telegram) -if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT build_winstore) +if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE AND NOT build_macstore AND NOT build_winstore) add_executable(Updater WIN32) init_non_host_target(Updater) @@ -1713,6 +1815,10 @@ if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT ) target_include_directories(Updater PRIVATE ${lib_base_loc}) if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + target_link_libraries(Updater + PRIVATE + delayimp + ) target_link_options(Updater PRIVATE /DELAYLOAD:user32.dll @@ -1724,6 +1830,12 @@ if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT else() target_link_options(Updater PRIVATE -municode) endif() + elseif (APPLE) + add_custom_command(TARGET Updater + PRE_LINK + COMMAND mkdir -p $/../Frameworks + COMMAND cp $ $/../Frameworks/ + ) endif() if (DESKTOP_APP_SPECIAL_TARGET) @@ -1777,6 +1889,7 @@ if (LINUX AND DESKTOP_APP_USE_PACKAGED) install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "telegram.png") install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "telegram.png") install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "telegram.png") + install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "telegram-symbolic.svg") install(FILES "../lib/xdg/org.telegram.desktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.service" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo") diff --git a/Telegram/Resources/animations/palette.tgs b/Telegram/Resources/animations/palette.tgs new file mode 100644 index 00000000000000..cc4887f9471607 Binary files /dev/null and b/Telegram/Resources/animations/palette.tgs differ diff --git a/Telegram/Resources/animations/stats.tgs b/Telegram/Resources/animations/stats.tgs new file mode 100644 index 00000000000000..4966041cefc459 Binary files /dev/null and b/Telegram/Resources/animations/stats.tgs differ diff --git a/Telegram/Resources/animations/voice_ttl_idle.tgs b/Telegram/Resources/animations/voice_ttl_idle.tgs new file mode 100644 index 00000000000000..01661129813aa5 Binary files /dev/null and b/Telegram/Resources/animations/voice_ttl_idle.tgs differ diff --git a/Telegram/Resources/animations/voice_ttl_start.tgs b/Telegram/Resources/animations/voice_ttl_start.tgs new file mode 100644 index 00000000000000..835556dd0b363c Binary files /dev/null and b/Telegram/Resources/animations/voice_ttl_start.tgs differ diff --git a/Telegram/Resources/art/ttl/video_message_icon.svg b/Telegram/Resources/art/ttl/video_message_icon.svg new file mode 100644 index 00000000000000..aeadc83852231c --- /dev/null +++ b/Telegram/Resources/art/ttl/video_message_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Telegram/Resources/art/winners.tgs b/Telegram/Resources/art/winners.tgs new file mode 100644 index 00000000000000..d1112ecbe732ed Binary files /dev/null and b/Telegram/Resources/art/winners.tgs differ diff --git a/Telegram/Resources/emoji/emoji_1.webp b/Telegram/Resources/emoji/emoji_1.webp index c4051383aeecb5..4bf92eca57f56a 100644 Binary files a/Telegram/Resources/emoji/emoji_1.webp and b/Telegram/Resources/emoji/emoji_1.webp differ diff --git a/Telegram/Resources/emoji/emoji_2.webp b/Telegram/Resources/emoji/emoji_2.webp index c707db8e9dad35..9889477938b48e 100644 Binary files a/Telegram/Resources/emoji/emoji_2.webp and b/Telegram/Resources/emoji/emoji_2.webp differ diff --git a/Telegram/Resources/emoji/emoji_3.webp b/Telegram/Resources/emoji/emoji_3.webp index d7eddeb5cbae9f..5e2172deb2a7af 100644 Binary files a/Telegram/Resources/emoji/emoji_3.webp and b/Telegram/Resources/emoji/emoji_3.webp differ diff --git a/Telegram/Resources/emoji/emoji_4.webp b/Telegram/Resources/emoji/emoji_4.webp index b668f059baa89e..4859c133715917 100644 Binary files a/Telegram/Resources/emoji/emoji_4.webp and b/Telegram/Resources/emoji/emoji_4.webp differ diff --git a/Telegram/Resources/emoji/emoji_5.webp b/Telegram/Resources/emoji/emoji_5.webp index 064a94d3f3c4b0..3539ebe03404c0 100644 Binary files a/Telegram/Resources/emoji/emoji_5.webp and b/Telegram/Resources/emoji/emoji_5.webp differ diff --git a/Telegram/Resources/emoji/emoji_6.webp b/Telegram/Resources/emoji/emoji_6.webp index fe7d47564924ab..8da39a6b1390f0 100644 Binary files a/Telegram/Resources/emoji/emoji_6.webp and b/Telegram/Resources/emoji/emoji_6.webp differ diff --git a/Telegram/Resources/emoji/emoji_7.webp b/Telegram/Resources/emoji/emoji_7.webp index 970a64c95b36f1..3c31b23452ce34 100644 Binary files a/Telegram/Resources/emoji/emoji_7.webp and b/Telegram/Resources/emoji/emoji_7.webp differ diff --git a/Telegram/Resources/emoji/emoji_8.webp b/Telegram/Resources/emoji/emoji_8.webp new file mode 100644 index 00000000000000..c3e6b3b0d461a9 Binary files /dev/null and b/Telegram/Resources/emoji/emoji_8.webp differ diff --git a/Telegram/Resources/icons/boosts/boost_mini2.png b/Telegram/Resources/icons/boosts/boost_mini2.png new file mode 100644 index 00000000000000..b4884ccc8219ee Binary files /dev/null and b/Telegram/Resources/icons/boosts/boost_mini2.png differ diff --git a/Telegram/Resources/icons/boosts/boost_mini2@2x.png b/Telegram/Resources/icons/boosts/boost_mini2@2x.png new file mode 100644 index 00000000000000..9fb6e01e999dea Binary files /dev/null and b/Telegram/Resources/icons/boosts/boost_mini2@2x.png differ diff --git a/Telegram/Resources/icons/boosts/boost_mini2@3x.png b/Telegram/Resources/icons/boosts/boost_mini2@3x.png new file mode 100644 index 00000000000000..16aff7dbe80ce2 Binary files /dev/null and b/Telegram/Resources/icons/boosts/boost_mini2@3x.png differ diff --git a/Telegram/Resources/icons/boosts/boost_unclaimed.png b/Telegram/Resources/icons/boosts/boost_unclaimed.png new file mode 100644 index 00000000000000..fb093176f775d8 Binary files /dev/null and b/Telegram/Resources/icons/boosts/boost_unclaimed.png differ diff --git a/Telegram/Resources/icons/boosts/boost_unclaimed@2x.png b/Telegram/Resources/icons/boosts/boost_unclaimed@2x.png new file mode 100644 index 00000000000000..30d5bd6bc708a0 Binary files /dev/null and b/Telegram/Resources/icons/boosts/boost_unclaimed@2x.png differ diff --git a/Telegram/Resources/icons/boosts/boost_unclaimed@3x.png b/Telegram/Resources/icons/boosts/boost_unclaimed@3x.png new file mode 100644 index 00000000000000..f63cca1c90887b Binary files /dev/null and b/Telegram/Resources/icons/boosts/boost_unclaimed@3x.png differ diff --git a/Telegram/Resources/icons/boosts/boost_unknown.png b/Telegram/Resources/icons/boosts/boost_unknown.png new file mode 100644 index 00000000000000..78f64285205068 Binary files /dev/null and b/Telegram/Resources/icons/boosts/boost_unknown.png differ diff --git a/Telegram/Resources/icons/boosts/boost_unknown@2x.png b/Telegram/Resources/icons/boosts/boost_unknown@2x.png new file mode 100644 index 00000000000000..ad22dc971e1d33 Binary files /dev/null and b/Telegram/Resources/icons/boosts/boost_unknown@2x.png differ diff --git a/Telegram/Resources/icons/boosts/boost_unknown@3x.png b/Telegram/Resources/icons/boosts/boost_unknown@3x.png new file mode 100644 index 00000000000000..bbf4532a27ff43 Binary files /dev/null and b/Telegram/Resources/icons/boosts/boost_unknown@3x.png differ diff --git a/Telegram/Resources/icons/boosts/filled_gift.png b/Telegram/Resources/icons/boosts/filled_gift.png new file mode 100644 index 00000000000000..123818f74b8d1b Binary files /dev/null and b/Telegram/Resources/icons/boosts/filled_gift.png differ diff --git a/Telegram/Resources/icons/boosts/filled_gift@2x.png b/Telegram/Resources/icons/boosts/filled_gift@2x.png new file mode 100644 index 00000000000000..13e5102ed47f9c Binary files /dev/null and b/Telegram/Resources/icons/boosts/filled_gift@2x.png differ diff --git a/Telegram/Resources/icons/boosts/filled_gift@3x.png b/Telegram/Resources/icons/boosts/filled_gift@3x.png new file mode 100644 index 00000000000000..7e3ad4e24fb933 Binary files /dev/null and b/Telegram/Resources/icons/boosts/filled_gift@3x.png differ diff --git a/Telegram/Resources/icons/boosts/mini_gift.png b/Telegram/Resources/icons/boosts/mini_gift.png new file mode 100644 index 00000000000000..1008e51240b088 Binary files /dev/null and b/Telegram/Resources/icons/boosts/mini_gift.png differ diff --git a/Telegram/Resources/icons/boosts/mini_gift@2x.png b/Telegram/Resources/icons/boosts/mini_gift@2x.png new file mode 100644 index 00000000000000..462d3876453503 Binary files /dev/null and b/Telegram/Resources/icons/boosts/mini_gift@2x.png differ diff --git a/Telegram/Resources/icons/boosts/mini_gift@3x.png b/Telegram/Resources/icons/boosts/mini_gift@3x.png new file mode 100644 index 00000000000000..8858b4fff5eadb Binary files /dev/null and b/Telegram/Resources/icons/boosts/mini_gift@3x.png differ diff --git a/Telegram/Resources/icons/boosts/mini_giveaway.png b/Telegram/Resources/icons/boosts/mini_giveaway.png new file mode 100644 index 00000000000000..79ffc0f1d7c46c Binary files /dev/null and b/Telegram/Resources/icons/boosts/mini_giveaway.png differ diff --git a/Telegram/Resources/icons/boosts/mini_giveaway@2x.png b/Telegram/Resources/icons/boosts/mini_giveaway@2x.png new file mode 100644 index 00000000000000..bb5e94a6567162 Binary files /dev/null and b/Telegram/Resources/icons/boosts/mini_giveaway@2x.png differ diff --git a/Telegram/Resources/icons/boosts/mini_giveaway@3x.png b/Telegram/Resources/icons/boosts/mini_giveaway@3x.png new file mode 100644 index 00000000000000..c9f85345256755 Binary files /dev/null and b/Telegram/Resources/icons/boosts/mini_giveaway@3x.png differ diff --git a/Telegram/Resources/icons/chat/audio_once.png b/Telegram/Resources/icons/chat/audio_once.png new file mode 100644 index 00000000000000..4d1f93652302cd Binary files /dev/null and b/Telegram/Resources/icons/chat/audio_once.png differ diff --git a/Telegram/Resources/icons/chat/audio_once@2x.png b/Telegram/Resources/icons/chat/audio_once@2x.png new file mode 100644 index 00000000000000..f66cfbbcb64460 Binary files /dev/null and b/Telegram/Resources/icons/chat/audio_once@2x.png differ diff --git a/Telegram/Resources/icons/chat/audio_once@3x.png b/Telegram/Resources/icons/chat/audio_once@3x.png new file mode 100644 index 00000000000000..bce2481c5534a9 Binary files /dev/null and b/Telegram/Resources/icons/chat/audio_once@3x.png differ diff --git a/Telegram/Resources/icons/chat/input_edit.png b/Telegram/Resources/icons/chat/input_edit.png index 28639bab229e3d..43282531346bdf 100644 Binary files a/Telegram/Resources/icons/chat/input_edit.png and b/Telegram/Resources/icons/chat/input_edit.png differ diff --git a/Telegram/Resources/icons/chat/input_edit@2x.png b/Telegram/Resources/icons/chat/input_edit@2x.png index 8e7bbf2eda83ff..70c031a4a54f42 100644 Binary files a/Telegram/Resources/icons/chat/input_edit@2x.png and b/Telegram/Resources/icons/chat/input_edit@2x.png differ diff --git a/Telegram/Resources/icons/chat/input_edit@3x.png b/Telegram/Resources/icons/chat/input_edit@3x.png index 7da15659a2f53f..fa6be01a577223 100644 Binary files a/Telegram/Resources/icons/chat/input_edit@3x.png and b/Telegram/Resources/icons/chat/input_edit@3x.png differ diff --git a/Telegram/Resources/icons/chat/input_forward.png b/Telegram/Resources/icons/chat/input_forward.png index c04e20ceb36fcc..2d9c7833543787 100644 Binary files a/Telegram/Resources/icons/chat/input_forward.png and b/Telegram/Resources/icons/chat/input_forward.png differ diff --git a/Telegram/Resources/icons/chat/input_forward@2x.png b/Telegram/Resources/icons/chat/input_forward@2x.png index c500804d00c95a..26e435458b38ec 100644 Binary files a/Telegram/Resources/icons/chat/input_forward@2x.png and b/Telegram/Resources/icons/chat/input_forward@2x.png differ diff --git a/Telegram/Resources/icons/chat/input_forward@3x.png b/Telegram/Resources/icons/chat/input_forward@3x.png index 9df3f2369c21a0..488f8e1e109bc5 100644 Binary files a/Telegram/Resources/icons/chat/input_forward@3x.png and b/Telegram/Resources/icons/chat/input_forward@3x.png differ diff --git a/Telegram/Resources/icons/chat/input_link_settings.png b/Telegram/Resources/icons/chat/input_link_settings.png new file mode 100644 index 00000000000000..bd01d4ad19da88 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_link_settings.png differ diff --git a/Telegram/Resources/icons/chat/input_link_settings@2x.png b/Telegram/Resources/icons/chat/input_link_settings@2x.png new file mode 100644 index 00000000000000..0eb12d2033a212 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_link_settings@2x.png differ diff --git a/Telegram/Resources/icons/chat/input_link_settings@3x.png b/Telegram/Resources/icons/chat/input_link_settings@3x.png new file mode 100644 index 00000000000000..851858fafd273a Binary files /dev/null and b/Telegram/Resources/icons/chat/input_link_settings@3x.png differ diff --git a/Telegram/Resources/icons/chat/input_reply.png b/Telegram/Resources/icons/chat/input_reply.png deleted file mode 100644 index f20b997732252f..00000000000000 Binary files a/Telegram/Resources/icons/chat/input_reply.png and /dev/null differ diff --git a/Telegram/Resources/icons/chat/input_reply@2x.png b/Telegram/Resources/icons/chat/input_reply@2x.png deleted file mode 100644 index e44f5f79eb0d62..00000000000000 Binary files a/Telegram/Resources/icons/chat/input_reply@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/chat/input_reply@3x.png b/Telegram/Resources/icons/chat/input_reply@3x.png deleted file mode 100644 index 97688789f3449d..00000000000000 Binary files a/Telegram/Resources/icons/chat/input_reply@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/chat/input_reply_quote.png b/Telegram/Resources/icons/chat/input_reply_quote.png new file mode 100644 index 00000000000000..0f0ade5dcd2af8 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_reply_quote.png differ diff --git a/Telegram/Resources/icons/chat/input_reply_quote@2x.png b/Telegram/Resources/icons/chat/input_reply_quote@2x.png new file mode 100644 index 00000000000000..c7bcb7d8eb746b Binary files /dev/null and b/Telegram/Resources/icons/chat/input_reply_quote@2x.png differ diff --git a/Telegram/Resources/icons/chat/input_reply_quote@3x.png b/Telegram/Resources/icons/chat/input_reply_quote@3x.png new file mode 100644 index 00000000000000..f1bc500a1f7cc3 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_reply_quote@3x.png differ diff --git a/Telegram/Resources/icons/chat/input_reply_settings.png b/Telegram/Resources/icons/chat/input_reply_settings.png new file mode 100644 index 00000000000000..9b67e535c29a71 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_reply_settings.png differ diff --git a/Telegram/Resources/icons/chat/input_reply_settings@2x.png b/Telegram/Resources/icons/chat/input_reply_settings@2x.png new file mode 100644 index 00000000000000..2a4aef116e3608 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_reply_settings@2x.png differ diff --git a/Telegram/Resources/icons/chat/input_reply_settings@3x.png b/Telegram/Resources/icons/chat/input_reply_settings@3x.png new file mode 100644 index 00000000000000..e2f131d6aa4356 Binary files /dev/null and b/Telegram/Resources/icons/chat/input_reply_settings@3x.png differ diff --git a/Telegram/Resources/icons/chat/large_lockedchat.png b/Telegram/Resources/icons/chat/large_lockedchat.png new file mode 100644 index 00000000000000..812b705b30dc02 Binary files /dev/null and b/Telegram/Resources/icons/chat/large_lockedchat.png differ diff --git a/Telegram/Resources/icons/chat/large_lockedchat@2x.png b/Telegram/Resources/icons/chat/large_lockedchat@2x.png new file mode 100644 index 00000000000000..30bf73e9b7ead2 Binary files /dev/null and b/Telegram/Resources/icons/chat/large_lockedchat@2x.png differ diff --git a/Telegram/Resources/icons/chat/large_lockedchat@3x.png b/Telegram/Resources/icons/chat/large_lockedchat@3x.png new file mode 100644 index 00000000000000..a0174e129552ae Binary files /dev/null and b/Telegram/Resources/icons/chat/large_lockedchat@3x.png differ diff --git a/Telegram/Resources/icons/chat/link_photo_enlarge.png b/Telegram/Resources/icons/chat/link_photo_enlarge.png new file mode 100644 index 00000000000000..c4ba35289e0e7b Binary files /dev/null and b/Telegram/Resources/icons/chat/link_photo_enlarge.png differ diff --git a/Telegram/Resources/icons/chat/link_photo_enlarge@2x.png b/Telegram/Resources/icons/chat/link_photo_enlarge@2x.png new file mode 100644 index 00000000000000..02e1328ed30d4f Binary files /dev/null and b/Telegram/Resources/icons/chat/link_photo_enlarge@2x.png differ diff --git a/Telegram/Resources/icons/chat/link_photo_enlarge@3x.png b/Telegram/Resources/icons/chat/link_photo_enlarge@3x.png new file mode 100644 index 00000000000000..3dbc8aad427ac0 Binary files /dev/null and b/Telegram/Resources/icons/chat/link_photo_enlarge@3x.png differ diff --git a/Telegram/Resources/icons/chat/mini_copy.png b/Telegram/Resources/icons/chat/mini_copy.png new file mode 100644 index 00000000000000..05af01e49ab508 Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_copy.png differ diff --git a/Telegram/Resources/icons/chat/mini_copy@2x.png b/Telegram/Resources/icons/chat/mini_copy@2x.png new file mode 100644 index 00000000000000..69693d749154be Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_copy@2x.png differ diff --git a/Telegram/Resources/icons/chat/mini_copy@3x.png b/Telegram/Resources/icons/chat/mini_copy@3x.png new file mode 100644 index 00000000000000..809361d53bdeee Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_copy@3x.png differ diff --git a/Telegram/Resources/icons/chat/mini_lock.png b/Telegram/Resources/icons/chat/mini_lock.png new file mode 100644 index 00000000000000..a81e9beb5ec25e Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_lock.png differ diff --git a/Telegram/Resources/icons/chat/mini_lock@2x.png b/Telegram/Resources/icons/chat/mini_lock@2x.png new file mode 100644 index 00000000000000..e4936715f99a53 Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_lock@2x.png differ diff --git a/Telegram/Resources/icons/chat/mini_lock@3x.png b/Telegram/Resources/icons/chat/mini_lock@3x.png new file mode 100644 index 00000000000000..2fd8ee86a64f4a Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_lock@3x.png differ diff --git a/Telegram/Resources/icons/chat/mini_media_once.png b/Telegram/Resources/icons/chat/mini_media_once.png new file mode 100644 index 00000000000000..8984491b7ae14c Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_media_once.png differ diff --git a/Telegram/Resources/icons/chat/mini_media_once@2x.png b/Telegram/Resources/icons/chat/mini_media_once@2x.png new file mode 100644 index 00000000000000..f8514d3af5b634 Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_media_once@2x.png differ diff --git a/Telegram/Resources/icons/chat/mini_media_once@3x.png b/Telegram/Resources/icons/chat/mini_media_once@3x.png new file mode 100644 index 00000000000000..3900061be980c1 Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_media_once@3x.png differ diff --git a/Telegram/Resources/icons/chat/mini_quote.png b/Telegram/Resources/icons/chat/mini_quote.png new file mode 100644 index 00000000000000..7c021f6ef96e5b Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_quote.png differ diff --git a/Telegram/Resources/icons/chat/mini_quote@2x.png b/Telegram/Resources/icons/chat/mini_quote@2x.png new file mode 100644 index 00000000000000..f4a9b43f59f699 Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_quote@2x.png differ diff --git a/Telegram/Resources/icons/chat/mini_quote@3x.png b/Telegram/Resources/icons/chat/mini_quote@3x.png new file mode 100644 index 00000000000000..2c572bd02c85be Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_quote@3x.png differ diff --git a/Telegram/Resources/icons/chat/mini_subscribers.png b/Telegram/Resources/icons/chat/mini_subscribers.png new file mode 100644 index 00000000000000..d9cac3f6324409 Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_subscribers.png differ diff --git a/Telegram/Resources/icons/chat/mini_subscribers@2x.png b/Telegram/Resources/icons/chat/mini_subscribers@2x.png new file mode 100644 index 00000000000000..1e6a9f0540a5c7 Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_subscribers@2x.png differ diff --git a/Telegram/Resources/icons/chat/mini_subscribers@3x.png b/Telegram/Resources/icons/chat/mini_subscribers@3x.png new file mode 100644 index 00000000000000..1e4a68bc70291c Binary files /dev/null and b/Telegram/Resources/icons/chat/mini_subscribers@3x.png differ diff --git a/Telegram/Resources/icons/chat/reply_type_channel.png b/Telegram/Resources/icons/chat/reply_type_channel.png new file mode 100644 index 00000000000000..8aa9e90f8a9f51 Binary files /dev/null and b/Telegram/Resources/icons/chat/reply_type_channel.png differ diff --git a/Telegram/Resources/icons/chat/reply_type_channel@2x.png b/Telegram/Resources/icons/chat/reply_type_channel@2x.png new file mode 100644 index 00000000000000..cb8bb8c2bfca05 Binary files /dev/null and b/Telegram/Resources/icons/chat/reply_type_channel@2x.png differ diff --git a/Telegram/Resources/icons/chat/reply_type_channel@3x.png b/Telegram/Resources/icons/chat/reply_type_channel@3x.png new file mode 100644 index 00000000000000..e659c35ac1de29 Binary files /dev/null and b/Telegram/Resources/icons/chat/reply_type_channel@3x.png differ diff --git a/Telegram/Resources/icons/chat/reply_type_group.png b/Telegram/Resources/icons/chat/reply_type_group.png new file mode 100644 index 00000000000000..b40ba4b8140488 Binary files /dev/null and b/Telegram/Resources/icons/chat/reply_type_group.png differ diff --git a/Telegram/Resources/icons/chat/reply_type_group@2x.png b/Telegram/Resources/icons/chat/reply_type_group@2x.png new file mode 100644 index 00000000000000..8ad079fb38eaa2 Binary files /dev/null and b/Telegram/Resources/icons/chat/reply_type_group@2x.png differ diff --git a/Telegram/Resources/icons/chat/reply_type_group@3x.png b/Telegram/Resources/icons/chat/reply_type_group@3x.png new file mode 100644 index 00000000000000..11dd58c1923098 Binary files /dev/null and b/Telegram/Resources/icons/chat/reply_type_group@3x.png differ diff --git a/Telegram/Resources/icons/chat/reply_type_user.png b/Telegram/Resources/icons/chat/reply_type_user.png new file mode 100644 index 00000000000000..f2d6cf6fcd20a6 Binary files /dev/null and b/Telegram/Resources/icons/chat/reply_type_user.png differ diff --git a/Telegram/Resources/icons/chat/reply_type_user@2x.png b/Telegram/Resources/icons/chat/reply_type_user@2x.png new file mode 100644 index 00000000000000..8ef54da954090e Binary files /dev/null and b/Telegram/Resources/icons/chat/reply_type_user@2x.png differ diff --git a/Telegram/Resources/icons/chat/reply_type_user@3x.png b/Telegram/Resources/icons/chat/reply_type_user@3x.png new file mode 100644 index 00000000000000..67a947f36d87a6 Binary files /dev/null and b/Telegram/Resources/icons/chat/reply_type_user@3x.png differ diff --git a/Telegram/Resources/icons/dialogs/avatar_hidden.png b/Telegram/Resources/icons/dialogs/avatar_hidden.png new file mode 100644 index 00000000000000..e032148c8ad27d Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_hidden.png differ diff --git a/Telegram/Resources/icons/dialogs/avatar_hidden@2x.png b/Telegram/Resources/icons/dialogs/avatar_hidden@2x.png new file mode 100644 index 00000000000000..ef143054e1659b Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_hidden@2x.png differ diff --git a/Telegram/Resources/icons/dialogs/avatar_hidden@3x.png b/Telegram/Resources/icons/dialogs/avatar_hidden@3x.png new file mode 100644 index 00000000000000..f6ef6b733b92c3 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_hidden@3x.png differ diff --git a/Telegram/Resources/icons/dialogs/avatar_notes.png b/Telegram/Resources/icons/dialogs/avatar_notes.png new file mode 100644 index 00000000000000..740d389103c940 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_notes.png differ diff --git a/Telegram/Resources/icons/dialogs/avatar_notes@2x.png b/Telegram/Resources/icons/dialogs/avatar_notes@2x.png new file mode 100644 index 00000000000000..98dc7c89cfd385 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_notes@2x.png differ diff --git a/Telegram/Resources/icons/dialogs/avatar_notes@3x.png b/Telegram/Resources/icons/dialogs/avatar_notes@3x.png new file mode 100644 index 00000000000000..735961bd9030bb Binary files /dev/null and b/Telegram/Resources/icons/dialogs/avatar_notes@3x.png differ diff --git a/Telegram/Resources/icons/dialogs/mini_arrow.png b/Telegram/Resources/icons/dialogs/mini_arrow.png new file mode 100644 index 00000000000000..81b5076f827ef7 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/mini_arrow.png differ diff --git a/Telegram/Resources/icons/dialogs/mini_arrow@2x.png b/Telegram/Resources/icons/dialogs/mini_arrow@2x.png new file mode 100644 index 00000000000000..6e1e87682fb573 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/mini_arrow@2x.png differ diff --git a/Telegram/Resources/icons/dialogs/mini_arrow@3x.png b/Telegram/Resources/icons/dialogs/mini_arrow@3x.png new file mode 100644 index 00000000000000..36fd0e0c35be42 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/mini_arrow@3x.png differ diff --git a/Telegram/Resources/icons/dialogs/mini_tag_lock.png b/Telegram/Resources/icons/dialogs/mini_tag_lock.png new file mode 100644 index 00000000000000..cb1e4d76a2e838 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/mini_tag_lock.png differ diff --git a/Telegram/Resources/icons/dialogs/mini_tag_lock@2x.png b/Telegram/Resources/icons/dialogs/mini_tag_lock@2x.png new file mode 100644 index 00000000000000..cc875b7fa7ff9f Binary files /dev/null and b/Telegram/Resources/icons/dialogs/mini_tag_lock@2x.png differ diff --git a/Telegram/Resources/icons/dialogs/mini_tag_lock@3x.png b/Telegram/Resources/icons/dialogs/mini_tag_lock@3x.png new file mode 100644 index 00000000000000..68254340d43a20 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/mini_tag_lock@3x.png differ diff --git a/Telegram/Resources/icons/info/edit/stickers_add.png b/Telegram/Resources/icons/info/edit/stickers_add.png new file mode 100644 index 00000000000000..d4c77a202c4fe3 Binary files /dev/null and b/Telegram/Resources/icons/info/edit/stickers_add.png differ diff --git a/Telegram/Resources/icons/info/edit/stickers_add@2x.png b/Telegram/Resources/icons/info/edit/stickers_add@2x.png new file mode 100644 index 00000000000000..f188f440d5c882 Binary files /dev/null and b/Telegram/Resources/icons/info/edit/stickers_add@2x.png differ diff --git a/Telegram/Resources/icons/info/edit/stickers_add@3x.png b/Telegram/Resources/icons/info/edit/stickers_add@3x.png new file mode 100644 index 00000000000000..48de8115e32395 Binary files /dev/null and b/Telegram/Resources/icons/info/edit/stickers_add@3x.png differ diff --git a/Telegram/Resources/icons/info/info_media_saved.png b/Telegram/Resources/icons/info/info_media_saved.png new file mode 100644 index 00000000000000..0b3ebc5dde0a72 Binary files /dev/null and b/Telegram/Resources/icons/info/info_media_saved.png differ diff --git a/Telegram/Resources/icons/info/info_media_saved@2x.png b/Telegram/Resources/icons/info/info_media_saved@2x.png new file mode 100644 index 00000000000000..aa91e6fb81eb60 Binary files /dev/null and b/Telegram/Resources/icons/info/info_media_saved@2x.png differ diff --git a/Telegram/Resources/icons/info/info_media_saved@3x.png b/Telegram/Resources/icons/info/info_media_saved@3x.png new file mode 100644 index 00000000000000..6c9814fe12eae5 Binary files /dev/null and b/Telegram/Resources/icons/info/info_media_saved@3x.png differ diff --git a/Telegram/Resources/icons/limits/boost.png b/Telegram/Resources/icons/limits/boost.png new file mode 100644 index 00000000000000..b2c8bc7a06f0f9 Binary files /dev/null and b/Telegram/Resources/icons/limits/boost.png differ diff --git a/Telegram/Resources/icons/limits/boost@2x.png b/Telegram/Resources/icons/limits/boost@2x.png new file mode 100644 index 00000000000000..a4c54adcd31aad Binary files /dev/null and b/Telegram/Resources/icons/limits/boost@2x.png differ diff --git a/Telegram/Resources/icons/limits/boost@3x.png b/Telegram/Resources/icons/limits/boost@3x.png new file mode 100644 index 00000000000000..194195fdb67fa9 Binary files /dev/null and b/Telegram/Resources/icons/limits/boost@3x.png differ diff --git a/Telegram/Resources/icons/mediaview/mini_repost.png b/Telegram/Resources/icons/mediaview/mini_repost.png new file mode 100644 index 00000000000000..95e0e71ebc37d3 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/mini_repost.png differ diff --git a/Telegram/Resources/icons/mediaview/mini_repost@2x.png b/Telegram/Resources/icons/mediaview/mini_repost@2x.png new file mode 100644 index 00000000000000..47e5fd6af25218 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/mini_repost@2x.png differ diff --git a/Telegram/Resources/icons/mediaview/mini_repost@3x.png b/Telegram/Resources/icons/mediaview/mini_repost@3x.png new file mode 100644 index 00000000000000..3b73fe2d3bb403 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/mini_repost@3x.png differ diff --git a/Telegram/Resources/icons/mediaview/views.png b/Telegram/Resources/icons/mediaview/views.png new file mode 100644 index 00000000000000..e8496be337b3b5 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/views.png differ diff --git a/Telegram/Resources/icons/mediaview/views@2x.png b/Telegram/Resources/icons/mediaview/views@2x.png new file mode 100644 index 00000000000000..30b516900c2274 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/views@2x.png differ diff --git a/Telegram/Resources/icons/mediaview/views@3x.png b/Telegram/Resources/icons/mediaview/views@3x.png new file mode 100644 index 00000000000000..523f11cc8842d2 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/views@3x.png differ diff --git a/Telegram/Resources/icons/menu/boosts.png b/Telegram/Resources/icons/menu/boosts.png new file mode 100644 index 00000000000000..64a5ae3b9a3151 Binary files /dev/null and b/Telegram/Resources/icons/menu/boosts.png differ diff --git a/Telegram/Resources/icons/menu/boosts@2x.png b/Telegram/Resources/icons/menu/boosts@2x.png new file mode 100644 index 00000000000000..0478fa66047ac7 Binary files /dev/null and b/Telegram/Resources/icons/menu/boosts@2x.png differ diff --git a/Telegram/Resources/icons/menu/boosts@3x.png b/Telegram/Resources/icons/menu/boosts@3x.png new file mode 100644 index 00000000000000..33590248c93a60 Binary files /dev/null and b/Telegram/Resources/icons/menu/boosts@3x.png differ diff --git a/Telegram/Resources/icons/menu/forward.png b/Telegram/Resources/icons/menu/forward.png index abdda604583f11..4a50099f4a03d9 100644 Binary files a/Telegram/Resources/icons/menu/forward.png and b/Telegram/Resources/icons/menu/forward.png differ diff --git a/Telegram/Resources/icons/menu/forward@2x.png b/Telegram/Resources/icons/menu/forward@2x.png index 5f51d642da365b..c27765246f6109 100644 Binary files a/Telegram/Resources/icons/menu/forward@2x.png and b/Telegram/Resources/icons/menu/forward@2x.png differ diff --git a/Telegram/Resources/icons/menu/forward@3x.png b/Telegram/Resources/icons/menu/forward@3x.png index d89d37bc6ff81a..557b032eff6eef 100644 Binary files a/Telegram/Resources/icons/menu/forward@3x.png and b/Telegram/Resources/icons/menu/forward@3x.png differ diff --git a/Telegram/Resources/icons/menu/link_above.png b/Telegram/Resources/icons/menu/link_above.png new file mode 100644 index 00000000000000..1f8881dfebec24 Binary files /dev/null and b/Telegram/Resources/icons/menu/link_above.png differ diff --git a/Telegram/Resources/icons/menu/link_above@2x.png b/Telegram/Resources/icons/menu/link_above@2x.png new file mode 100644 index 00000000000000..61c53d18e377cb Binary files /dev/null and b/Telegram/Resources/icons/menu/link_above@2x.png differ diff --git a/Telegram/Resources/icons/menu/link_above@3x.png b/Telegram/Resources/icons/menu/link_above@3x.png new file mode 100644 index 00000000000000..be58d0df3a1f8e Binary files /dev/null and b/Telegram/Resources/icons/menu/link_above@3x.png differ diff --git a/Telegram/Resources/icons/menu/link_below.png b/Telegram/Resources/icons/menu/link_below.png new file mode 100644 index 00000000000000..315150fa660076 Binary files /dev/null and b/Telegram/Resources/icons/menu/link_below.png differ diff --git a/Telegram/Resources/icons/menu/link_below@2x.png b/Telegram/Resources/icons/menu/link_below@2x.png new file mode 100644 index 00000000000000..f30fe99b492a0f Binary files /dev/null and b/Telegram/Resources/icons/menu/link_below@2x.png differ diff --git a/Telegram/Resources/icons/menu/link_below@3x.png b/Telegram/Resources/icons/menu/link_below@3x.png new file mode 100644 index 00000000000000..39f6fbf1ac6f86 Binary files /dev/null and b/Telegram/Resources/icons/menu/link_below@3x.png differ diff --git a/Telegram/Resources/icons/menu/link_enlarge.png b/Telegram/Resources/icons/menu/link_enlarge.png new file mode 100644 index 00000000000000..4750b24d631881 Binary files /dev/null and b/Telegram/Resources/icons/menu/link_enlarge.png differ diff --git a/Telegram/Resources/icons/menu/link_enlarge@2x.png b/Telegram/Resources/icons/menu/link_enlarge@2x.png new file mode 100644 index 00000000000000..ff98f6dc4aac6b Binary files /dev/null and b/Telegram/Resources/icons/menu/link_enlarge@2x.png differ diff --git a/Telegram/Resources/icons/menu/link_enlarge@3x.png b/Telegram/Resources/icons/menu/link_enlarge@3x.png new file mode 100644 index 00000000000000..78b3f137be2fca Binary files /dev/null and b/Telegram/Resources/icons/menu/link_enlarge@3x.png differ diff --git a/Telegram/Resources/icons/menu/link_shrink.png b/Telegram/Resources/icons/menu/link_shrink.png new file mode 100644 index 00000000000000..48bf8f0350b84c Binary files /dev/null and b/Telegram/Resources/icons/menu/link_shrink.png differ diff --git a/Telegram/Resources/icons/menu/link_shrink@2x.png b/Telegram/Resources/icons/menu/link_shrink@2x.png new file mode 100644 index 00000000000000..289e1f69dce5d2 Binary files /dev/null and b/Telegram/Resources/icons/menu/link_shrink@2x.png differ diff --git a/Telegram/Resources/icons/menu/link_shrink@3x.png b/Telegram/Resources/icons/menu/link_shrink@3x.png new file mode 100644 index 00000000000000..dfb66a523c8929 Binary files /dev/null and b/Telegram/Resources/icons/menu/link_shrink@3x.png differ diff --git a/Telegram/Resources/icons/menu/mode_messages.png b/Telegram/Resources/icons/menu/mode_messages.png new file mode 100644 index 00000000000000..a1a15eea221df6 Binary files /dev/null and b/Telegram/Resources/icons/menu/mode_messages.png differ diff --git a/Telegram/Resources/icons/menu/mode_messages@2x.png b/Telegram/Resources/icons/menu/mode_messages@2x.png new file mode 100644 index 00000000000000..0218c696fb4448 Binary files /dev/null and b/Telegram/Resources/icons/menu/mode_messages@2x.png differ diff --git a/Telegram/Resources/icons/menu/mode_messages@3x.png b/Telegram/Resources/icons/menu/mode_messages@3x.png new file mode 100644 index 00000000000000..faffe713b46da2 Binary files /dev/null and b/Telegram/Resources/icons/menu/mode_messages@3x.png differ diff --git a/Telegram/Resources/icons/menu/mode_topics.png b/Telegram/Resources/icons/menu/mode_topics.png new file mode 100644 index 00000000000000..e64c31734e837c Binary files /dev/null and b/Telegram/Resources/icons/menu/mode_topics.png differ diff --git a/Telegram/Resources/icons/menu/mode_topics@2x.png b/Telegram/Resources/icons/menu/mode_topics@2x.png new file mode 100644 index 00000000000000..11eccab7098081 Binary files /dev/null and b/Telegram/Resources/icons/menu/mode_topics@2x.png differ diff --git a/Telegram/Resources/icons/menu/mode_topics@3x.png b/Telegram/Resources/icons/menu/mode_topics@3x.png new file mode 100644 index 00000000000000..e0b678367992e7 Binary files /dev/null and b/Telegram/Resources/icons/menu/mode_topics@3x.png differ diff --git a/Telegram/Resources/icons/menu/reply.png b/Telegram/Resources/icons/menu/reply.png index cc12969ec27a76..a40619d2e9b16d 100644 Binary files a/Telegram/Resources/icons/menu/reply.png and b/Telegram/Resources/icons/menu/reply.png differ diff --git a/Telegram/Resources/icons/menu/reply@2x.png b/Telegram/Resources/icons/menu/reply@2x.png index e4fe939f8cc990..8ea749d737087a 100644 Binary files a/Telegram/Resources/icons/menu/reply@2x.png and b/Telegram/Resources/icons/menu/reply@2x.png differ diff --git a/Telegram/Resources/icons/menu/reply@3x.png b/Telegram/Resources/icons/menu/reply@3x.png index e5825a5a949ea6..04c83d47f9aa6b 100644 Binary files a/Telegram/Resources/icons/menu/reply@3x.png and b/Telegram/Resources/icons/menu/reply@3x.png differ diff --git a/Telegram/Resources/icons/menu/stats.png b/Telegram/Resources/icons/menu/stats.png new file mode 100644 index 00000000000000..0bafaa3956155d Binary files /dev/null and b/Telegram/Resources/icons/menu/stats.png differ diff --git a/Telegram/Resources/icons/menu/stats@2x.png b/Telegram/Resources/icons/menu/stats@2x.png new file mode 100644 index 00000000000000..18db4e9ecdbd5e Binary files /dev/null and b/Telegram/Resources/icons/menu/stats@2x.png differ diff --git a/Telegram/Resources/icons/menu/stats@3x.png b/Telegram/Resources/icons/menu/stats@3x.png new file mode 100644 index 00000000000000..77bc3547df3d72 Binary files /dev/null and b/Telegram/Resources/icons/menu/stats@3x.png differ diff --git a/Telegram/Resources/icons/menu/tag_filter.png b/Telegram/Resources/icons/menu/tag_filter.png new file mode 100644 index 00000000000000..1bef612c5bbd27 Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_filter.png differ diff --git a/Telegram/Resources/icons/menu/tag_filter@2x.png b/Telegram/Resources/icons/menu/tag_filter@2x.png new file mode 100644 index 00000000000000..056113881671f1 Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_filter@2x.png differ diff --git a/Telegram/Resources/icons/menu/tag_filter@3x.png b/Telegram/Resources/icons/menu/tag_filter@3x.png new file mode 100644 index 00000000000000..e8661460e7c83e Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_filter@3x.png differ diff --git a/Telegram/Resources/icons/menu/tag_remove.png b/Telegram/Resources/icons/menu/tag_remove.png new file mode 100644 index 00000000000000..0a495b4efe5a63 Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_remove.png differ diff --git a/Telegram/Resources/icons/menu/tag_remove@2x.png b/Telegram/Resources/icons/menu/tag_remove@2x.png new file mode 100644 index 00000000000000..521ab6968f6f0a Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_remove@2x.png differ diff --git a/Telegram/Resources/icons/menu/tag_remove@3x.png b/Telegram/Resources/icons/menu/tag_remove@3x.png new file mode 100644 index 00000000000000..5ebfb2b9110f1f Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_remove@3x.png differ diff --git a/Telegram/Resources/icons/menu/tag_rename.png b/Telegram/Resources/icons/menu/tag_rename.png new file mode 100644 index 00000000000000..69040f6aaf7d18 Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_rename.png differ diff --git a/Telegram/Resources/icons/menu/tag_rename@2x.png b/Telegram/Resources/icons/menu/tag_rename@2x.png new file mode 100644 index 00000000000000..2eb0fb82f9d721 Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_rename@2x.png differ diff --git a/Telegram/Resources/icons/menu/tag_rename@3x.png b/Telegram/Resources/icons/menu/tag_rename@3x.png new file mode 100644 index 00000000000000..ec18a3f00dc7c1 Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_rename@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_color_names.png b/Telegram/Resources/icons/settings/premium/features/feature_color_names.png new file mode 100644 index 00000000000000..4acd54d82e04e4 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_color_names.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_color_names@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_color_names@2x.png new file mode 100644 index 00000000000000..f8dd406fe00186 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_color_names@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_color_names@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_color_names@3x.png new file mode 100644 index 00000000000000..1bd9d32ae02b1f Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_color_names@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_custombg.png b/Telegram/Resources/icons/settings/premium/features/feature_custombg.png new file mode 100644 index 00000000000000..234f294345dd56 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_custombg.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_custombg@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_custombg@2x.png new file mode 100644 index 00000000000000..0fb615748e9ee8 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_custombg@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_custombg@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_custombg@3x.png new file mode 100644 index 00000000000000..571d1379e708b8 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_custombg@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack.png b/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack.png new file mode 100644 index 00000000000000..327d069c7908e7 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack@2x.png new file mode 100644 index 00000000000000..076b0843321828 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack@3x.png new file mode 100644 index 00000000000000..4325db686d856c Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_links.png b/Telegram/Resources/icons/settings/premium/features/feature_links.png new file mode 100644 index 00000000000000..11c44d03f2d3a6 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_links.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_links2.png b/Telegram/Resources/icons/settings/premium/features/feature_links2.png new file mode 100644 index 00000000000000..8bfedd0f42cd95 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_links2.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_links2@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_links2@2x.png new file mode 100644 index 00000000000000..c89f732258dc24 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_links2@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_links2@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_links2@3x.png new file mode 100644 index 00000000000000..66c9d9d3a35770 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_links2@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_links@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_links@2x.png new file mode 100644 index 00000000000000..78ebcd3233ae88 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_links@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_links@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_links@3x.png new file mode 100644 index 00000000000000..80b73df03e932f Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_links@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_reactions.png b/Telegram/Resources/icons/settings/premium/features/feature_reactions.png new file mode 100644 index 00000000000000..efbe55f2a38f02 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_reactions.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_reactions@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_reactions@2x.png new file mode 100644 index 00000000000000..b7391712616468 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_reactions@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_reactions@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_reactions@3x.png new file mode 100644 index 00000000000000..c71f495befce24 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_reactions@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_status.png b/Telegram/Resources/icons/settings/premium/features/feature_status.png new file mode 100644 index 00000000000000..b7969a4600bec5 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_status.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_status@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_status@2x.png new file mode 100644 index 00000000000000..10f2a645ecb840 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_status@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_status@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_status@3x.png new file mode 100644 index 00000000000000..5f93a1e33ac734 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_status@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_stories.png b/Telegram/Resources/icons/settings/premium/features/feature_stories.png new file mode 100644 index 00000000000000..7420822b7533f3 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_stories.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_stories@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_stories@2x.png new file mode 100644 index 00000000000000..ec1d7e056d7855 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_stories@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_stories@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_stories@3x.png new file mode 100644 index 00000000000000..86e53f6013b9a6 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_stories@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_voice.png b/Telegram/Resources/icons/settings/premium/features/feature_voice.png new file mode 100644 index 00000000000000..3f8f0a9c362b76 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_voice.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_voice@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_voice@2x.png new file mode 100644 index 00000000000000..75ffbbf9d95a90 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_voice@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_voice@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_voice@3x.png new file mode 100644 index 00000000000000..1761533709ffa2 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_voice@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_wallpaper.png b/Telegram/Resources/icons/settings/premium/features/feature_wallpaper.png new file mode 100644 index 00000000000000..a88ef05cfdaa4d Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_wallpaper.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_wallpaper@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_wallpaper@2x.png new file mode 100644 index 00000000000000..ad2bc2ed51ec35 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_wallpaper@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_wallpaper@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_wallpaper@3x.png new file mode 100644 index 00000000000000..6e0c0765a91e5b Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_wallpaper@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_lastseen.png b/Telegram/Resources/icons/settings/premium/large_lastseen.png new file mode 100644 index 00000000000000..e47bd7935bc38b Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_lastseen.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_lastseen@2x.png b/Telegram/Resources/icons/settings/premium/large_lastseen@2x.png new file mode 100644 index 00000000000000..305b6a491966b1 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_lastseen@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_lastseen@3x.png b/Telegram/Resources/icons/settings/premium/large_lastseen@3x.png new file mode 100644 index 00000000000000..0cd1e12ab09dc2 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_lastseen@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_readtime.png b/Telegram/Resources/icons/settings/premium/large_readtime.png new file mode 100644 index 00000000000000..c06294a82e44e2 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_readtime.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_readtime@2x.png b/Telegram/Resources/icons/settings/premium/large_readtime@2x.png new file mode 100644 index 00000000000000..f30a355ee8d7aa Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_readtime@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_readtime@3x.png b/Telegram/Resources/icons/settings/premium/large_readtime@3x.png new file mode 100644 index 00000000000000..1e7d022a30e991 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_readtime@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/lastseen.png b/Telegram/Resources/icons/settings/premium/lastseen.png new file mode 100644 index 00000000000000..9299ed21638a2e Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/lastseen.png differ diff --git a/Telegram/Resources/icons/settings/premium/lastseen@2x.png b/Telegram/Resources/icons/settings/premium/lastseen@2x.png new file mode 100644 index 00000000000000..994a9071949b00 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/lastseen@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/lastseen@3x.png b/Telegram/Resources/icons/settings/premium/lastseen@3x.png new file mode 100644 index 00000000000000..42b3f18652aa2e Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/lastseen@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/privacy.png b/Telegram/Resources/icons/settings/premium/privacy.png new file mode 100644 index 00000000000000..9d2309768f3cf9 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/privacy.png differ diff --git a/Telegram/Resources/icons/settings/premium/privacy@2x.png b/Telegram/Resources/icons/settings/premium/privacy@2x.png new file mode 100644 index 00000000000000..f82d78947fa763 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/privacy@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/privacy@3x.png b/Telegram/Resources/icons/settings/premium/privacy@3x.png new file mode 100644 index 00000000000000..ce3c775438cd9b Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/privacy@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/tags.png b/Telegram/Resources/icons/settings/premium/tags.png new file mode 100644 index 00000000000000..9a761e7b08ea92 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/tags.png differ diff --git a/Telegram/Resources/icons/settings/premium/tags@2x.png b/Telegram/Resources/icons/settings/premium/tags@2x.png new file mode 100644 index 00000000000000..194aec6497c97a Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/tags@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/tags@3x.png b/Telegram/Resources/icons/settings/premium/tags@3x.png new file mode 100644 index 00000000000000..f6b8511cfecda1 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/tags@3x.png differ diff --git a/Telegram/Resources/icons/statistics/mini_stats_like.png b/Telegram/Resources/icons/statistics/mini_stats_like.png new file mode 100644 index 00000000000000..0f21e721332637 Binary files /dev/null and b/Telegram/Resources/icons/statistics/mini_stats_like.png differ diff --git a/Telegram/Resources/icons/statistics/mini_stats_like@2x.png b/Telegram/Resources/icons/statistics/mini_stats_like@2x.png new file mode 100644 index 00000000000000..1c72c0131745b3 Binary files /dev/null and b/Telegram/Resources/icons/statistics/mini_stats_like@2x.png differ diff --git a/Telegram/Resources/icons/statistics/mini_stats_like@3x.png b/Telegram/Resources/icons/statistics/mini_stats_like@3x.png new file mode 100644 index 00000000000000..5a02e90e0af9e9 Binary files /dev/null and b/Telegram/Resources/icons/statistics/mini_stats_like@3x.png differ diff --git a/Telegram/Resources/icons/statistics/mini_stats_share.png b/Telegram/Resources/icons/statistics/mini_stats_share.png new file mode 100644 index 00000000000000..17772dc3484d35 Binary files /dev/null and b/Telegram/Resources/icons/statistics/mini_stats_share.png differ diff --git a/Telegram/Resources/icons/statistics/mini_stats_share@2x.png b/Telegram/Resources/icons/statistics/mini_stats_share@2x.png new file mode 100644 index 00000000000000..d5785b282e2194 Binary files /dev/null and b/Telegram/Resources/icons/statistics/mini_stats_share@2x.png differ diff --git a/Telegram/Resources/icons/statistics/mini_stats_share@3x.png b/Telegram/Resources/icons/statistics/mini_stats_share@3x.png new file mode 100644 index 00000000000000..8a0e1a72d64583 Binary files /dev/null and b/Telegram/Resources/icons/statistics/mini_stats_share@3x.png differ diff --git a/Telegram/Resources/icons/stories/boost_mini.png b/Telegram/Resources/icons/stories/boost_mini.png new file mode 100644 index 00000000000000..90a24d8f10410e Binary files /dev/null and b/Telegram/Resources/icons/stories/boost_mini.png differ diff --git a/Telegram/Resources/icons/stories/boost_mini@2x.png b/Telegram/Resources/icons/stories/boost_mini@2x.png new file mode 100644 index 00000000000000..fd746a03ad095a Binary files /dev/null and b/Telegram/Resources/icons/stories/boost_mini@2x.png differ diff --git a/Telegram/Resources/icons/stories/boost_mini@3x.png b/Telegram/Resources/icons/stories/boost_mini@3x.png new file mode 100644 index 00000000000000..637a5716d5bbe0 Binary files /dev/null and b/Telegram/Resources/icons/stories/boost_mini@3x.png differ diff --git a/Telegram/Resources/icons/stories/boosts_mini.png b/Telegram/Resources/icons/stories/boosts_mini.png new file mode 100644 index 00000000000000..80c667ecc925ae Binary files /dev/null and b/Telegram/Resources/icons/stories/boosts_mini.png differ diff --git a/Telegram/Resources/icons/stories/boosts_mini@2x.png b/Telegram/Resources/icons/stories/boosts_mini@2x.png new file mode 100644 index 00000000000000..95a8063967eb63 Binary files /dev/null and b/Telegram/Resources/icons/stories/boosts_mini@2x.png differ diff --git a/Telegram/Resources/icons/stories/boosts_mini@3x.png b/Telegram/Resources/icons/stories/boosts_mini@3x.png new file mode 100644 index 00000000000000..423de820910c74 Binary files /dev/null and b/Telegram/Resources/icons/stories/boosts_mini@3x.png differ diff --git a/Telegram/Resources/icons/tray_monochrome.svg b/Telegram/Resources/icons/tray_monochrome.svg new file mode 100644 index 00000000000000..b960a4afd36b27 --- /dev/null +++ b/Telegram/Resources/icons/tray_monochrome.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/voice_lock/audio_once_bg.png b/Telegram/Resources/icons/voice_lock/audio_once_bg.png new file mode 100644 index 00000000000000..62778676ce6400 Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/audio_once_bg.png differ diff --git a/Telegram/Resources/icons/voice_lock/audio_once_bg@2x.png b/Telegram/Resources/icons/voice_lock/audio_once_bg@2x.png new file mode 100644 index 00000000000000..225d6af3b8da0d Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/audio_once_bg@2x.png differ diff --git a/Telegram/Resources/icons/voice_lock/audio_once_bg@3x.png b/Telegram/Resources/icons/voice_lock/audio_once_bg@3x.png new file mode 100644 index 00000000000000..1d82b2d6e73cd0 Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/audio_once_bg@3x.png differ diff --git a/Telegram/Resources/icons/voice_lock/audio_once_number.png b/Telegram/Resources/icons/voice_lock/audio_once_number.png new file mode 100644 index 00000000000000..61e22f105da97f Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/audio_once_number.png differ diff --git a/Telegram/Resources/icons/voice_lock/audio_once_number@2x.png b/Telegram/Resources/icons/voice_lock/audio_once_number@2x.png new file mode 100644 index 00000000000000..15047cb489de2c Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/audio_once_number@2x.png differ diff --git a/Telegram/Resources/icons/voice_lock/audio_once_number@3x.png b/Telegram/Resources/icons/voice_lock/audio_once_number@3x.png new file mode 100644 index 00000000000000..6e39511e17a9cc Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/audio_once_number@3x.png differ diff --git a/Telegram/Resources/icons/voice_lock/input_mic_s.png b/Telegram/Resources/icons/voice_lock/input_mic_s.png new file mode 100644 index 00000000000000..86c1146a5d5dd6 Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/input_mic_s.png differ diff --git a/Telegram/Resources/icons/voice_lock/input_mic_s@2x.png b/Telegram/Resources/icons/voice_lock/input_mic_s@2x.png new file mode 100644 index 00000000000000..e70fd6a669030d Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/input_mic_s@2x.png differ diff --git a/Telegram/Resources/icons/voice_lock/input_mic_s@3x.png b/Telegram/Resources/icons/voice_lock/input_mic_s@3x.png new file mode 100644 index 00000000000000..d7a4ad98140957 Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/input_mic_s@3x.png differ diff --git a/Telegram/Resources/icons/voice_lock/recorded_delete.png b/Telegram/Resources/icons/voice_lock/recorded_delete.png new file mode 100644 index 00000000000000..f204f8f2206338 Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/recorded_delete.png differ diff --git a/Telegram/Resources/icons/voice_lock/recorded_delete@2x.png b/Telegram/Resources/icons/voice_lock/recorded_delete@2x.png new file mode 100644 index 00000000000000..8bebe1a61bb735 Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/recorded_delete@2x.png differ diff --git a/Telegram/Resources/icons/voice_lock/recorded_delete@3x.png b/Telegram/Resources/icons/voice_lock/recorded_delete@3x.png new file mode 100644 index 00000000000000..db0067c534ed82 Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/recorded_delete@3x.png differ diff --git a/Telegram/Resources/icons/win_quit.png b/Telegram/Resources/icons/win_quit.png new file mode 100644 index 00000000000000..a823f133e81b47 Binary files /dev/null and b/Telegram/Resources/icons/win_quit.png differ diff --git a/Telegram/Resources/icons/win_quit@2x.png b/Telegram/Resources/icons/win_quit@2x.png new file mode 100644 index 00000000000000..569097eda9395b Binary files /dev/null and b/Telegram/Resources/icons/win_quit@2x.png differ diff --git a/Telegram/Resources/icons/win_quit@3x.png b/Telegram/Resources/icons/win_quit@3x.png new file mode 100644 index 00000000000000..c11e92bb2a5a99 Binary files /dev/null and b/Telegram/Resources/icons/win_quit@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d677c6b40ec57d..6369098637d0b2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -129,6 +129,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_reconnecting#other" = "Reconnect in {count} s..."; "lng_reconnecting_try_now" = "Try now"; +"lng_code_block_header_copy" = "copy"; + "lng_status_service_notifications" = "service notifications"; "lng_status_support" = "support"; "lng_status_bot" = "bot"; @@ -139,6 +141,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_status_last_week" = "last seen within a week"; "lng_status_last_month" = "last seen within a month"; "lng_status_lastseen_now" = "last seen just now"; +"lng_status_lastseen_when" = "when?"; "lng_status_lastseen_minutes#one" = "last seen {count} minute ago"; "lng_status_lastseen_minutes#other" = "last seen {count} minutes ago"; "lng_status_lastseen_hours#one" = "last seen {count} hour ago"; @@ -166,6 +169,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_remember" = "Remember this choice"; +"lng_lastseen_show_title" = "Show Your Last Seen"; +"lng_lastseen_show_about" = "To see **{user}'s** Last Seen time, either start\nshowing your own Last Seen time..."; +"lng_lastseen_show_button" = "Show My Last Seen"; +"lng_lastseen_or" = "or"; +"lng_lastseen_premium_title" = "Upgrade to Premium"; +"lng_lastseen_premium_about" = "Subscription will let you see **{user}'s** Last Seen\nstatus without showing yours."; +"lng_lastseen_premium_button" = "Subscribe to Telegram Premium"; +"lng_lastseen_shown_toast" = "Your last seen time is now visible."; + +"lng_readtime_show_title" = "Show Your Read Date"; +"lng_readtime_show_about" = "To see when **{user}** read the message,\neither start showing your own read time..."; +"lng_readtime_show_button" = "Show My Read Time"; +"lng_readtime_or" = "or"; +"lng_readtime_premium_title" = "Upgrade to Premium"; +"lng_readtime_premium_about" = "Subscription will let you see **{user}'s** read time\nwithout showing yours."; +"lng_readtime_premium_button" = "Subscribe to Telegram Premium"; +"lng_readtime_shown_toast" = "Your read times are now visible."; + "lng_channels_limit_title" = "Too Many Communities"; "lng_channels_limit1#one" = "You are a member of **{count}** groups and channels."; "lng_channels_limit1#other" = "You are a member of **{count}** groups and channels."; @@ -393,6 +414,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dlg_new_bot_name" = "Bot name"; "lng_no_chats" = "Your chats will be here"; "lng_no_chats_filter" = "No chats currently belong to this folder."; +"lng_no_saved_sublists" = "You can save messages from other chats here."; "lng_contacts_loading" = "Loading..."; "lng_contacts_not_found" = "No contacts found"; "lng_topics_not_found" = "No topics found."; @@ -402,6 +424,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dlg_search_from" = "From: {user}"; "lng_settings_save" = "Save"; +"lng_settings_apply" = "Apply"; "lng_username_title" = "Username"; "lng_username_description1" = "You can choose a username on Telegram. If you do, other people will be able to find you by this username and contact you without knowing your phone number."; @@ -410,7 +433,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_username_invalid" = "This username is invalid."; "lng_username_occupied" = "This username is already occupied."; "lng_username_too_short" = "This username is too short."; -"lng_username_purchase_available" = "Sorry, this link is occupied by someone. But it's available for purchase through\nofficial {link}."; +"lng_username_purchase_available" = "**This username is already taken.** However, it is currently available for purchase. {link}"; +"lng_username_purchase_available_link" = "Learn more..."; "lng_username_bad_symbols" = "Only a-z, 0-9, and underscores allowed."; "lng_username_available" = "This username is available."; "lng_username_not_found" = "User @{user} not found."; @@ -543,11 +567,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_workmode_tray" = "Show tray icon"; "lng_settings_workmode_window" = "Show taskbar icon"; "lng_settings_close_to_taskbar" = "Close to taskbar"; +"lng_settings_monochrome_icon" = "Use monochrome icon"; "lng_settings_window_system" = "Window title"; "lng_settings_title_chat_name" = "Show chat name"; "lng_settings_title_account_name" = "Show active account"; "lng_settings_title_total_count" = "Total unread count"; "lng_settings_native_frame" = "Use system window frame"; +"lng_settings_qt_frame" = "Use Qt window frame"; "lng_settings_auto_start" = "Launch Telegram when system starts"; "lng_settings_start_min" = "Launch minimized"; "lng_settings_auto_start_disabled_uwp" = "Starting with the system was disabled in Windows Settings.\n\nPlease enable Telegram Desktop in the Startup Apps Settings."; @@ -586,6 +612,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_section_background" = "Chat background"; "lng_settings_bg_from_gallery" = "Choose from gallery"; "lng_settings_bg_from_file" = "Choose from file"; +"lng_settings_bg_remove" = "Remove wallpaper"; "lng_settings_bg_theme_edit" = "Edit theme"; "lng_settings_bg_theme_create" = "Create new theme"; "lng_settings_bg_cloud_themes" = "Custom themes"; @@ -604,6 +631,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_call_accept_calls" = "Accept calls from this device"; "lng_settings_call_device_default" = "Same as the System"; +"lng_settings_section_devices" = "Speakers and Camera"; +"lng_settings_devices_calls" = "Calls and video chats"; +"lng_settings_devices_calls_same" = "Use the same devices for calls"; +"lng_settings_devices_inactive" = "Unavailable"; + "lng_settings_language" = "Language"; "lng_settings_default_scale" = "Default interface scale"; "lng_settings_connection_type" = "Connection type"; @@ -617,6 +649,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_phone_number_privacy" = "Phone number"; "lng_settings_forwards_privacy" = "Forwarded messages"; "lng_settings_profile_photo_privacy" = "Profile photo"; +"lng_settings_messages_privacy" = "Messages"; "lng_settings_voices_privacy" = "Voice messages"; "lng_settings_bio_privacy" = "Bio"; "lng_settings_privacy_premium" = "Only subscribers of {link} can restrict receiving voice messages."; @@ -769,11 +802,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_clear_payment_info_clear" = "Clear"; "lng_clear_payment_info_confirm" = "Delete your shipping info and instruct all payment providers to remove your saved credit cards? Note that Telegram never stores your credit card data."; +"lng_settings_theme_settings" = "Theme settings"; +"lng_settings_theme_name_color" = "Your name color"; "lng_settings_auto_night_mode" = "Auto-Night mode"; -"lng_settings_auto_night_enabled" = "Match the system settings"; +"lng_settings_auto_night_mode_off" = "Off"; +"lng_settings_auto_night_mode_on" = "System"; "lng_settings_auto_night_warning" = "You have enabled auto-night mode. If you want to change the dark mode settings, you'll need to disable it first."; "lng_settings_auto_night_disable" = "Disable"; +"lng_settings_color_title" = "Color preview"; +"lng_settings_color_reply" = "Reply to your message"; +"lng_settings_color_reply_channel" = "Reply to your channel message"; +"lng_settings_color_text" = "Your name and replies to your messages will be shown in the selected color."; +"lng_settings_color_text_channel" = "The name of the channel and replies to its messages will be shown in the selected color."; +"lng_settings_color_link_name" = "Telegram"; +"lng_settings_color_link_title" = "Link Preview"; +"lng_settings_color_link_description" = "Your selected color will also tint the link preview."; +"lng_settings_color_about" = "You can choose a color to tint your name, the links you send, and replies to your messages."; +"lng_settings_color_about_channel" = "You can choose a color to tint your channel's name, the links it sends, and replies to its messages."; +"lng_settings_color_emoji" = "Add icons to replies"; +"lng_settings_color_emoji_remove" = "Remove icon"; +"lng_settings_color_emoji_off" = "Off"; +"lng_settings_color_emoji_about" = "Make replies to your messages stand out by adding custom patterns to them."; +"lng_settings_color_emoji_about_channel" = "Make replies to your channel's messages stand out by adding custom patterns to them."; +"lng_settings_color_subscribe" = "Subscribe to {link} to choose a custom color for your name."; +"lng_settings_color_changed" = "Your name color has been updated!"; +"lng_settings_color_changed_channel" = "Your channel color has been updated!"; + "lng_suggest_hide_new_title" = "Hide new chats?"; "lng_suggest_hide_new_about" = "You are receiving lots of new chats from users who are not in your Contact List.\n\nDo you want to have such chats **automatically muted** and **archived**?"; "lng_suggest_hide_new_to_settings" = "Go to Settings"; @@ -786,6 +841,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_manage_enabled_dictionary" = "Dictionary is enabled"; "lng_settings_manage_remove_dictionary" = "Remove Dictionary"; +"lng_settings_gift_premium" = "Premium Gifting"; +"lng_settings_gift_premium_users_confirm" = "Proceed"; +"lng_settings_gift_premium_users_error#one" = "You can select maximum {count} user."; +"lng_settings_gift_premium_users_error#other" = "You can select maximum {count} users."; + "lng_backgrounds_header" = "Choose Wallpaper"; "lng_theme_sure_keep" = "Keep this theme?"; "lng_theme_reverting#one" = "Reverting to the old theme in {count} second."; @@ -801,18 +861,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_background_text1" = "Ah, you kids today with techno music! You should enjoy the classics, like Hasselhoff!"; "lng_background_text2" = "I can't even take you seriously right now."; "lng_background_bad_link" = "This background link appears to be invalid."; -"lng_background_apply" = "Apply"; "lng_background_share" = "Share"; "lng_background_link_copied" = "Link copied to clipboard"; "lng_background_blur" = "Blurred"; "lng_background_sure_delete" = "Are you sure you want to delete this background?"; "lng_background_other_info" = "{user} will be able to apply this wallpaper"; +"lng_background_other_channel" = "All subscribers will see this wallpaper"; +"lng_background_other_group" = "All members will see this wallpaper"; "lng_background_apply1" = "Apply the wallpaper in this chat."; "lng_background_apply2" = "Enjoy the view."; "lng_background_apply_button" = "Apply For This Chat"; "lng_background_dimming" = "Background dimming"; "lng_background_sure_reset_default" = "Are you sure you want to reset the wallpaper?"; "lng_background_reset_default" = "Reset"; +"lng_background_apply_me" = "Apply for me"; +"lng_background_apply_both" = "Apply for me and {user}"; +"lng_background_apply_channel" = "Apply For Channel"; +"lng_background_apply_group" = "Apply For Group"; "lng_download_path_ask" = "Ask download path for each file"; "lng_download_path" = "Download path"; @@ -1023,6 +1088,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_edit_privacy_contacts" = "My contacts"; "lng_edit_privacy_close_friends" = "Close friends"; "lng_edit_privacy_nobody" = "Nobody"; +"lng_edit_privacy_premium" = "Premium users"; "lng_edit_privacy_exceptions" = "Add exceptions"; "lng_edit_privacy_exceptions_count#one" = "{count} user"; @@ -1049,6 +1115,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_edit_privacy_lastseen_exceptions" = "These settings will override the values above."; "lng_edit_privacy_lastseen_always_title" = "Always share with"; "lng_edit_privacy_lastseen_never_title" = "Never share with"; +"lng_edit_lastseen_hide_read_time" = "Hide read time"; +"lng_edit_lastseen_hide_read_time_about" = "Hide the time when you read messages from people who can't see your last seen. If you turn this on, their read time will also be hidden from you. This setting does not affect group chats."; +"lng_edit_lastseen_subscribe" = "Subscribe to Telegram Premium"; +"lng_edit_lastseen_subscribe_about" = "If you subscribe to Premium, you will see other users' last seen and read time even if you hid yours from them (unless they specifically restricted it)."; "lng_edit_privacy_groups_title" = "Group invite settings"; "lng_edit_privacy_groups_header" = "Who can invite you to groups and channels"; @@ -1120,6 +1190,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_edit_privacy_voices_always_title" = "Always allow"; "lng_edit_privacy_voices_never_title" = "Never allow"; +"lng_messages_privacy_title" = "Messages"; +"lng_messages_privacy_subtitle" = "Who can send me messages?"; +"lng_messages_privacy_everyone" = "Everybody"; +"lng_messages_privacy_restricted" = "My Contacts and Premium Users"; +"lng_messages_privacy_about" = "You can restrict incoming messages to only contacts and Premium users."; +"lng_messages_privacy_premium_button" = "Subscribe to Telegram Premium"; +"lng_messages_privacy_premium_about" = "Subscribe now to change this setting and get access to other exclusive features of Telegram Premium."; +"lng_messages_privacy_premium" = "Only subscribers of {link} can select this option."; +"lng_messages_privacy_premium_link" = "Telegram Premium"; + "lng_self_destruct_title" = "Account self-destruction"; "lng_self_destruct_description" = "If you don't come online at least once within this period, your account will be deleted along with all groups, messages and contacts."; "lng_self_destruct_sessions_title" = "Session termination"; @@ -1141,6 +1221,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_mute_box_title" = "Mute notifications for..."; "lng_preview_loading" = "Getting Link Info..."; +"lng_preview_cant" = "Could not generate preview for this link."; "lng_profile_settings_section" = "Settings"; "lng_profile_bot_settings" = "Bot Settings"; @@ -1148,6 +1229,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_profile_bot_privacy" = "Bot Privacy Policy"; "lng_profile_common_groups#one" = "{count} group in common"; "lng_profile_common_groups#other" = "{count} groups in common"; +"lng_profile_similar_channels#one" = "{count} similar channel"; +"lng_profile_similar_channels#other" = "{count} similar channels"; +"lng_profile_saved_messages#one" = "{count} saved message"; +"lng_profile_saved_messages#other" = "{count} saved messages"; "lng_profile_participants_section" = "Members"; "lng_profile_subscribers_section" = "Subscribers"; "lng_profile_add_contact" = "Add Contact"; @@ -1182,6 +1267,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_profile_loading" = "Loading..."; "lng_profile_saved_stories#one" = "{count} saved story"; "lng_profile_saved_stories#other" = "{count} saved stories"; +"lng_profile_posts#one" = "{count} post"; +"lng_profile_posts#other" = "{count} posts"; "lng_profile_photos#one" = "{count} photo"; "lng_profile_photos#other" = "{count} photos"; "lng_profile_gifs#one" = "{count} GIF"; @@ -1318,6 +1405,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_manage_peer_reactions_none_about" = "Members of the group can't add any reactions to messages."; "lng_manage_peer_reactions_some_title" = "Only allow these reactions"; "lng_manage_peer_reactions_available" = "Available reactions"; +"lng_manage_peer_reactions_own" = "You can also {link} emoji packs and use them as reactions."; +"lng_manage_peer_reactions_own_link" = "create your own"; +"lng_manage_peer_reactions_level#one" = "Your channel needs to reach level **{count}** to use **{same_count}** custom reaction."; +"lng_manage_peer_reactions_level#other" = "Your channel needs to reach level **{count}** to use **{same_count}** custom reactions."; +"lng_manage_peer_reactions_boost" = "Boost your channel {link}."; +"lng_manage_peer_reactions_boost_link" = "here"; +"lng_manage_peer_reactions_limit" = "Channels can't have more custom reactions."; "lng_manage_peer_antispam" = "Aggressive Anti-Spam"; "lng_manage_peer_antispam_about" = "Telegram will filter more spam but may occasionally affect ordinary messages. You can report False Positives in Recent Actions."; @@ -1413,6 +1507,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_report_messages_none" = "Select Messages"; "lng_report_messages_count#one" = "Report {count} Message"; "lng_report_messages_count#other" = "Report {count} Messages"; +"lng_report_reaction" = "Report reaction"; +"lng_report_and_ban" = "Ban and report"; +"lng_report_reaction_title" = "Report reaction"; +"lng_report_reaction_about" = "Are you sure you want to report reactions from this user?"; +"lng_report_and_ban_button" = "Ban user"; "lng_report_details_about" = "Please enter any additional details relevant to your report."; "lng_report_details" = "Additional Details"; "lng_report_reason_spam" = "Spam"; @@ -1612,11 +1711,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_suggested_video_button" = "View Video"; "lng_action_attach_menu_bot_allowed" = "You allowed this bot to message you when you added it in the attachment menu."; "lng_action_webapp_bot_allowed" = "You allowed this bot to message you in his web-app."; -"lng_action_set_wallpaper_me" = "You set a new wallpaper for this chat"; -"lng_action_set_wallpaper" = "{user} set a new wallpaper for this chat"; +"lng_action_set_wallpaper_me" = "You set a new wallpaper for this chat."; +"lng_action_set_wallpaper" = "{user} set a new wallpaper for this chat."; +"lng_action_set_wallpaper_both_me" = "You set a new wallpaper for {user} and you."; "lng_action_set_wallpaper_button" = "View Wallpaper"; -"lng_action_set_same_wallpaper_me" = "You set the same wallpaper for this chat"; -"lng_action_set_same_wallpaper" = "{user} set the same wallpaper for this chat"; +"lng_action_set_wallpaper_remove" = "Remove"; +"lng_action_set_same_wallpaper_me" = "You set the same wallpaper for this chat."; +"lng_action_set_same_wallpaper" = "{user} set the same wallpaper for this chat."; "lng_action_topic_created_inside" = "Topic created"; "lng_action_topic_closed_inside" = "Topic closed"; "lng_action_topic_reopened_inside" = "Topic reopened"; @@ -1637,6 +1738,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_story_mention_button" = "View Story"; "lng_action_story_mention_me_unavailable" = "The story where you mentioned {user} is no longer available."; "lng_action_story_mention_unavailable" = "The story where {user} mentioned you is no longer available."; +"lng_action_giveaway_started_group" = "{from} just started a giveaway of Telegram Premium subscriptions to its members."; +"lng_action_giveaway_started" = "{from} just started a giveaway of Telegram Premium subscriptions to its followers."; +"lng_action_giveaway_results#one" = "{count} winner of the giveaway was randomly selected by Telegram and received private messages with giftcodes."; +"lng_action_giveaway_results#other" = "{count} winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes."; +"lng_action_giveaway_results_some" = "Some winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes."; +"lng_action_giveaway_results_none" = "No winners of the giveaway could be selected."; +"lng_action_boost_apply#one" = "{from} boosted the group"; +"lng_action_boost_apply#other" = "{from} boosted the group {count} times"; + +"lng_similar_channels_title" = "Similar channels"; +"lng_similar_channels_view_all" = "View all"; +"lng_similar_channels_more" = "More Channels"; +"lng_similar_channels_premium_all#one" = "Subscribe to {link} to unlock up to **{count}** similar channel."; +"lng_similar_channels_premium_all#other" = "Subscribe to {link} to unlock up to **{count}** similar channels."; +"lng_similar_channels_premium_all_link" = "Telegram Premium"; +"lng_similar_channels_show_more" = "Show more channels"; "lng_premium_gift_duration_months#one" = "for {count} month"; "lng_premium_gift_duration_months#other" = "for {count} months"; @@ -1649,6 +1766,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_ttl_video_received" = "{from} sent you a self-destructing video. Please view it on your mobile."; "lng_ttl_video_sent" = "You sent a self-destructing video."; "lng_ttl_video_expired" = "Video has expired"; +"lng_ttl_voice_sent" = "You sent a self-destructing voice messsage."; +"lng_ttl_voice_expired" = "Voice message expired"; +"lng_ttl_round_sent" = "You sent a self-destructing video message."; +"lng_ttl_round_expired" = "Round message expired"; +"lng_ttl_voice_tooltip_in" = "This voice message can only be played once."; +"lng_ttl_voice_tooltip_out" = "This message will disappear once **{user}** plays it once."; +"lng_ttl_voice_close_in" = "Delete and close"; +"lng_ttl_round_tooltip_in" = "This video message can only be played once."; +"lng_ttl_round_tooltip_out" = "This message will disappear once **{user}** plays it once."; "lng_profile_add_more_after_create" = "You will be able to add more members after you create the group."; "lng_profile_camera_title" = "Capture yourself"; @@ -1778,9 +1904,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_forwarded_hidden" = "The account was hidden by the user."; "lng_forwarded_imported" = "This message was imported from another app. It may not be real."; "lng_signed_author" = "Author: {user}"; -"lng_sponsored" = "sponsored"; -"lng_recommended" = "recommended"; +"lng_sponsored_message_title" = "Sponsored"; +"lng_recommended_message_title" = "Recommended"; "lng_edited" = "edited"; +"lng_commented" = "commented"; "lng_edited_date" = "Edited: {date}"; "lng_sent_date" = "Sent: {date}"; "lng_views_tooltip#one" = "Views: {count}"; @@ -1861,6 +1988,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_stickers" = "Group stickers"; "lng_group_stickers_description" = "You can choose a sticker set which will be available for every member while in the group chat."; "lng_group_stickers_add" = "Choose sticker set"; +"lng_group_emoji" = "Group emoji pack"; +"lng_group_emoji_description" = "Choose an emoji pack that will be available to all members within the group."; "lng_premium" = "Premium"; "lng_premium_free" = "Free"; @@ -1891,6 +2020,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_summary_subtitle_gift#other" = "{user} has gifted you a {count}-months subscription for Telegram Premium."; "lng_premium_summary_subtitle_gift_me#one" = "You gifted {user} a {count}-month subscription for Telegram Premium."; "lng_premium_summary_subtitle_gift_me#other" = "You gifted {user} a {count}-months subscription for Telegram Premium."; +"lng_premium_summary_subtitle_wallpapers" = "Wallpapers for Both Sides"; +"lng_premium_summary_about_wallpapers" = "Set custom wallpapers for you and your chat partner."; "lng_premium_summary_subtitle_stories" = "Upgraded Stories"; "lng_premium_summary_about_stories" = "Priority order, stealth mode, permanent views history and more."; "lng_premium_summary_subtitle_double_limits" = "Doubled Limits"; @@ -1907,6 +2038,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_summary_about_emoji_status" = "Add any of thousands emoji next to your name to display current activity."; "lng_premium_summary_subtitle_infinite_reactions" = "Infinite Reactions"; "lng_premium_summary_about_infinite_reactions" = "React with thousands of emoji — with multiple reactions per message."; +"lng_premium_summary_subtitle_tags_for_messages" = "Tags for Messages"; +"lng_premium_summary_about_tags_for_messages" = "Organize your Saved Messages with tags for quicker access."; +"lng_premium_summary_subtitle_last_seen" = "Last Seen Times"; +"lng_premium_summary_about_last_seen" = "View the last seen and read times of others even if you hide yours."; +"lng_premium_summary_subtitle_message_privacy" = "Message Privacy"; +"lng_premium_summary_about_message_privacy" = "Restrict people you don't know from sending you messages."; "lng_premium_summary_subtitle_premium_stickers" = "Premium Stickers"; "lng_premium_summary_about_premium_stickers" = "Exclusive enlarged stickers featuring additional effects, updated monthly."; "lng_premium_summary_subtitle_animated_emoji" = "Animated Emoji"; @@ -1998,6 +2135,336 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_gift_per" = "{cost} / month"; "lng_premium_gift_terms" = "You can review the list of features and terms of use for Telegram Premium {link}."; "lng_premium_gift_terms_link" = "here"; +"lng_premium_gifts_about_user1" = "Give **{user}** access to exclusive features."; +"lng_premium_gifts_about_user2" = "Give **{user}** and **{second_user}** access to exclusive features."; +"lng_premium_gifts_about_user3" = "Give **{user}**, **{second_user}** and **{name}** access to exclusive features."; +"lng_premium_gifts_about_user_more#one" = "Give **{user}**, **{second_user}**, **{name}** and **{count}** more friend access to exclusive features."; +"lng_premium_gifts_about_user_more#other" = "Give **{user}**, **{second_user}**, **{name}** and **{count}** more friends access to exclusive features."; +"lng_premium_gifts_about_reward#one" = "You will receive {emoji}**{count}** boost."; +"lng_premium_gifts_about_reward#other" = "You will receive {emoji}**{count}** boosts."; +"lng_premium_gifts_about_paid_title" = "Gifts Sent!"; +"lng_premium_gifts_about_paid1" = "**{user}** has been notified about the gifts you purchased."; +"lng_premium_gifts_about_paid2" = "**{user}** and **{second_user}** have been notified about the gifts you purchased."; +"lng_premium_gifts_about_paid3" = "**{user}**, **{second_user}** and **{name}** have been notified about the gifts you purchased."; +"lng_premium_gifts_about_paid_more#one" = "**{user}**, **{second_user}**, **{name}** and **{count}** other have been notified about the gifts you purchased."; +"lng_premium_gifts_about_paid_more#other" = "**{user}**, **{second_user}**, **{name}** and **{count}** others have been notified about the gifts you purchased."; +"lng_premium_gifts_about_paid_below#one" = "They now have access to additional features."; +"lng_premium_gifts_about_paid_below#other" = "They now have access to additional features."; +"lng_premium_gifts_summary_subtitle" = "What's Included"; +"lng_premium_gifts_terms" = "By gifting Telegram Premium, you agree to the Telegram {link} and {policy}."; +"lng_premium_gifts_terms_policy" = "Privacy Policy"; + +"lng_boost_channel_button" = "Boost Channel"; +"lng_boost_group_button" = "Boost Group"; +"lng_boost_again_button" = "Boost Again"; +"lng_boost_group_about" = "Boost your group to unlock additional\nappearance settings."; +"lng_boost_level#one" = "Level {count}"; +"lng_boost_level#other" = "Level {count}"; +"lng_boost_level_unlocks#one" = "Level {count} Unlocks:"; +"lng_boost_level_unlocks#other" = "Level {count} Unlocks:"; +"lng_boost_channel_title_first" = "Enable stories for channel"; +"lng_boost_channel_title_first_group" = "Enable stories for group"; +"lng_boost_channel_needs_unlock#one" = "{channel} needs **{count}** more boost to unlock new features."; +"lng_boost_channel_needs_unlock#other" = "{channel} needs **{count}** more boosts to unlock new features."; +//"lng_boost_channel_needs_first#one" = "{channel} needs **{count}** more boost to enable posting stories. Help make it possible!"; +//"lng_boost_channel_needs_first#other" = "{channel} needs **{count}** more boosts to enable posting stories. Help make it possible!"; +"lng_boost_channel_title_more" = "Help upgrade channel"; +"lng_boost_channel_title_more_group" = "Help upgrade group"; +//"lng_boost_channel_needs_more#one" = "{channel} needs **{count}** more boost to be able to {post}."; +//"lng_boost_channel_needs_more#other" = "{channel} needs **{count}** more boosts to be able to {post}."; +"lng_boost_channel_title_max" = "Maximum level reached"; +"lng_boost_channel_you_title" = "You boosted {channel}!"; +//"lng_boost_channel_you_first#one" = "This channel needs **{count}** more boost\nto enable stories."; +//"lng_boost_channel_you_first#other" = "This channel needs **{count}** more boosts\nto enable stories."; +//"lng_boost_channel_you_more#one" = "This channel needs **{count}** more boost\nto be able to {post}."; +//"lng_boost_channel_you_more#other" = "This channel needs **{count}** more boosts\nto be able to {post}."; +"lng_boost_channel_reached_first" = "This channel reached **Level 1** and can now post stories."; +"lng_boost_channel_reached_more#one" = "This channel reached **Level {count}** and can now {post}."; +"lng_boost_channel_reached_more#other" = "This channel reached **Level {count}** and can now {post}."; +//"lng_boost_channel_you_first_group#one" = "This group needs **{count}** more boost\nto enable stories."; +//"lng_boost_channel_you_first_group#other" = "This group needs **{count}** more boosts\nto enable stories."; +//"lng_boost_channel_you_more_group#one" = "This group needs **{count}** more boost\nto be able to {post}."; +//"lng_boost_channel_you_more_group#other" = "This group needs **{count}** more boosts\nto be able to {post}."; +"lng_boost_channel_reached_first_group" = "This group reached **Level 1** and can now post stories."; +"lng_boost_channel_reached_more_group#one" = "This group reached **Level {count}** and can now {post}."; +"lng_boost_channel_reached_more_group#other" = "This group reached **Level {count}** and can now {post}."; +"lng_boost_channel_post_stories#one" = "post **{count} story** per day"; +"lng_boost_channel_post_stories#other" = "post **{count} stories** per day"; +"lng_boost_channel_features" = "Your boosts will help {channel} to unlock new features."; +"lng_boost_group_lift_restrictions" = "Boost the group to remove messaging restrictions."; +"lng_boost_group_lift_restrictions_many#one" = "Boost the group **{count} times** to remove messaging restrictions."; +"lng_boost_group_lift_restrictions_many#other" = "Boost the group **{count} times** to remove messaging restrictions."; +"lng_boost_error_gifted_title" = "Can't boost with gifted Premium!"; +"lng_boost_error_gifted_text" = "Because your **Telegram Premium** subscription was gifted to you, you can't use it to boost channels."; +"lng_boost_error_gifted_text_group" = "Because your **Telegram Premium** subscription was gifted to you, you can't use it to boost groups."; +"lng_boost_need_more" = "More boosts needed"; +"lng_boost_need_more_text#one" = "To boost {channel}, gift **Telegram Premium** to a friend and get **{count}** boosts."; +"lng_boost_need_more_text#other" = "To boost {channel}, gift **Telegram Premium** to a friend and get **{count}** boosts."; +"lng_boost_need_more_again#one" = "To boost {channel} again, gift **Telegram Premium** to a friend and get **{count}** additional boost."; +"lng_boost_need_more_again#other" = "To boost {channel} again, gift **Telegram Premium** to a friend and get **{count}** additional boosts."; +"lng_boost_error_already_title" = "Already Boosted!"; +"lng_boost_error_already_text" = "You are already boosting this channel."; +"lng_boost_error_already_text_group" = "You are already boosting this group."; +"lng_boost_error_premium_title" = "Premium needed!"; +"lng_boost_error_premium_text_group" = "Only **Telegram Premium** subscribers can boost groups. Do you want to subscribe to **Telegram Premium**?"; +"lng_boost_error_premium_text" = "Only **Telegram Premium** subscribers can boost channels. Do you want to subscribe to **Telegram Premium**?"; +"lng_boost_error_premium_yes" = "Yes"; +"lng_boost_error_flood_title" = "Can't boost too often!"; +"lng_boost_error_flood_text_group" = "You can change the group you boost only once a day. Next time you can boost is in {left}."; +"lng_boost_error_flood_text" = "You can change the channel you boost only once a day. Next time you can boost is in {left}."; +"lng_boost_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?"; +"lng_boost_now_replace" = "Replace"; +"lng_boost_reassign_title" = "Reassign boost"; +"lng_boost_reassign_text" = "To boost {channel}, reassign a previous boost or {gift}."; +"lng_boost_reassign_gift#one" = "gift **Telegram Premium** to a friend to get **{count}** additional boost"; +"lng_boost_reassign_gift#other" = "gift **Telegram Premium** to a friend to get **{count}** additional boosts"; +"lng_boost_remove_title" = "Remove your boost from"; +"lng_boost_reassign_button" = "Reassign"; +"lng_boost_available_in" = "available in {duration}"; +"lng_boost_available_in_toast#one" = "Wait until the boost is available or get **{count}** more boost by gifting a **Telegram Premium** subscription."; +"lng_boost_available_in_toast#other" = "Wait until the boost is available or get **{count}** more boosts by gifting a **Telegram Premium** subscription."; +"lng_boost_reassign_done#one" = "{count} boost is reassigned from {channels}."; +"lng_boost_reassign_done#other" = "{count} boosts are reassigned from {channels}."; +"lng_boost_reassign_channels#one" = "{count} channel"; +"lng_boost_reassign_channels#other" = "{count} channels"; +"lng_boost_reassign_groups#one" = "{count} group"; +"lng_boost_reassign_groups#other" = "{count} groups"; +"lng_boost_reassign_mixed#one" = "{count} group or channel"; +"lng_boost_reassign_mixed#other" = "{count} groups and channels"; + +"lng_boost_channel_title_color" = "Enable colors"; +"lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color."; +"lng_boost_channel_needs_level_color#other" = "Your channel needs to reach **Level {count}** to change channel color."; + +"lng_boost_channel_title_wallpaper" = "Enable wallpapers"; +"lng_boost_channel_needs_level_wallpaper#one" = "Your channel needs to reach **Level {count}** to change channel wallpaper."; +"lng_boost_channel_needs_level_wallpaper#other" = "Your channel needs to reach **Level {count}** to change channel wallpaper."; +"lng_boost_group_needs_level_wallpaper#one" = "Your group needs to reach **Level {count}** to change group wallpaper."; +"lng_boost_group_needs_level_wallpaper#other" = "Your group needs to reach **Level {count}** to change group wallpaper."; + +"lng_boost_channel_title_status" = "Enable emoji status"; +"lng_boost_channel_needs_level_status#one" = "Your channel needs to reach **Level {count}** to set emoji status."; +"lng_boost_channel_needs_level_status#other" = "Your channel needs to reach **Level {count}** to set emoji status."; +"lng_boost_group_needs_level_status#one" = "Your group needs to reach **Level {count}** to set emoji status."; +"lng_boost_group_needs_level_status#other" = "Your group needs to reach **Level {count}** to set emoji status."; + +"lng_boost_channel_title_reactions" = "Custom reactions"; +"lng_boost_channel_needs_level_reactions#one" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as a reaction."; +"lng_boost_channel_needs_level_reactions#other" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as reactions."; + +"lng_boost_group_title_emoji" = "Enable emoji pack"; +"lng_boost_group_needs_level_emoji#one" = "Your group needs to reach **Level {count}** to set emoji pack."; +"lng_boost_group_needs_level_emoji#other" = "Your group needs to reach **Level {count}** to set emoji pack."; + +"lng_boost_channel_ask" = "Ask your **Premium** subscribers to boost your channel with this link:"; +"lng_boost_channel_ask_button" = "Copy Link"; +"lng_boost_channel_or" = "or"; +"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}"; +"lng_boost_channel_gifting_link" = "Get boosts >"; + +"lng_feature_stories#one" = "**{count}** Story Per Day"; +"lng_feature_stories#other" = "**{count}** Stories Per Day"; +"lng_feature_reactions#one" = "**{count}** Custom Reaction"; +"lng_feature_reactions#other" = "**{count}** Custom Reactions"; +"lng_feature_name_color_channel#one" = "**{count}** Channel Name Color"; +"lng_feature_name_color_channel#other" = "**{count}** Channel Name Colors"; +"lng_feature_link_style_channel#one" = "**{count}** Style for Links and Quotes"; +"lng_feature_link_style_channel#other" = "**{count}** Styles for Links and Quotes"; +"lng_feature_link_emoji" = "Custom Logo for Links and Quotes"; +"lng_feature_emoji_status" = "**1000+** Emoji Statuses"; +"lng_feature_backgrounds_channel#one" = "**{count}** Channel Background"; +"lng_feature_backgrounds_channel#other" = "**{count}** Channel Backgrounds"; +"lng_feature_custom_background_channel" = "Custom Channel Background"; +"lng_feature_backgrounds_group#one" = "**{count}** Group Background"; +"lng_feature_backgrounds_group#other" = "**{count}** Group Backgrounds"; +"lng_feature_custom_background_group" = "Custom Group Background"; +"lng_feature_custom_emoji_pack" = "Custom Emoji Pack"; +"lng_feature_transcribe" = "Voice-to-Text Conversion"; + +"lng_giveaway_new_title" = "Boosts via Gifts"; +"lng_giveaway_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers."; +"lng_giveaway_new_about_group" = "Get more boosts for your group by gifting Premium to your subscribers."; +"lng_giveaway_create_option" = "Create Giveaway"; +"lng_giveaway_create_subtitle" = "winners are chosen randomly"; +"lng_giveaway_award_option" = "Award Specific Users"; +"lng_giveaway_award_subtitle" = "Select recipients >"; +"lng_giveaway_award_chosen#one" = "{count} recipient >"; +"lng_giveaway_award_chosen#other" = "{count} recipients >"; +"lng_giveaway_quantity_title" = "Quantity of prizes"; +"lng_giveaway_quantity#one" = "{count} boost"; +"lng_giveaway_quantity#other" = "{count} boosts"; +"lng_giveaway_quantity_about" = "Choose how many Premium subscriptions to give away and boosts to receive."; +"lng_giveaway_channels_title" = "Channels included in the giveaway"; +"lng_giveaway_channels_this#one" = "this channel will receive {count} boost"; +"lng_giveaway_channels_this#other" = "this channel will receive {count} boosts"; +"lng_giveaway_channels_this_group#one" = "this group will receive {count} boost"; +"lng_giveaway_channels_this_group#other" = "this group will receive {count} boosts"; +"lng_giveaway_channels_add" = "Add Channel"; +"lng_giveaway_channels_about" = "Choose the channels the users need to join to take part in the giveaway."; +"lng_giveaway_users_title" = "Users eligible for the giveaway"; +"lng_giveaway_users_all" = "All subscribers"; +"lng_giveaway_users_all_group" = "All members"; +"lng_giveaway_users_from_all_countries" = "from all countries"; +"lng_giveaway_users_from_one_country" = "from {country}"; +"lng_giveaway_users_from_countries#one" = "from {count} country"; +"lng_giveaway_users_from_countries#other" = "from {count} countries"; +"lng_giveaway_users_new" = "Only new subscribers"; +"lng_giveaway_users_new_group" = "Only new members"; +"lng_giveaway_users_about" = "Choose if you want to limit the giveaway only to those who joined the channel after the giveaway started or to users from specific countries."; +"lng_giveaway_users_about_group" = "Choose if you want to limit the giveaway only to those who joined the group after the giveaway started or to members from specific countries."; +"lng_giveaway_start" = "Start Giveaway"; +"lng_giveaway_award" = "Gift Premium"; +"lng_giveaway_start_sure" = "Are you sure you want to start this prepaid giveaway now? This action cannot be undone."; +"lng_giveaway_date_title" = "Date when giveaway ends"; +"lng_giveaway_date" = "Date and Time"; +"lng_giveaway_date_about#one" = "Choose when {count} subscriber of your channel will be randomly selected to receive Telegram Premium."; +"lng_giveaway_date_about#other" = "Choose when {count} subscribers of your channel will be randomly selected to receive Telegram Premium."; +"lng_giveaway_date_about_group#one" = "Choose when {count} members of your group will be randomly selected to receive Telegram Premium."; +"lng_giveaway_date_about_group#other" = "Choose when {count} members of your group will be randomly selected to receive Telegram Premium."; +"lng_giveaway_duration_title#one" = "Duration of Premium subscription"; +"lng_giveaway_duration_title#other" = "Duration of Premium subscriptions"; +"lng_giveaway_duration_price" = "{price} x {amount}"; +"lng_giveaway_date_select" = "Select Date and Time"; +"lng_giveaway_date_confirm" = "Confirm"; +"lng_giveaway_recipients_save" = "Save Recipients"; +"lng_giveaway_recipients_deselect" = "Deselect All"; +"lng_giveaway_maximum_countries_error#one" = "You can select maximum {count} country."; +"lng_giveaway_maximum_countries_error#other" = "You can select maximum {count} countries."; +"lng_giveaway_maximum_channels_error#one" = "You can select maximum {count} channel."; +"lng_giveaway_maximum_channels_error#other" = "You can select maximum {count} channels."; +"lng_giveaway_maximum_users_error#one" = "You can select maximum {count} user."; +"lng_giveaway_maximum_users_error#other" = "You can select maximum {count} users."; +"lng_giveaway_channels_confirm_title" = "Channel is Private"; +"lng_giveaway_channels_confirm_about" = "Are you sure you want to add a private channel? Users won't be able to join it without an invite link."; +"lng_giveaway_additional_prizes" = "Additional prizes"; +"lng_giveaway_additional_about" = "Turn this on if you want to give the winners your own prizes in addition to Premium subscriptions."; +"lng_giveaway_additional_prizes_ph" = "Enter your prize"; +"lng_giveaway_prizes_just_premium#one" = "All prizes: **{count}** Telegram Premium subscription {duration}."; +"lng_giveaway_prizes_just_premium#other" = "All prizes: **{count}** Telegram Premium subscriptions {duration}."; +"lng_giveaway_prizes_additional#one" = "All prizes: **{count}** {prize} with Telegram Premium subscription {duration}."; +"lng_giveaway_prizes_additional#other" = "All prizes: **{count}** {prize} with Telegram Premium subscriptions {duration}."; +"lng_giveaway_show_winners" = "Show winners"; +"lng_giveaway_show_winners_about" = "Choose whether to make the list of winners public when the giveaway ends."; + +"lng_giveaway_created_title" = "Giveaway created"; +"lng_giveaway_created_body" = "Check your channels' {link} to see how this giveaway boosted your channel."; +"lng_giveaway_created_body_group" = "Check your groups' {link} to see how this giveaway boosted your group."; +"lng_giveaway_awarded_title" = "Premium subscriptions gifted"; +"lng_giveaway_awarded_body" = "Check your channels' {link} to see how gifts boosted your channel."; +"lng_giveaway_awarded_body_group" = "Check your groups' {link} to see how gifts boosted your group."; +"lng_giveaway_created_link" = "Statistics"; + +"lng_prize_title" = "Congratulations!"; +"lng_prize_about" = "You won a prize in a giveaway organized by {channel}."; +"lng_prize_duration" = "Your prize is a **Telegram Premium** subscription {duration}."; +"lng_prize_gift_about" = "You've received a gift from {channel}."; +"lng_prize_gift_duration" = "Your gift is a **Telegram Premium** subscription {duration}."; +"lng_prize_open" = "Open Gift Link"; +"lng_prize_unclaimed_title" = "Unclaimed Prize"; +"lng_prize_unclaimed_about" = "You have an unclaimed prize from a giveaway by {channel}."; +"lng_prize_unclaimed_duration" = "This prize is a **Telegram Premium** subscription {duration}."; + +"lng_prizes_title#one" = "Giveaway Prize"; +"lng_prizes_title#other" = "Giveaway Prizes"; +"lng_prizes_additional#one" = "**{count}** {prize}"; +"lng_prizes_additional#other" = "**{count}** {prize}"; +"lng_prizes_additional_with" = "with"; +"lng_prizes_about#one" = "**{count}** Telegram Premium Subscription {duration}."; +"lng_prizes_about#other" = "**{count}** Telegram Premium Subscriptions {duration}."; +"lng_prizes_participants" = "Participants"; +"lng_prizes_participants_all#one" = "All subscribers of the channel:"; +"lng_prizes_participants_all#other" = "All subscribers of the channels:"; +"lng_prizes_participants_all_group#one" = "All members of the group:"; +"lng_prizes_participants_all_group#other" = "All members of the groups:"; +"lng_prizes_participants_all_mixed#one" = "All members of the group:"; +"lng_prizes_participants_all_mixed#other" = "All members of the groups and channels:"; +"lng_prizes_participants_new#one" = "All users who joined the channel below after this date:"; +"lng_prizes_participants_new#other" = "All users who joined the channels below after this date:"; +"lng_prizes_participants_new_group#one" = "All users who joined the group below after this date:"; +"lng_prizes_participants_new_group#other" = "All users who joined the groups below after this date:"; +"lng_prizes_participants_new_mixed#one" = "All users who joined the group below after this date:"; +"lng_prizes_participants_new_mixed#other" = "All users who joined the groups and channels below after this date:"; +"lng_prizes_countries" = "from {countries}"; +"lng_prizes_countries_and_one" = "{countries}, {country}"; +"lng_prizes_countries_and_last" = "{countries} and {country}"; +"lng_prizes_date" = "Winners Selection Date"; +"lng_prizes_how_works" = "Learn more"; +"lng_prizes_how_title" = "About this giveaway"; +"lng_prizes_end_title" = "Giveaway ended"; +"lng_prizes_how_text" = "This giveaway is sponsored by {admins}."; +"lng_prizes_end_text" = "This giveaway was sponsored by {admins}."; +"lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its followers"; +"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers"; +"lng_prizes_admins_group#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its members"; +"lng_prizes_admins_group#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its members"; +"lng_prizes_additional_added#one" = "{channel} also included **{count} {prize}** in the prize. Admins of the channel are responsible for delivering this prize."; +"lng_prizes_additional_added#other" = "{channel} also included **{count} {prize}** in the prizes. Admins of the channel are responsible for delivering these prizes."; +"lng_prizes_additional_added_group#one" = "{channel} also included **{count} {prize}** in the prize. Admins of the group are responsible for delivering this prize."; +"lng_prizes_additional_added_group#other" = "{channel} also included **{count} {prize}** in the prizes. Admins of the group are responsible for delivering these prizes."; +"lng_prizes_how_when_finish" = "On {date}, Telegram will automatically select {winners}."; +"lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}."; +"lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link."; +"lng_prizes_end_activated#other" = "**{count}** of the winners already used their gift links."; +"lng_prizes_winners_all_of_one#one" = "{count} random subscriber of {channel}"; +"lng_prizes_winners_all_of_one#other" = "{count} random subscribers of {channel}"; +"lng_prizes_winners_all_of_one_group#one" = "{count} random member of {channel}"; +"lng_prizes_winners_all_of_one_group#other" = "{count} random members of {channel}"; +"lng_prizes_winners_all_of_many#one" = "{count} random subscriber of {channel} and other listed groups and channels"; +"lng_prizes_winners_all_of_many#other" = "{count} random subscribers of {channel} and other listed groups and channels"; +"lng_prizes_winners_all_of_many_group#one" = "{count} random member of {channel} and other listed groups and channels"; +"lng_prizes_winners_all_of_many_group#other" = "{count} random members of {channel} and other listed groups and channels"; +"lng_prizes_winners_new_of_one#one" = "{count} random user that joined {channel} after {start_date}"; +"lng_prizes_winners_new_of_one#other" = "{count} random users that joined {channel} after {start_date}"; +"lng_prizes_winners_new_of_many#one" = "{count} random user that joined {channel} and other listed groups and channels after {start_date}"; +"lng_prizes_winners_new_of_many#other" = "{count} random users that joined {channel} and other listed groups and channels after {start_date}"; +"lng_prizes_how_participate_one" = "To take part in this giveaway please join {channel} before {date}."; +"lng_prizes_how_participate_many" = "To take part in this giveaway please join {channel} and other listed groups and channels before {date}."; +"lng_prizes_how_no_admin" = "You are not eligible to participate in this giveaway, because you are an admin of participating channel ({channel})."; +"lng_prizes_how_no_admin_group" = "You are not eligible to participate in this giveaway, because you are an admin of participating group ({channel})."; +"lng_prizes_how_no_joined" = "You are not eligible to participate in this giveaway, because you joined this channel on {date}, which is before the contest started."; +"lng_prizes_how_no_joined_group" = "You are not eligible to participate in this giveaway, because you joined this group on {date}, which is before the contest started."; +"lng_prizes_how_no_country" = "You are not eligible to participate in this giveaway, because your country is not included in the terms of the giveaway."; +"lng_prizes_how_yes_joined_one" = "You are participating in this giveaway, because you have joined {channel}."; +"lng_prizes_how_yes_joined_many" = "You are participating in this giveaway, because you have joined {channel} (and other listed groups and channels)."; +"lng_prizes_you_won" = "You won a prize in this giveaway {cup}"; +"lng_prizes_view_prize" = "View my prize"; +"lng_prizes_you_didnt" = "You didn't win a prize in this giveaway."; +"lng_prizes_cancelled" = "The channel cancelled the prizes by reversing the payment for them."; +"lng_prizes_cancelled_group" = "The channel cancelled the prizes by reversing the payment for them."; +"lng_prizes_badge" = "x{amount}"; + +"lng_prizes_results_title" = "Winners Selected!"; +"lng_prizes_results_about#one" = "**{count}** winner of the {link} was randomly selected by Telegram."; +"lng_prizes_results_about#other" = "**{count}** winners of the {link} were randomly selected by Telegram."; +"lng_prizes_results_link" = "Giveaway"; +"lng_prizes_results_winners" = "Winners"; +"lng_prizes_results_more#one" = "and {count} more!"; +"lng_prizes_results_more#other" = "and {count} more!"; +"lng_prizes_results_all" = "All winners received gift links in private messages."; +"lng_prizes_results_some" = "Some winners couldn't be selected."; + +"lng_gift_link_title" = "Gift Link"; +"lng_gift_link_about" = "This link allows you to activate\na **Telegram Premium** subscription."; +"lng_gift_link_label_from" = "From"; +"lng_gift_link_label_to" = "To"; +"lng_gift_link_label_to_unclaimed" = "No recipient"; +"lng_gift_link_label_gift" = "Gift"; +"lng_gift_link_gift_premium" = "Telegram Premium {duration}"; +"lng_gift_link_label_reason" = "Reason"; +"lng_gift_link_reason_giveaway" = "Giveaway"; +"lng_gift_link_reason_unclaimed" = "Incomplete Giveaway"; +"lng_gift_link_reason_chosen" = "You were selected by the channel"; +"lng_gift_link_label_date" = "Date"; +"lng_gift_link_also_send" = "You can also {link} to a friend as a gift."; +"lng_gift_link_also_send_link" = "send this link"; +"lng_gift_link_use" = "Use Link"; +"lng_gift_link_used_title" = "Used Gift Link"; +"lng_gift_link_used_about" = "This link was used to activate\na **Telegram Premium** subscription."; +"lng_gift_link_used_footer" = "This link was used on {date}."; +"lng_gift_link_expired" = "Gift code link expired"; +"lng_gift_link_pending_about" = "This link allows {user} to activate\na **Telegram Premium** subscription."; +"lng_gift_link_pending_toast" = "Only the recipient can see the link."; +"lng_gift_link_pending_footer" = "This link hasn't been activated yet."; "lng_accounts_limit_title" = "Limit Reached"; "lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts."; @@ -2066,6 +2533,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stickers_remove_pack_confirm" = "Remove"; "lng_stickers_archive_pack" = "Archive Stickers"; "lng_stickers_has_been_archived" = "Sticker pack has been archived."; +"lng_emoji_group_set" = "Group emoji set"; +"lng_emoji_remove_group_set" = "Remove group emoji set?"; +"lng_emoji_group_from_your" = "Choose from your emoji"; +"lng_emoji_group_from_featured" = "Choose from trending emoji"; "lng_masks_archive_pack" = "Archive Masks"; "lng_masks_has_been_archived" = "Mask pack has been archived."; "lng_masks_installed" = "Mask pack has been installed."; @@ -2078,6 +2549,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_in_dlg_contact" = "Contact"; "lng_in_dlg_audio" = "Voice message"; "lng_in_dlg_video_message" = "Video message"; +"lng_in_dlg_voice_message_ttl" = "One-time Voice Message"; +"lng_in_dlg_video_message_ttl" = "One-time Video Message"; "lng_in_dlg_file" = "File"; "lng_in_dlg_sticker" = "Sticker"; "lng_in_dlg_sticker_emoji" = "{emoji} Sticker"; @@ -2143,6 +2616,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_broadcast_silent_ph" = "Silent broadcast..."; "lng_send_anonymous_ph" = "Send anonymously..."; "lng_story_reply_ph" = "Reply privately..."; +"lng_story_comment_ph" = "Comment story..."; "lng_send_text_no" = "Text not allowed."; "lng_send_text_no_about" = "The admins of this group only allow sending {types}."; "lng_send_text_type_and_last" = "{types} and {last}"; @@ -2165,6 +2639,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_record_listen_cancel_sure" = "Are you sure you want to discard your recorded voice message?"; "lng_record_lock_discard" = "Discard"; "lng_record_hold_tip" = "Please hold the mouse button pressed to record a voice message."; +"lng_record_once_first_tooltip" = "Click to set this message to **Play Once**."; +"lng_record_once_active_tooltip" = "The recipients will be able to listen to it only once."; "lng_will_be_notified" = "Members will be notified when you post"; "lng_wont_be_notified" = "Members will not be notified when you post"; "lng_willbe_history" = "Please select a chat to start messaging"; @@ -2178,6 +2654,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_saved_messages" = "Saved Messages"; "lng_saved_short" = "Save"; "lng_saved_forward_here" = "Forward messages here for quick access"; +"lng_saved_quote_here" = "Quote here to save"; +"lng_saved_open_chat" = "Open Chat"; +"lng_saved_open_channel" = "Open Channel"; +"lng_saved_open_group" = "Open Group"; +"lng_saved_about_hidden" = "Senders of this messages restricted to link their name when forwarding."; "lng_scheduled_messages" = "Scheduled Messages"; "lng_scheduled_messages_empty" = "No scheduled messages here yet..."; @@ -2204,6 +2685,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_comments_open_none" = "Leave a comment"; "lng_replies_view_original" = "View in chat"; "lng_replies_messages" = "Replies"; +"lng_hidden_author_messages" = "Author Hidden"; +"lng_my_notes" = "My Notes"; "lng_replies_discussion_started" = "Discussion started"; "lng_replies_no_comments" = "No comments here yet..."; @@ -2260,17 +2743,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_remove_from_menu" = "Remove From Menu"; "lng_bot_remove_from_menu_sure" = "Remove {bot} from the attachment menu?"; "lng_bot_remove_from_menu_done" = "Bot removed from the menu."; +"lng_bot_remove_from_side_menu" = "Remove From Menu"; +"lng_bot_remove_from_side_menu_sure" = "Remove {bot} from the main menu?"; +"lng_bot_remove_from_side_menu_done" = "Bot removed from the main menu."; "lng_bot_settings" = "Settings"; "lng_bot_open" = "Open Bot"; "lng_bot_reload_page" = "Reload Page"; "lng_bot_add_to_menu" = "{bot} asks your permission to be added as an option to your attachments menu so you can access it from any chat."; "lng_bot_add_to_menu_done" = "Bot added to the menu."; +"lng_bot_will_be_added" = "{bot} shortcuts will be added to the attachment options and the main menu."; +"lng_bot_side_menu_new" = "NEW"; "lng_bot_menu_not_supported" = "This bot isn't supported in the attach menu."; "lng_bot_menu_already_added" = "This bot is already added in your attach menu."; "lng_bot_menu_button" = "Menu"; "lng_bot_close_warning_title" = "Warning"; "lng_bot_close_warning" = "Changes that you made may not be saved."; "lng_bot_close_warning_sure" = "Close anyway"; +"lng_bot_add_to_side_menu" = "{bot} asks your permission to be added as an option to your main menu so you can access it any time."; +"lng_bot_add_to_side_menu_done" = "Bot added to the main menu."; "lng_typing" = "typing"; "lng_user_typing" = "{user} is typing"; @@ -2359,6 +2849,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_copy_email" = "Copy Email Address"; "lng_context_copy_hashtag" = "Copy Hashtag"; "lng_context_copy_mention" = "Copy Username"; +"lng_context_copy_filename" = "Copy Filename"; "lng_context_save_image" = "Save Image As..."; "lng_context_copy_image" = "Copy Image"; "lng_context_cancel_download" = "Cancel Download"; @@ -2374,9 +2865,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_open_gif" = "Open GIF"; "lng_context_save_gif" = "Save GIF"; "lng_context_delete_gif" = "Delete GIF"; +"lng_context_open_channel" = "Open Channel"; +"lng_context_open_group" = "Open Group"; "lng_context_attached_stickers" = "Attached Stickers"; "lng_context_to_msg" = "Go To Message"; "lng_context_reply_msg" = "Reply"; +"lng_context_quote_and_reply" = "Quote & Reply"; "lng_context_edit_msg" = "Edit"; "lng_context_forward_msg" = "Forward Message"; "lng_context_send_now_msg" = "Send now"; @@ -2411,17 +2905,34 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_seen_reacted#other" = "{count} Reacted"; "lng_context_seen_reacted_none" = "Nobody Reacted"; "lng_context_seen_reacted_all" = "Show All Reactions"; -"lng_context_set_as_quick" = "Set As Quick"; +"lng_context_set_as_quick" = "Set as Quick"; +"lng_context_filter_by_tag" = "Filter by Tag"; +"lng_context_tag_add_name" = "Add Name"; +"lng_context_tag_edit_name" = "Edit Name"; +"lng_context_remove_tag" = "Remove Tag"; "lng_context_delete_from_disk" = "Delete from disk"; "lng_context_delete_all_files" = "Delete all files"; "lng_context_save_custom_sound" = "Save for notifications"; "lng_context_translate" = "Translate"; "lng_context_translate_selected" = "Translate Selected Text"; +"lng_context_read_hidden" = "read"; +"lng_context_read_show" = "show when"; + +"lng_add_tag_about" = "Tag this message with an emoji for quick search."; +"lng_subscribe_tag_about" = "Organize your Saved Messages with tags. {link}"; +"lng_subscribe_tag_link" = "Learn More..."; +"lng_edit_tag_about" = "You can label your emoji tag with a text name."; +"lng_edit_tag_name" = "Name"; +"lng_add_tag_button" = "Add tags"; +"lng_add_tag_phrase" = "to messages {arrow}"; +"lng_add_tag_phrase_long" = "to your Saved Messages {arrow}"; +"lng_unlock_tags" = "Unlock"; "lng_context_animated_emoji" = "This message contains emoji from **{name} pack**."; "lng_context_animated_emoji_many#one" = "This message contains emoji from **{count} pack**."; "lng_context_animated_emoji_many#other" = "This message contains emoji from **{count} packs**."; "lng_context_animated_reaction" = "This reaction is from **{name} pack**."; +"lng_context_animated_tag" = "This tag is from **{name} pack**."; "lng_context_animated_reactions" = "Reactions contain emoji from **{name} pack**."; "lng_context_animated_reactions_many#one" = "Reactions contain emoji from **{count} pack**."; "lng_context_animated_reactions_many#other" = "Reactions contain emoji from **{count} packs**."; @@ -2480,6 +2991,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_forwarding_from_two" = "{user} and {second_user}"; "lng_inline_switch_choose" = "Choose conversation..."; "lng_inline_switch_cant" = "Sorry, no way to write here :("; +"lng_preview_reply_to" = "Reply to {name}"; +"lng_preview_reply_to_quote" = "Reply to quote by {name}"; + +"lng_reply_in_another_title" = "Reply in..."; +"lng_reply_in_another_chat" = "Reply in Another Chat"; +"lng_reply_show_in_chat" = "Show in Chat"; +"lng_reply_remove" = "Do Not Reply"; +"lng_reply_about_quote" = "You can select specific part to quote."; +"lng_reply_options_header" = "Reply to Message"; +"lng_reply_options_quote" = "Update Quote"; +"lng_reply_header_short" = "Reply"; +"lng_reply_quote_selected" = "Quote Selected"; +"lng_reply_from_private_chat" = "This reply is from a private chat."; +"lng_link_options_header" = "Link Preview Settings"; +"lng_link_header_short" = "Link"; +"lng_link_move_up" = "Move Up"; +"lng_link_move_down" = "Move Down"; +"lng_link_shrink_photo" = "Shrink Photo"; +"lng_link_enlarge_photo" = "Enlarge Photo"; +"lng_link_remove" = "Do Not Preview"; +"lng_link_about_choose" = "Click on a link to generate its preview."; "lng_share_cant" = "Sorry, no way to share here :("; "lng_reply_cant" = "Sorry, no way to reply to an old message in supergroup :("; @@ -2491,6 +3023,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_share_wrong_user" = "This game was opened from a different user."; "lng_share_game_link_copied" = "Game link copied to clipboard."; "lng_share_done" = "Done!"; +"lng_share_message_to_saved_messages" = "Message forwarded to **Saved Messages**."; +"lng_share_messages_to_saved_messages" = "Messages forwarded to **Saved Messages**."; +"lng_share_message_to_chat" = "Message forwarded to **{chat}**."; +"lng_share_messages_to_chat" = "Messages forwarded to **{chat}**."; +"lng_share_message_to_two_chats" = "Message forwarded to **{user}** and **{chat}**."; +"lng_share_messages_to_two_chats" = "Messages forwarded to **{user}** and **{chat}**."; +"lng_share_message_to_many_chats#one" = "Message forwarded to **{count} chat**."; +"lng_share_message_to_many_chats#other" = "Message forwarded to **{count} chats**."; +"lng_share_messages_to_many_chats#one" = "Messages forwarded to **{count} chat**."; +"lng_share_messages_to_many_chats#other" = "Messages forwarded to **{count} chats**."; "lng_contact_phone" = "Phone Number"; "lng_enter_contact_data" = "New Contact"; @@ -2503,6 +3045,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_edit_bot_title" = "Edit bot"; "lng_edit_sign_messages" = "Sign messages"; "lng_edit_group" = "Edit group"; +"lng_edit_channel_color" = "Change name color"; +"lng_edit_channel_level_min" = "Level 1+"; +"lng_edit_channel_wallpaper" = "Channel wallpaper"; +"lng_edit_channel_wallpaper_about" = "Set a wallpaper that will be visible for everyone reading your channel."; +"lng_edit_channel_wallpaper_group" = "Group wallpaper"; +"lng_edit_channel_wallpaper_about_group" = "Set a wallpaper that will be visible for everyone participating in your group."; +"lng_edit_channel_status" = "Channel emoji status"; +"lng_edit_channel_status_about" = "Choose a status that will be shown next to the channel's name."; +"lng_edit_channel_status_group" = "Group emoji status"; +"lng_edit_channel_status_about_group" = "Choose a status that will be shown next to the group's name."; "lng_edit_self_title" = "Edit your name"; "lng_confirm_contact_data" = "New Contact"; "lng_add_contact" = "Create"; @@ -2631,6 +3183,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_menu_formatting_italic" = "Italic"; "lng_menu_formatting_underline" = "Underline"; "lng_menu_formatting_strike_out" = "Strike-through"; +"lng_menu_formatting_blockquote" = "Quote"; "lng_menu_formatting_monospace" = "Monospace"; "lng_menu_formatting_spoiler" = "Spoiler"; "lng_menu_formatting_link_create" = "Create link"; @@ -2643,6 +3196,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_formatting_link_create" = "Create"; "lng_text_copied" = "Text copied to clipboard."; +"lng_code_copied" = "Block copied to clipboard."; "lng_spellchecker_submenu" = "Spelling"; "lng_spellchecker_add" = "Add to Dictionary"; @@ -2751,6 +3305,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_payments_terms_title" = "Terms of Service"; "lng_payments_terms_text" = "Subscribe and accept terms of service of {bot}?"; +"lng_payments_terms_text_once" = "Are you accepting terms of service of {bot}?"; "lng_payments_terms_agree" = "I agree to {link}"; "lng_payments_terms_link" = "Terms of Service"; "lng_payments_terms_accept" = "Accept"; @@ -3019,7 +3574,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_rtmp_key_copy" = "Copy Stream Key"; "lng_group_call_rtmp_key_copied" = "Stream Key copied to clipboard."; "lng_group_call_rtmp_key_warning" = "**Never share your Stream Key with anyone or show it on stream!**"; -"lng_group_call_rtmp_info" = "To stream video with another app, enter these Server URL and Stream Key in your streaming app.\n\nOnce you start broadcasting in your streaming app, tap Start Streaming below."; +"lng_group_call_rtmp_info" = "To stream video with another app, enter these Server URL and Stream Key in your streaming app.\n\nOnce you start broadcasting in your streaming app, click Start Streaming below."; "lng_group_call_rtmp_start" = "Start Streaming"; "lng_group_call_rtmp_revoke" = "Revoke Stream Key"; "lng_group_call_rtmp_revoke_sure" = "Are you sure you want to revoke your Server URL and Stream Key?"; @@ -3035,6 +3590,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_audio_player_reverse" = "Reverse order"; "lng_audio_player_shuffle" = "Shuffle"; "lng_audio_transcribe_long" = "This voice message is too long."; +"lng_audio_transcribe_trials_left#one" = "You have {count} free transcription left until {date}."; +"lng_audio_transcribe_trials_left#other" = "You have {count} free transcriptions left until {date}."; +"lng_audio_transcribe_trials_over" = "You have used all your free transcriptions this week. Wait until {date} to use it again or subscribe to {link} now."; "lng_rights_edit_admin" = "Manage permissions"; "lng_rights_edit_admin_header" = "What can this admin do?"; @@ -3060,6 +3618,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_rights_slowmode_interval_seconds#other" = "every {count} seconds"; "lng_rights_slowmode_interval_minutes#one" = "every {count} minute"; "lng_rights_slowmode_interval_minutes#other" = "every {count} minutes"; +"lng_rights_boosts_no_restrict" = "Do not restrict boosters"; +"lng_rights_boosts_about" = "Turn this on to always allow users who boosted your group to send messages and media."; +"lng_rights_boosts_about_on" = "Choose how many boosts a user must give to the group to bypass restrictions on sending messages."; "lng_slowmode_enabled"= "Slow mode is enabled. You can send your next message in {left}."; "lng_slowmode_no_many" = "Slow mode is enabled. You can't send more than one message at a time."; @@ -3083,9 +3644,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gigagroup_suggest_more" = "Learn more"; "lng_rights_channel_info" = "Change channel info"; +"lng_rights_channel_manage" = "Manage messages"; "lng_rights_channel_post" = "Post messages"; "lng_rights_channel_edit" = "Edit messages of others"; "lng_rights_channel_delete" = "Delete messages of others"; +"lng_rights_channel_manage_stories" = "Manage stories"; +"lng_rights_channel_post_stories" = "Post stories"; +"lng_rights_channel_edit_stories" = "Edit stories of others"; +"lng_rights_channel_delete_stories" = "Delete stories of others"; "lng_rights_channel_manage_calls" = "Manage live streams"; "lng_rights_group_info" = "Change group info"; "lng_rights_group_ban" = "Ban users"; @@ -3150,6 +3716,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_restricted_send_gifs" = "The admins of this group restricted you from posting GIFs here."; "lng_restricted_send_inline" = "The admins of this group restricted you from posting inline content here."; "lng_restricted_send_polls" = "The admins of this group restricted you from posting polls here."; +"lng_restricted_boost_group" = "Boost this group to send messages"; "lng_restricted_send_message_until" = "The admins of this group restricted you from writing here until {date}, {time}."; "lng_restricted_send_photos_until" = "The admins of this group restricted you from posting photos here until {date}, {time}."; @@ -3179,6 +3746,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_restricted_send_voice_messages" = "{user} restricted sending of voice messages to them."; "lng_restricted_send_video_messages" = "{user} restricted sending of video messages to them."; +"lng_restricted_send_non_premium" = "Only Premium users can message {user}."; +"lng_restricted_send_non_premium_more" = "Learn more..."; + +"lng_send_non_premium_text" = "Subscribe to **Premium**\n to message {user}."; +"lng_send_non_premium_go" = "Get Premium"; +"lng_send_non_premium_story" = "Replies restricted"; +"lng_send_non_premium_unlock" = "unlock"; +"lng_send_non_premium_message_toast" = "**{user}** only accepts messages from contacts and {link} subscribers."; +"lng_send_non_premium_message_toast_link" = "Telegram Premium"; "lng_exceptions_list_title" = "Exceptions"; "lng_removed_list_title" = "Removed users"; @@ -3278,6 +3854,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_changed_stickers_group" = "{from} changed the group's {sticker_set}"; "lng_admin_log_changed_stickers_set" = "sticker set"; "lng_admin_log_removed_stickers_group" = "{from} removed the group's sticker set"; +"lng_admin_log_changed_emoji_group" = "{from} changed the group's {sticker_set}"; +"lng_admin_log_changed_emoji_set" = "emoji set"; +"lng_admin_log_removed_emoji_group" = "{from} removed the group's emoji set"; "lng_admin_log_changed_linked_chat" = "{from} changed the discussion group to «{chat}»"; "lng_admin_log_removed_linked_chat" = "{from} removed the discussion group"; "lng_admin_log_changed_linked_channel" = "{from} changed the linked channel to «{chat}»"; @@ -3302,6 +3881,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_participant_volume_channel" = "{from} changed live stream volume for {user} to {percent}"; "lng_admin_log_antispam_enabled" = "{from} enabled aggressive anti-spam"; "lng_admin_log_antispam_disabled" = "{from} disabled aggressive anti-spam"; +"lng_admin_log_change_color" = "{from} changed channel color from {previous} to {color}"; +"lng_admin_log_set_background_emoji" = "{from} set channel background emoji to {emoji}"; +"lng_admin_log_change_background_emoji" = "{from} changed channel background emoji from {previous} to {emoji}"; +"lng_admin_log_removed_background_emoji" = "{from} removed channel background emoji {emoji}"; +"lng_admin_log_change_profile_color" = "{from} changed channel profile color from {previous} to {color}"; +"lng_admin_log_set_profile_background_emoji" = "{from} set channel profile background emoji to {emoji}"; +"lng_admin_log_change_profile_background_emoji" = "{from} changed channel profile background emoji from {previous} to {emoji}"; +"lng_admin_log_removed_profile_background_emoji" = "{from} removed channel profile background emoji {emoji}"; +"lng_admin_log_change_wallpaper" = "{from} changed channel wallpaper"; +"lng_admin_log_set_status" = "{from} set channel emoji status to {emoji}"; +"lng_admin_log_change_status" = "{from} changed channel emoji status from {previous} to {emoji}"; +"lng_admin_log_removed_status" = "{from} removed channel emoji status {emoji}"; +"lng_admin_log_set_status_until" = "{from} set channel emoji status to {emoji} until {date}"; +"lng_admin_log_change_status_until" = "{from} changed channel emoji status from {previous} to {emoji} until {date}"; "lng_admin_log_user_with_username" = "{name} ({mention})"; "lng_admin_log_messages_ttl_set" = "{from} enabled messages auto-delete after {duration}"; "lng_admin_log_messages_ttl_changed" = "{from} changed messages auto-delete period from {previous} to {duration}"; @@ -3344,6 +3937,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_admin_post_messages" = "Post messages"; "lng_admin_log_admin_edit_messages" = "Edit messages"; "lng_admin_log_admin_delete_messages" = "Delete messages"; +"lng_admin_log_admin_post_stories" = "Post stories"; +"lng_admin_log_admin_edit_stories" = "Edit stories"; +"lng_admin_log_admin_delete_stories" = "Delete stories"; "lng_admin_log_admin_remain_anonymous" = "Remain anonymous"; "lng_admin_log_admin_ban_users" = "Ban users"; "lng_admin_log_admin_invite_users" = "Add members"; @@ -3824,16 +4420,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_view_button_voice_chat_channel" = "Live stream"; "lng_view_button_request_join" = "Request to Join"; "lng_view_button_external_link" = "Open link"; +"lng_view_button_boost" = "Boost"; +"lng_view_button_giftcode" = "Open"; "lng_sponsored_hide_ads" = "Hide"; "lng_sponsored_title" = "What are sponsored messages?"; -"lng_sponsored_info_description1" = "Unlike other apps, Telegram never uses your private data to target ads. Sponsored messages on Telegram are based solely on the topic of the public channels in which they are shown. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored messages.\n\nUnlike other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can’t spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers a free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:"; +"lng_sponsored_info_description1" = "Unlike other apps, Telegram never uses your private data to target ads. Sponsored messages on Telegram are based solely on the topic of the public channels in which they are shown. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored messages.\n\nUnlike other apps, Telegram doesn't track whether you clicked on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can’t spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers a free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:"; "lng_sponsored_info_description2" = "Sponsored Messages are currently in test mode. Once they are fully launched and allow Telegram to cover its basic costs, we will start sharing ad revenue with the owners of public channels in which sponsored messages are displayed.\n\nOnline ads should no longer be synonymous with abuse of user privacy. Let us redefine how a tech company should operate – together."; "lng_sponsored_info_menu" = "About this ad"; "lng_sponsored_info_submenu" = "Advertiser: {text}"; "lng_telegram_features_url" = "https://t.me/TelegramTips"; +"lng_mini_apps_disclaimer_title" = "Warning"; +"lng_mini_apps_disclaimer_text" = "You are about to use a mini app operated by an independent party **not affiliated with Telegram**. You must agree to the Terms of Use of mini apps to continue."; +"lng_mini_apps_disclaimer_button" = "I agree to the {link}"; +"lng_mini_apps_disclaimer_link" = "Terms of Use"; +"lng_mini_apps_tos_url" = "https://telegram.org/tos/mini-apps"; + "lng_ringtones_box_title" = "Notification Sound"; "lng_ringtones_box_cloud_subtitle" = "Choose your tone"; "lng_ringtones_box_upload_choose" = "Choose ringtone"; @@ -3863,10 +4467,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_forum_topics_no_discussion" = "Topics can't be enabled in discussion groups at the moment."; "lng_forum_choose_title_and_icon" = "Choose title and icon for your topic"; "lng_forum_replies_only" = "You can reply to messages in topics."; +"lng_forum_message_in" = "Message in {topic}"; +"lng_forum_reply_in" = "Reply in {topic}"; "lng_forum_no_topics" = "No topics currently created in this forum."; "lng_forum_create_topic" = "Create topic"; "lng_forum_discard_sure" = "Are you sure you want to discard this topic?"; "lng_forum_view_as_messages" = "View as Messages"; +"lng_forum_view_as_topics" = "View as Topics"; "lng_forum_no_messages" = "No messages"; "lng_forum_messages#one" = "{count} message"; "lng_forum_messages#other" = "{count} messages"; @@ -3880,6 +4487,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_request_peer_confirm_rights" = "This will also add {bot} to {chat} with the following rights: {rights}."; "lng_request_peer_confirm_send" = "Send"; "lng_request_user_title" = "Choose User"; +"lng_request_users_title" = "Choose Users"; "lng_request_user_premium_yes" = "The user should have a Premium subscription."; "lng_request_user_premium_no" = "The user shouldn't have a Premium subscription."; "lng_request_user_no" = "No such users"; @@ -3931,6 +4539,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stories_views#one" = "{count} view"; "lng_stories_views#other" = "{count} views"; "lng_stories_no_views" = "No views"; +"lng_stories_view_reactions" = "View reactions"; "lng_stories_unsupported" = "This story is not supported\nby your version of Telegram."; "lng_stories_cant_reply" = "You can't reply to this story."; "lng_stories_about_silent" = "This video has no sound."; @@ -3951,6 +4560,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stories_recent_button" = "Recent Stories"; "lng_stories_archive_title" = "Stories Archive"; "lng_stories_archive_about" = "Only you can see archived stories unless you choose to save them to your profile."; +"lng_stories_channel_archive_about" = "Only admins of the channel can see archived stories unless they are saved to the channel page."; "lng_stories_reply_sent" = "Message Sent"; "lng_stories_hidden_to_contacts" = "Stories from {user} will now be shown in **Archived Chats**."; "lng_stories_shown_in_chats" = "Stories from {user} will now be shown in the **Chats List**."; @@ -3970,7 +4580,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stories_archive_done" = "This story is hidden from your profile."; "lng_stories_archive_done_many#one" = "{count} story is hidden from your profile."; "lng_stories_archive_done_many#other" = "{count} stories are hidden from your profile."; +"lng_stories_channel_save_sure" = "Do you want to save this story to the channel page?"; +"lng_stories_channel_save_sure_many#one" = "Do you want to save {count} story to the channel page?"; +"lng_stories_channel_save_sure_many#other" = "Do you want to save {count} stories to the channel page?"; +"lng_stories_channel_save_done" = "This story is saved to the channel page."; +"lng_stories_channel_save_done_many#one" = "{count} story is saved to the channel page."; +"lng_stories_channel_save_done_many#other" = "{count} stories are saved to the channel page."; +"lng_stories_channel_save_done_about" = "Saved stories can be viewed by others on the channel page until they are removed."; +"lng_stories_channel_archive_sure" = "Do you want to hide this story from the channel page?"; +"lng_stories_channel_archive_sure_many#one" = "Do you want to hide {count} story from the channel page?"; +"lng_stories_channel_archive_sure_many#other" = "Do you want to hide {count} stories from the channel page?"; +"lng_stories_channel_archive_done" = "This story is hidden from the channel page."; +"lng_stories_channel_archive_done_many#one" = "{count} story is hidden from the channel page."; +"lng_stories_channel_archive_done_many#other" = "{count} stories are hidden from the channel page."; "lng_stories_save_promo" = "Subscribe to {link} to download other people's unprotected stories to disk."; +"lng_stories_reaction_as_message" = "Send reaction as a private message"; "lng_stealth_mode_menu_item" = "Stealth Mode"; "lng_stealth_mode_title" = "Stealth Mode"; @@ -3992,6 +4616,114 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stories_link_invalid" = "This link is broken or has expired."; +"lng_stats_title" = "Statistics"; +"lng_stats_message_title" = "Message Statistic"; +"lng_stats_story_title" = "Story Statistic"; +"lng_stats_zoom_out" = "Zoom Out"; + +"lng_stats_day_month_year" = "{days_count} {month} {year}"; +"lng_stats_day_month" = "{days_count} {month}"; +"lng_stats_weekday_day_month_year" = "{day}, {days_count} {month} {year}"; +"lng_stats_weekday_day_month_time" = "{day}, {days_count} {month} {time}"; + +"lng_stats_overview_title" = "Overview"; +"lng_stats_overview_member_count" = "Followers"; +"lng_stats_overview_mean_view_count" = "Views Per Post"; +"lng_stats_overview_mean_share_count" = "Shared Per Post"; +"lng_stats_overview_mean_reactions_count" = "Reactions Per Post"; +"lng_stats_overview_mean_story_view_count" = "Views Per Story"; +"lng_stats_overview_mean_story_share_count" = "Shared Per Story"; +"lng_stats_overview_mean_story_reactions_count" = "Reactions Per Story"; +"lng_stats_overview_enabled_notifications" = "Enabled Notifications"; +"lng_stats_overview_messages" = "Messages"; +"lng_stats_overview_group_mean_view_count" = "Viewing Members"; +"lng_stats_overview_group_mean_post_count" = "Posting Members"; +"lng_stats_overview_message_private_shares" = "Private Shares"; +"lng_stats_overview_message_public_shares" = "Public Shares"; +"lng_stats_overview_message_views" = "Views"; +"lng_stats_overview_message_public_share#one" = "{count} public share"; +"lng_stats_overview_message_public_share#other" = "{count} public shares"; + +"lng_stats_members_title" = "Top members"; +"lng_stats_admins_title" = "Top admins"; +"lng_stats_inviters_title" = "Top inviters"; +"lng_stats_member_messages#one" = "{count} message"; +"lng_stats_member_messages#other" = "{count} messages"; +"lng_stats_member_characters#one" = "{count} symbol per message"; +"lng_stats_member_characters#other" = "{count} symbols per message"; +"lng_stats_member_deletions#one" = "{count} deletions"; +"lng_stats_member_deletions#other" = "{count} deletions"; +"lng_stats_member_bans#one" = "{count} ban"; +"lng_stats_member_bans#other" = "{count} bans"; +"lng_stats_member_restrictions#one" = "{count} restriction"; +"lng_stats_member_restrictions#other" = "{count} restrictions"; +"lng_stats_member_invitations#one" = "{count} invitation"; +"lng_stats_member_invitations#other" = "{count} invitations"; + +"lng_stats_recent_messages_title" = "Recent posts"; +"lng_stats_recent_messages_views#one" = "{count} view"; +"lng_stats_recent_messages_views#other" = "{count} views"; + +"lng_stats_loading" = "Loading stats..."; +"lng_stats_loading_subtext" = "Please wait a few moments while we generate your stats."; + +"lng_chart_title_member_count" = "Growth"; +"lng_chart_title_join" = "Followers"; +"lng_chart_title_mute" = "Notifications"; +"lng_chart_title_view_count_by_hour" = "Views by hours"; +"lng_chart_title_view_count_by_source" = "Views by source"; +"lng_chart_title_join_by_source" = "New followers by source"; +"lng_chart_title_language" = "Languages"; +"lng_chart_title_message_interaction" = "Interactions"; +"lng_chart_title_instant_view_interaction" = "IV Interactions"; +"lng_chart_title_reactions_by_emotion" = "Reactions"; +"lng_chart_title_story_interactions" = "Story Interactions"; +"lng_chart_title_story_reactions_by_emotion" = "Story reactions"; + +"lng_chart_title_group_join" = "Group members"; +"lng_chart_title_group_join_by_source" = "New members by source"; +"lng_chart_title_group_language" = "Members's primary language"; +"lng_chart_title_group_message_content" = "Messages"; +"lng_chart_title_group_action" = "Actions"; +"lng_chart_title_group_day" = "Views by hours"; +"lng_chart_title_group_week" = "Top days of week"; + +"lng_boosts_title" = "Boosts"; +"lng_boosts_level" = "Level"; +"lng_boosts_existing" = "Existing boosts"; +"lng_boosts_premium_audience" = "Premium subscribers"; +"lng_boosts_premium_members" = "Premium members"; +"lng_boosts_next_level" = "Boosts to level up"; +"lng_boosts_list_title#one" = "{count} Boost"; +"lng_boosts_list_title#other" = "{count} Boosts"; +"lng_boosts_list_subtext" = "Your channel is currently boosted by these users."; +"lng_boosts_list_subtext_group" = "Your group is currently boosted by these users."; +"lng_boosts_show_more_boosts#one" = "Show {count} More Boosts"; +"lng_boosts_show_more_boosts#other" = "Show {count} More Boosts"; +"lng_boosts_show_more_gifts#one" = "Show {count} More Boosts"; +"lng_boosts_show_more_gifts#other" = "Show {count} More Boosts"; +"lng_boosts_list_status" = "boost expires on {date}"; +"lng_boosts_link_title" = "Link for boosting"; +"lng_boosts_link_subtext" = "Share this link with your subscribers to get more boosts."; +"lng_boosts_link_subtext_group" = "Share this link with the members of your group to get more boosts."; +"lng_boosts_get_boosts" = "Get Boosts via Gifts"; +"lng_boosts_get_boosts_subtext" = "Get more boosts for your channel by gifting Telegram Premium to your subscribers."; +"lng_boosts_get_boosts_subtext_group" = "Get more boosts for your group by gifting Telegram Premium to the members."; +"lng_boosts_list_unclaimed" = "Unclaimed"; +"lng_boosts_list_pending" = "To be distributed"; +"lng_boosts_list_pending_about" = "The recipient will be selected when the giveaway ends."; +"lng_boosts_list_tab_gifts#one" = "{count} Gifts"; +"lng_boosts_list_tab_gifts#other" = "{count} Gifts"; + +"lng_boosts_prepaid_giveaway_title" = "Prepaid giveaways"; +"lng_boosts_prepaid_giveaway_single" = "Prepaid giveaway"; +"lng_boosts_prepaid_giveaway_quantity#one" = "{count} Telegram Premium"; +"lng_boosts_prepaid_giveaway_quantity#other" = "{count} Telegram Premium"; +"lng_boosts_prepaid_giveaway_moths#one" = "{count}-month subscriptions"; +"lng_boosts_prepaid_giveaway_moths#other" = "{count}-month subscriptions"; +"lng_boosts_prepaid_giveaway_status#one" = "{count} subscription {duration}"; +"lng_boosts_prepaid_giveaway_status#other" = "{count} subscriptions {duration}"; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/Resources/night-custom-base.tdesktop-theme b/Telegram/Resources/night-custom-base.tdesktop-theme index e651fa9cc0f83c..5fc6424ae95aff 100644 Binary files a/Telegram/Resources/night-custom-base.tdesktop-theme and b/Telegram/Resources/night-custom-base.tdesktop-theme differ diff --git a/Telegram/Resources/night-green.tdesktop-theme b/Telegram/Resources/night-green.tdesktop-theme index 1647e4654e71b3..a60d844be5fb5f 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 fcb8dc8d64a121..96f788d13efeb7 100644 Binary files a/Telegram/Resources/night.tdesktop-theme and b/Telegram/Resources/night.tdesktop-theme differ diff --git a/Telegram/Resources/qrc/emoji_8.qrc b/Telegram/Resources/qrc/emoji_8.qrc new file mode 100644 index 00000000000000..51765a39b62424 --- /dev/null +++ b/Telegram/Resources/qrc/emoji_8.qrc @@ -0,0 +1,5 @@ + + + ../emoji/emoji_8.webp + + diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc index 28b0c58cc654ae..a129237ca0b3d7 100644 --- a/Telegram/Resources/qrc/telegram/animations.qrc +++ b/Telegram/Resources/qrc/telegram/animations.qrc @@ -10,5 +10,9 @@ ../../animations/cloud_password/email.tgs ../../animations/ttl.tgs ../../animations/discussion.tgs + ../../animations/stats.tgs + ../../animations/voice_ttl_idle.tgs + ../../animations/voice_ttl_start.tgs + ../../animations/palette.tgs diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc index 8eac62c66a5d0f..ccf1a1d7ab1950 100644 --- a/Telegram/Resources/qrc/telegram/telegram.qrc +++ b/Telegram/Resources/qrc/telegram/telegram.qrc @@ -15,6 +15,7 @@ ../../art/slot_2_idle.tgs ../../art/slot_back.tgs ../../art/slot_pull.tgs + ../../art/winners.tgs ../../day-blue.tdesktop-theme ../../night.tdesktop-theme ../../night-green.tdesktop-theme @@ -25,9 +26,11 @@ ../../art/recording/recording_info_audio.svg ../../art/recording/recording_info_video_landscape.svg ../../art/recording/recording_info_video_portrait.svg + ../../art/ttl/video_message_icon.svg ../../icons/settings/dino.svg ../../icons/settings/star.svg ../../icons/settings/starmini.svg + ../../icons/tray_monochrome.svg ../../art/topic_icons/blue.svg ../../art/topic_icons/yellow.svg ../../art/topic_icons/violet.svg diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 703efb68b0ff63..54c27edef59f79 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.15.0.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 53575af38d3c28..20b938e47e52a9 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,9,5,0 - PRODUCTVERSION 4,9,5,0 + FILEVERSION 4,15,0,0 + PRODUCTVERSION 4,15,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "4.9.5.0" - VALUE "LegalCopyright", "Copyright (C) 2014-2023" + VALUE "FileVersion", "4.15.0.0" + VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.9.5.0" + VALUE "ProductVersion", "4.15.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 78fefbb88d7205..f4f694d5b1b1fd 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,9,5,0 - PRODUCTVERSION 4,9,5,0 + FILEVERSION 4,15,0,0 + PRODUCTVERSION 4,15,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "4.9.5.0" - VALUE "LegalCopyright", "Copyright (C) 2014-2023" + VALUE "FileVersion", "4.15.0.0" + VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "4.9.5.0" + VALUE "ProductVersion", "4.15.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/_other/packer.cpp b/Telegram/SourceFiles/_other/packer.cpp index 0ba79c1beeb577..156ba726a5f370 100644 --- a/Telegram/SourceFiles/_other/packer.cpp +++ b/Telegram/SourceFiles/_other/packer.cpp @@ -267,7 +267,7 @@ int main(int argc, char *argv[]) } QByteArray inner = f.readAll(); stream << name << quint32(inner.size()) << inner; -#ifdef Q_OS_UNIX +#ifndef Q_OS_WIN stream << (QFileInfo(fullName).isExecutable() ? true : false); #endif } @@ -281,7 +281,7 @@ int main(int argc, char *argv[]) cout << "Compression start, size: " << resultSize << "\n"; QByteArray compressed, resultCheck; -#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win +#if defined Q_OS_WIN && !defined TDESKTOP_USE_PACKAGED // use Lzma SDK for win const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header compressed.resize(hSize + resultSize + 1024 * 1024); // rsa signature + sha1 + lzma props + max compressed size @@ -496,10 +496,8 @@ int main(int argc, char *argv[]) QString outName((targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version)); #elif defined Q_OS_MAC QString outName((targetarmac ? QString("tarmacupd%1") : QString("tmacupd%1")).arg(AlphaVersion ? AlphaVersion : version)); -#elif defined Q_OS_UNIX - QString outName(QString("tlinuxupd%1").arg(AlphaVersion ? AlphaVersion : version)); #else -#error Unknown platform! + QString outName(QString("tlinuxupd%1").arg(AlphaVersion ? AlphaVersion : version)); #endif if (AlphaVersion) { outName += "_" + AlphaSignature; diff --git a/Telegram/SourceFiles/_other/packer.h b/Telegram/SourceFiles/_other/packer.h index d099ef0e358dbd..4e5fbfc7ac042e 100644 --- a/Telegram/SourceFiles/_other/packer.h +++ b/Telegram/SourceFiles/_other/packer.h @@ -27,7 +27,7 @@ extern "C" { #include } // extern "C" -#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win +#if defined Q_OS_WIN && !defined TDESKTOP_USE_PACKAGED // use Lzma SDK for win #include #else #include diff --git a/Telegram/SourceFiles/_other/startup_task_win.cpp b/Telegram/SourceFiles/_other/startup_task_win.cpp index 8e780848d66507..a54b941ee7aecc 100644 --- a/Telegram/SourceFiles/_other/startup_task_win.cpp +++ b/Telegram/SourceFiles/_other/startup_task_win.cpp @@ -6,6 +6,7 @@ For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include +#include #include #include diff --git a/Telegram/SourceFiles/_other/updater_linux.cpp b/Telegram/SourceFiles/_other/updater_linux.cpp index f286b05be5137e..58bc10efadf615 100644 --- a/Telegram/SourceFiles/_other/updater_linux.cpp +++ b/Telegram/SourceFiles/_other/updater_linux.cpp @@ -41,6 +41,7 @@ bool do_mkdir(const char *path) { // from http://stackoverflow.com/questions/675 } bool _debug = false; +bool writeprotected = false; string updaterDir; string updaterName; string workDir; @@ -88,7 +89,7 @@ void writeLog(const char *format, ...) { va_end(args); } -bool copyFile(const char *from, const char *to, bool writeprotected) { +bool copyFile(const char *from, const char *to) { FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb"); if (!ffrom) { if (fto) fclose(fto); @@ -211,7 +212,7 @@ void delFolder() { rmdir(delFolder.c_str()); } -bool update(bool writeprotected) { +bool update() { writeLog("Update started.."); string updDir = workDir + "tupdates/temp", readyFilePath = workDir + "tupdates/temp/ready", tdataDir = workDir + "tupdates/temp/tdata"; @@ -324,7 +325,7 @@ bool update(bool writeprotected) { writeLog("Copying file '%s' to '%s'..", fname.c_str(), tofname.c_str()); int copyTries = 0, triesLimit = 30; do { - if (!copyFile(fname.c_str(), tofname.c_str(), writeprotected)) { + if (!copyFile(fname.c_str(), tofname.c_str())) { ++copyTries; usleep(100000); } else { @@ -359,10 +360,10 @@ int main(int argc, char *argv[]) { bool needupdate = true; bool autostart = false; bool debug = false; - bool writeprotected = false; bool tosettings = false; bool startintray = false; bool customWorkingDir = false; + bool justUpdate = false; char *key = 0; char *workdir = 0; @@ -381,6 +382,9 @@ int main(int argc, char *argv[]) { customWorkingDir = true; } else if (equal(argv[i], "-writeprotected")) { writeprotected = true; + justUpdate = true; + } else if (equal(argv[i], "-justupdate")) { + justUpdate = true; } else if (equal(argv[i], "-key") && ++i < argc) { key = argv[i]; } else if (equal(argv[i], "-workpath") && ++i < argc) { @@ -455,7 +459,7 @@ int main(int argc, char *argv[]) { } else { writeLog("Passed workpath is '%s'", workDir.c_str()); } - update(writeprotected); + update(); } } else { writeLog("Error: bad exe name!"); @@ -464,36 +468,38 @@ int main(int argc, char *argv[]) { writeLog("Error: short exe name!"); } - const auto fullBinaryPath = exePath + exeName; - - auto values = vector(); - const auto push = [&](string arg) { - // Force null-terminated .data() call result. - values.push_back(arg + char(0)); - }; - push(!argv0.empty() ? argv0 : fullBinaryPath); - push("-noupdate"); - if (autostart) push("-autostart"); - if (debug) push("-debug"); - if (startintray) push("-startintray"); - if (tosettings) push("-tosettings"); - if (key) { - push("-key"); - push(key); - } - if (customWorkingDir && workdir) { - push("-workdir"); - push(workdir); - } + // let the parent launch instead + if (justUpdate) { + writeLog("Closing log and quitting.."); + } else { + const auto fullBinaryPath = exePath + exeName; + + auto values = vector(); + const auto push = [&](string arg) { + // Force null-terminated .data() call result. + values.push_back(arg + char(0)); + }; + push(!argv0.empty() ? argv0 : fullBinaryPath); + push("-noupdate"); + if (autostart) push("-autostart"); + if (debug) push("-debug"); + if (startintray) push("-startintray"); + if (tosettings) push("-tosettings"); + if (key) { + push("-key"); + push(key); + } + if (customWorkingDir && workdir) { + push("-workdir"); + push(workdir); + } - auto args = vector(); - for (auto &arg : values) { - args.push_back(arg.data()); - } - args.push_back(nullptr); + auto args = vector(); + for (auto &arg : values) { + args.push_back(arg.data()); + } + args.push_back(nullptr); - // let the parent launch instead - if (!writeprotected) { pid_t pid = fork(); switch (pid) { case -1: @@ -503,9 +509,10 @@ int main(int argc, char *argv[]) { execv(fullBinaryPath.c_str(), args.data()); return 1; } + + writeLog("Executed Telegram, closing log and quitting.."); } - writeLog("Executed Telegram, closing log and quitting.."); closeLog(); return 0; diff --git a/Telegram/SourceFiles/_other/updater_win.cpp b/Telegram/SourceFiles/_other/updater_win.cpp index 1873815ce8a4f4..0b9b2478532794 100644 --- a/Telegram/SourceFiles/_other/updater_win.cpp +++ b/Telegram/SourceFiles/_other/updater_win.cpp @@ -537,11 +537,12 @@ HANDLE _generateDumpFileAtPath(const WCHAR *path) { GetLocalTime(&stLocalTime); - wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp", - szPath, szExeName, updaterVersionStr, - stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, - stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, - GetCurrentProcessId(), GetCurrentThreadId()); + wsprintf( + szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp", + szPath, szExeName, updaterVersionStr, + stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, + stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, + GetCurrentProcessId(), GetCurrentThreadId()); return CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0); } @@ -562,7 +563,7 @@ void _generateDump(EXCEPTION_POINTERS* pExceptionPointers) { DWORD len = GetModuleFileName(GetModuleHandle(0), szPath, maxFileLen); if (!len) return; - WCHAR *pathEnd = szPath + len; + WCHAR *pathEnd = szPath + len; if (!_wcsicmp(pathEnd - wcslen(_exeName), _exeName)) { wsprintf(pathEnd - wcslen(_exeName), L""); diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index 65826aa2f3e7dd..d49f1bbf784726 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -169,9 +169,7 @@ void SendBotCallbackData( void HideSingleUseKeyboard( not_null controller, not_null item) { - controller->content()->hideSingleUseKeyboard( - item->history()->peer, - item->id); + controller->content()->hideSingleUseKeyboard(item->fullId()); } } // namespace @@ -312,12 +310,14 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { case ButtonType::Default: { // Copy string before passing it to the sending method // because the original button can be destroyed inside. - const auto replyTo = item->isRegular() ? item->id : 0; + const auto replyTo = item->isRegular() + ? item->fullId() + : FullMsgId(); controller->content()->sendBotCommand({ .peer = item->history()->peer, .command = QString(button->text), .context = item->fullId(), - .replyTo = replyTo, + .replyTo = { replyTo }, }); } break; @@ -363,7 +363,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { case ButtonType::RequestPhone: { HideSingleUseKeyboard(controller, item); - const auto itemId = item->id; + const auto itemId = item->fullId(); const auto topicRootId = item->topicRootId(); const auto history = item->history(); controller->show(Ui::MakeConfirmBox({ @@ -376,7 +376,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { auto action = Api::SendAction(history); action.clearDraft = false; action.replyTo = { - .msgId = itemId, + .messageId = itemId, .topicRootId = topicRootId, }; history->session().api().shareContact( @@ -397,13 +397,11 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { chosen |= PollData::Flag::Quiz; } } - const auto replyToId = MsgId(0); - const auto topicRootId = MsgId(0); + const auto replyTo = FullReplyTo(); Window::PeerMenuCreatePoll( controller, item->history()->peer, - replyToId, - topicRootId, + replyTo, chosen, disabled); } break; @@ -417,12 +415,16 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { const auto peer = item->history()->peer; const auto itemId = item->id; const auto id = int32(button->buttonId); - const auto chosen = [=](not_null result) { + const auto chosen = [=](std::vector> result) { peer->session().api().request(MTPmessages_SendBotRequestedPeer( peer->input, MTP_int(itemId), MTP_int(id), - result->input + MTP_vector_from_range( + result + | ranges::views::transform([]( + not_null peer) { + return MTPInputPeer(peer->input); })) )).done([=](const MTPUpdates &result) { peer->session().api().applyUpdates(result); }).send(); diff --git a/Telegram/SourceFiles/api/api_chat_filters.cpp b/Telegram/SourceFiles/api/api_chat_filters.cpp index c045263ae1daf3..7287b56e30f001 100644 --- a/Telegram/SourceFiles/api/api_chat_filters.cpp +++ b/Telegram/SourceFiles/api/api_chat_filters.cpp @@ -20,12 +20,12 @@ For license and copyright information please follow this link: #include "history/history.h" #include "lang/lang_keys.h" #include "main/main_session.h" -#include "settings/settings_common.h" #include "ui/boxes/confirm_box.h" #include "ui/controls/filter_link_header.h" #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" #include "ui/filter_icons.h" +#include "ui/vertical_list.h" #include "window/window_session_controller.h" #include "styles/style_filter_icons.h" #include "styles/style_layers.h" @@ -341,7 +341,7 @@ void ToggleChatsController::setupAboveWidget() { _addedTopWidget = container->add(object_ptr(container)); const auto realAbove = container->add( object_ptr(container)); - AddDivider(realAbove); + Ui::AddDivider(realAbove); const auto totalCount = [&] { if (_chats.empty()) { return _additional.size(); @@ -422,7 +422,7 @@ void ToggleChatsController::setupBelowWidget() { auto widget = object_ptr( (QWidget*)nullptr, std::move(layout), - st::settingsDividerLabelPadding); + st::defaultBoxDividerLabelPadding); raw->add(object_ptr( raw, (_action == ToggleAction::Removing diff --git a/Telegram/SourceFiles/api/api_chat_invite.cpp b/Telegram/SourceFiles/api/api_chat_invite.cpp index 8b395d9a9e3f8c..dd06d2abc91669 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.cpp +++ b/Telegram/SourceFiles/api/api_chat_invite.cpp @@ -9,6 +9,7 @@ For license and copyright information please follow this link: #include "apiwrap.h" #include "window/window_session_controller.h" +#include "info/profile/info_profile_badge.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/empty_userpic.h" @@ -25,6 +26,7 @@ For license and copyright information please follow this link: #include "ui/toast/toast.h" #include "boxes/premium_limits_box.h" #include "styles/style_boxes.h" +#include "styles/style_info.h" #include "styles/style_layers.h" namespace Api { @@ -195,6 +197,13 @@ ConfirmInviteBox::ConfirmInviteBox( : _session(session) , _submit(std::move(submit)) , _title(this, st::confirmInviteTitle) +, _badge(std::make_unique( + this, + st::infoPeerBadge, + _session, + rpl::single(Info::Profile::Badge::Content{ BadgeForInvite(invite) }), + nullptr, + [=] { return false; })) , _status(this, st::confirmInviteStatus) , _about(this, st::confirmInviteAbout) , _aboutRequests(this, st::confirmInviteStatus) @@ -275,9 +284,24 @@ ConfirmInviteBox::ChatInvite ConfirmInviteBox::Parse( .isMegagroup = data.is_megagroup(), .isBroadcast = data.is_broadcast(), .isRequestNeeded = data.is_request_needed(), + .isFake = data.is_fake(), + .isScam = data.is_scam(), + .isVerified = data.is_verified(), }; } +[[nodiscard]] Info::Profile::BadgeType ConfirmInviteBox::BadgeForInvite( + const ChatInvite &invite) { + using Type = Info::Profile::BadgeType; + return invite.isVerified + ? Type::Verified + : invite.isScam + ? Type::Scam + : invite.isFake + ? Type::Fake + : Type::None; +} + void ConfirmInviteBox::prepare() { addButton( (_requestApprove @@ -326,8 +350,26 @@ void ConfirmInviteBox::prepare() { void ConfirmInviteBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); - _title->move((width() - _title->width()) / 2, st::confirmInviteTitleTop); - _status->move((width() - _status->width()) / 2, st::confirmInviteStatusTop); + + const auto padding = st::boxRowPadding; + auto nameWidth = width() - padding.left() - padding.right(); + auto badgeWidth = 0; + if (const auto widget = _badge->widget()) { + badgeWidth = st::infoVerifiedCheckPosition.x() + widget->width(); + nameWidth -= badgeWidth; + } + _title->resizeToWidth(std::min(nameWidth, _title->textMaxWidth())); + _title->moveToLeft( + (width() - _title->width() - badgeWidth) / 2, + st::confirmInviteTitleTop); + const auto badgeLeft = _title->x() + _title->width(); + const auto badgeTop = _title->y(); + const auto badgeBottom = _title->y() + _title->height(); + _badge->move(badgeLeft, badgeTop, badgeBottom); + + _status->move( + (width() - _status->width()) / 2, + st::confirmInviteStatusTop); auto bottom = _status->y() + _status->height() + st::boxPadding.bottom() diff --git a/Telegram/SourceFiles/api/api_chat_invite.h b/Telegram/SourceFiles/api/api_chat_invite.h index 1ae6f2529c8d9d..c8d99548c9d9e4 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.h +++ b/Telegram/SourceFiles/api/api_chat_invite.h @@ -12,6 +12,11 @@ For license and copyright information please follow this link: class UserData; class ChannelData; +namespace Info::Profile { +class Badge; +enum class BadgeType; +} // namespace Info::Profile + namespace Main { class Session; } // namespace Main @@ -66,10 +71,15 @@ class ConfirmInviteBox final : public Ui::BoxContent { bool isMegagroup = false; bool isBroadcast = false; bool isRequestNeeded = false; + bool isFake = false; + bool isScam = false; + bool isVerified = false; }; [[nodiscard]] static ChatInvite Parse( not_null session, const MTPDchatInvite &data); + [[nodiscard]] Info::Profile::BadgeType BadgeForInvite( + const ChatInvite &invite); ConfirmInviteBox( not_null session, @@ -81,12 +91,14 @@ class ConfirmInviteBox final : public Ui::BoxContent { Fn _submit; object_ptr _title; + std::unique_ptr _badge; object_ptr _status; object_ptr _about; object_ptr _aboutRequests; std::shared_ptr _photo; std::unique_ptr _photoEmpty; std::vector _participants; + bool _isChannel = false; bool _requestApprove = false; diff --git a/Telegram/SourceFiles/api/api_chat_participants.cpp b/Telegram/SourceFiles/api/api_chat_participants.cpp index 0ee748f2b99729..1714d9e2bc7ba7 100644 --- a/Telegram/SourceFiles/api/api_chat_participants.cpp +++ b/Telegram/SourceFiles/api/api_chat_participants.cpp @@ -211,6 +211,29 @@ void ApplyBotsList( Data::PeerUpdate::Flag::FullInfo); } +[[nodiscard]] ChatParticipants::Channels ParseSimilar( + not_null channel, + const MTPmessages_Chats &chats) { + auto result = ChatParticipants::Channels(); + std::vector>(); + chats.match([&](const auto &data) { + const auto &list = data.vchats().v; + result.list.reserve(list.size()); + for (const auto &chat : list) { + const auto peer = channel->owner().processChat(chat); + if (const auto channel = peer->asChannel()) { + result.list.push_back(channel); + } + } + if constexpr (MTPDmessages_chatsSlice::Is()) { + if (channel->session().premiumPossible()) { + result.more = data.vcount().v - data.vchats().v.size(); + } + } + }); + return result; +} + } // namespace ChatParticipant::ChatParticipant( @@ -685,4 +708,50 @@ void ChatParticipants::unblock( _kickRequests.emplace(kick, requestId); } +void ChatParticipants::loadSimilarChannels(not_null channel) { + if (!channel->isBroadcast()) { + return; + } else if (const auto i = _similar.find(channel); i != end(_similar)) { + if (i->second.requestId + || !i->second.channels.more + || !channel->session().premium()) { + return; + } + } + _similar[channel].requestId = _api.request( + MTPchannels_GetChannelRecommendations(channel->inputChannel) + ).done([=](const MTPmessages_Chats &result) { + auto &similar = _similar[channel]; + similar.requestId = 0; + auto parsed = ParseSimilar(channel, result); + if (similar.channels == parsed) { + return; + } + similar.channels = std::move(parsed); + if (const auto history = channel->owner().historyLoaded(channel)) { + if (const auto item = history->joinedMessageInstance()) { + history->owner().requestItemResize(item); + } + } + _similarLoaded.fire_copy(channel); + }).send(); +} + +auto ChatParticipants::similar(not_null channel) +-> const Channels & { + const auto i = channel->isBroadcast() + ? _similar.find(channel) + : end(_similar); + if (i != end(_similar)) { + return i->second.channels; + } + static const auto empty = Channels(); + return empty; +} + +auto ChatParticipants::similarLoaded() const +-> rpl::producer> { + return _similarLoaded.events(); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_chat_participants.h b/Telegram/SourceFiles/api/api_chat_participants.h index 41eda6fe681a05..816cc65263ceaa 100644 --- a/Telegram/SourceFiles/api/api_chat_participants.h +++ b/Telegram/SourceFiles/api/api_chat_participants.h @@ -120,7 +120,26 @@ class ChatParticipants final { not_null channel, not_null participant); + void loadSimilarChannels(not_null channel); + + struct Channels { + std::vector> list; + int more = 0; + + friend inline bool operator==( + const Channels &, + const Channels &) = default; + }; + [[nodiscard]] const Channels &similar(not_null channel); + [[nodiscard]] auto similarLoaded() const + -> rpl::producer>; + private: + struct SimilarChannels { + Channels channels; + mtpRequestId requestId = 0; + }; + MTP::Sender _api; using PeerRequests = base::flat_map; @@ -143,6 +162,9 @@ class ChatParticipants final { not_null>; base::flat_map _kickRequests; + base::flat_map, SimilarChannels> _similar; + rpl::event_stream> _similarLoaded; + }; } // namespace Api diff --git a/Telegram/SourceFiles/api/api_common.cpp b/Telegram/SourceFiles/api/api_common.cpp index 0a44a916f0ca39..cfb1e72207fd19 100644 --- a/Telegram/SourceFiles/api/api_common.cpp +++ b/Telegram/SourceFiles/api/api_common.cpp @@ -19,8 +19,8 @@ SendAction::SendAction( SendOptions options) : history(thread->owningHistory()) , options(options) -, replyTo({ .msgId = thread->topicRootId() }) { - replyTo.topicRootId = replyTo.msgId; +, replyTo({ .messageId = { history->peer->id, thread->topicRootId() } }) { + replyTo.topicRootId = replyTo.messageId.msg; } SendOptions DefaultSendWhenOnlineOptions() { @@ -31,7 +31,7 @@ SendOptions DefaultSendWhenOnlineOptions() { } MTPInputReplyTo SendAction::mtpReplyTo() const { - return Data::ReplyToForMTP(&history->owner(), replyTo); + return Data::ReplyToForMTP(history, replyTo); } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index 08ee59ca1f5a1a..155666a5d4ee69 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -7,6 +7,8 @@ For license and copyright information please follow this link: */ #pragma once +#include "data/data_drafts.h" + class History; namespace Data { @@ -22,8 +24,8 @@ struct SendOptions { TimeId scheduled = 0; bool silent = false; bool handleSupportSwitch = false; - bool removeWebPageId = false; bool hideViaBot = false; + crl::time ttlSeconds = 0; }; [[nodiscard]] SendOptions DefaultSendWhenOnlineOptions(); @@ -54,7 +56,7 @@ struct MessageToSend { SendAction action; TextWithTags textWithTags; - WebPageId webPageId = 0; + Data::WebPageDraft webPage; }; struct RemoteFileInfo { diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp index 916e276534a5b5..005effad994788 100644 --- a/Telegram/SourceFiles/api/api_editing.cpp +++ b/Telegram/SourceFiles/api/api_editing.cpp @@ -11,8 +11,10 @@ For license and copyright information please follow this link: #include "api/api_media.h" #include "api/api_text_entities.h" #include "ui/boxes/confirm_box.h" +#include "data/data_histories.h" #include "data/data_scheduled_messages.h" #include "data/data_session.h" +#include "data/data_web_page.h" #include "history/history.h" #include "history/history_item.h" #include "lang/lang_keys.h" @@ -45,6 +47,7 @@ template mtpRequestId EditMessage( not_null item, const TextWithEntities &textWithEntities, + Data::WebPageDraft webpage, SendOptions options, DoneCallback &&done, FailCallback &&fail, @@ -65,15 +68,21 @@ mtpRequestId EditMessage( const auto emptyFlag = MTPmessages_EditMessage::Flag(0); const auto flags = emptyFlag - | (!text.isEmpty() || media + | ((!text.isEmpty() || media) ? MTPmessages_EditMessage::Flag::f_message : emptyFlag) | ((media && inputMedia.has_value()) ? MTPmessages_EditMessage::Flag::f_media : emptyFlag) - | (options.removeWebPageId + | (webpage.removed ? MTPmessages_EditMessage::Flag::f_no_webpage : emptyFlag) + | ((!webpage.removed && !webpage.url.isEmpty()) + ? MTPmessages_EditMessage::Flag::f_media + : emptyFlag) + | ((!webpage.removed && !webpage.url.isEmpty() && webpage.invert) + ? MTPmessages_EditMessage::Flag::f_invert_media + : emptyFlag) | (!sentEntities.v.isEmpty() ? MTPmessages_EditMessage::Flag::f_entities : emptyFlag) @@ -89,7 +98,7 @@ mtpRequestId EditMessage( item->history()->peer->input, MTP_int(id), MTP_string(text), - inputMedia.value_or(MTPInputMedia()), + inputMedia.value_or(Data::WebPageForMTP(webpage, text.isEmpty())), MTPReplyMarkup(), sentEntities, MTP_int(options.scheduled) @@ -133,9 +142,15 @@ mtpRequestId EditMessage( FailCallback &&fail, std::optional inputMedia = std::nullopt) { const auto &text = item->originalText(); + const auto webpage = (!item->media() || !item->media()->webpage()) + ? Data::WebPageDraft{ .removed = true } + : Data::WebPageDraft{ + .id = item->media()->webpage()->id, + }; return EditMessage( item, text, + webpage, options, std::forward(done), std::forward(fail), @@ -216,12 +231,19 @@ mtpRequestId EditCaption( SendOptions options, Fn done, Fn fail) { - return EditMessage(item, caption, options, done, fail); + return EditMessage( + item, + caption, + Data::WebPageDraft(), + options, + done, + fail); } mtpRequestId EditTextMessage( not_null item, const TextWithEntities &caption, + Data::WebPageDraft webpage, SendOptions options, Fn done, Fn fail) { @@ -229,7 +251,7 @@ mtpRequestId EditTextMessage( applyUpdates(); done(id); }; - return EditMessage(item, caption, options, callback, fail); + return EditMessage(item, caption, webpage, options, callback, fail); } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_editing.h b/Telegram/SourceFiles/api/api_editing.h index 7c132124b1f0dd..86f106f45e9de9 100644 --- a/Telegram/SourceFiles/api/api_editing.h +++ b/Telegram/SourceFiles/api/api_editing.h @@ -9,6 +9,10 @@ For license and copyright information please follow this link: class HistoryItem; +namespace Data { +struct WebPageDraft; +} // namespace Data + namespace MTP { class Error; } // namespace MTP @@ -48,6 +52,7 @@ mtpRequestId EditCaption( mtpRequestId EditTextMessage( not_null item, const TextWithEntities &caption, + Data::WebPageDraft webpage, SendOptions options, Fn done, Fn fail); diff --git a/Telegram/SourceFiles/api/api_global_privacy.cpp b/Telegram/SourceFiles/api/api_global_privacy.cpp index 219d335f15ded0..f958bdfe804d31 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.cpp +++ b/Telegram/SourceFiles/api/api_global_privacy.cpp @@ -84,18 +84,61 @@ void GlobalPrivacy::dismissArchiveAndMuteSuggestion() { u"AUTOARCHIVE_POPULAR"_q); } +void GlobalPrivacy::updateHideReadTime(bool hide) { + update( + archiveAndMuteCurrent(), + unarchiveOnNewMessageCurrent(), + hide, + newRequirePremiumCurrent()); +} + +bool GlobalPrivacy::hideReadTimeCurrent() const { + return _hideReadTime.current(); +} + +rpl::producer GlobalPrivacy::hideReadTime() const { + return _hideReadTime.value(); +} + +void GlobalPrivacy::updateNewRequirePremium(bool value) { + update( + archiveAndMuteCurrent(), + unarchiveOnNewMessageCurrent(), + hideReadTimeCurrent(), + value); +} + +bool GlobalPrivacy::newRequirePremiumCurrent() const { + return _newRequirePremium.current(); +} + +rpl::producer GlobalPrivacy::newRequirePremium() const { + return _newRequirePremium.value(); +} + + void GlobalPrivacy::updateArchiveAndMute(bool value) { - update(value, unarchiveOnNewMessageCurrent()); + update( + value, + unarchiveOnNewMessageCurrent(), + hideReadTimeCurrent(), + newRequirePremiumCurrent()); } void GlobalPrivacy::updateUnarchiveOnNewMessage( UnarchiveOnNewMessage value) { - update(archiveAndMuteCurrent(), value); + update( + archiveAndMuteCurrent(), + value, + hideReadTimeCurrent(), + newRequirePremiumCurrent()); } void GlobalPrivacy::update( bool archiveAndMute, - UnarchiveOnNewMessage unarchiveOnNewMessage) { + UnarchiveOnNewMessage unarchiveOnNewMessage, + bool hideReadTime, + bool newRequirePremium) { using Flag = MTPDglobalPrivacySettings::Flag; _api.request(_requestId).cancel(); @@ -108,17 +151,26 @@ void GlobalPrivacy::update( : Flag()) | (unarchiveOnNewMessage != UnarchiveOnNewMessage::AnyUnmuted ? Flag::f_keep_archived_folders + : Flag()) + | (hideReadTime ? Flag::f_hide_read_marks : Flag()) + | ((newRequirePremium && _session->premium()) + ? Flag::f_new_noncontact_peers_require_premium : Flag()); _requestId = _api.request(MTPaccount_SetGlobalPrivacySettings( MTP_globalPrivacySettings(MTP_flags(flags)) )).done([=](const MTPGlobalPrivacySettings &result) { _requestId = 0; apply(result); - }).fail([=] { + }).fail([=](const MTP::Error &error) { _requestId = 0; + if (error.type() == u"PREMIUM_ACCOUNT_REQUIRED"_q) { + update(archiveAndMute, unarchiveOnNewMessage, hideReadTime, {}); + } }).send(); _archiveAndMute = archiveAndMute; _unarchiveOnNewMessage = unarchiveOnNewMessage; + _hideReadTime = hideReadTime; + _newRequirePremium = newRequirePremium; } void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) { @@ -129,6 +181,8 @@ void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) { : data.is_keep_archived_folders() ? UnarchiveOnNewMessage::NotInFoldersUnmuted : UnarchiveOnNewMessage::AnyUnmuted; + _hideReadTime = data.is_hide_read_marks(); + _newRequirePremium = data.is_new_noncontact_peers_require_premium(); }); } diff --git a/Telegram/SourceFiles/api/api_global_privacy.h b/Telegram/SourceFiles/api/api_global_privacy.h index 9e4b8e12156a7a..de70ba9b971f2a 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.h +++ b/Telegram/SourceFiles/api/api_global_privacy.h @@ -41,12 +41,22 @@ class GlobalPrivacy final { [[nodiscard]] rpl::producer<> suggestArchiveAndMute() const; void dismissArchiveAndMuteSuggestion(); + void updateHideReadTime(bool hide); + [[nodiscard]] bool hideReadTimeCurrent() const; + [[nodiscard]] rpl::producer hideReadTime() const; + + void updateNewRequirePremium(bool value); + [[nodiscard]] bool newRequirePremiumCurrent() const; + [[nodiscard]] rpl::producer newRequirePremium() const; + private: void apply(const MTPGlobalPrivacySettings &data); void update( bool archiveAndMute, - UnarchiveOnNewMessage unarchiveOnNewMessage); + UnarchiveOnNewMessage unarchiveOnNewMessage, + bool hideReadTime, + bool newRequirePremium); const not_null _session; MTP::Sender _api; @@ -55,6 +65,8 @@ class GlobalPrivacy final { rpl::variable _unarchiveOnNewMessage = UnarchiveOnNewMessage::None; rpl::variable _showArchiveAndMute = false; + rpl::variable _hideReadTime = false; + rpl::variable _newRequirePremium = false; std::vector> _callbacks; }; diff --git a/Telegram/SourceFiles/api/api_media.cpp b/Telegram/SourceFiles/api/api_media.cpp index ec8c1e4b26d1f5..a76cb4b67586fa 100644 --- a/Telegram/SourceFiles/api/api_media.cpp +++ b/Telegram/SourceFiles/api/api_media.cpp @@ -79,16 +79,19 @@ MTPInputMedia PrepareUploadedPhoto( not_null item, RemoteFileInfo info) { using Flag = MTPDinputMediaUploadedPhoto::Flag; - const auto spoiler = item->media() - && item->media()->hasSpoiler(); + const auto spoiler = item->media() && item->media()->hasSpoiler(); + const auto ttlSeconds = item->media() + ? item->media()->ttlSeconds() + : 0; const auto flags = (spoiler ? Flag::f_spoiler : Flag()) - | (info.attachedStickers.empty() ? Flag() : Flag::f_stickers); + | (info.attachedStickers.empty() ? Flag() : Flag::f_stickers) + | (ttlSeconds ? Flag::f_ttl_seconds : Flag()); return MTP_inputMediaUploadedPhoto( MTP_flags(flags), info.file, MTP_vector( ranges::to>(info.attachedStickers)), - MTP_int(0)); + MTP_int(ttlSeconds)); } MTPInputMedia PrepareUploadedDocument( @@ -98,12 +101,15 @@ MTPInputMedia PrepareUploadedDocument( return MTP_inputMediaEmpty(); } using Flag = MTPDinputMediaUploadedDocument::Flag; - const auto spoiler = item->media() - && item->media()->hasSpoiler(); + const auto spoiler = item->media() && item->media()->hasSpoiler(); + const auto ttlSeconds = item->media() + ? item->media()->ttlSeconds() + : 0; const auto flags = (spoiler ? Flag::f_spoiler : Flag()) | (info.thumb ? Flag::f_thumb : Flag()) | (item->groupId() ? Flag::f_nosound_video : Flag()) - | (info.attachedStickers.empty() ? Flag::f_stickers : Flag()); + | (info.attachedStickers.empty() ? Flag::f_stickers : Flag()) + | (ttlSeconds ? Flag::f_ttl_seconds : Flag()); const auto document = item->media()->document(); return MTP_inputMediaUploadedDocument( MTP_flags(flags), @@ -113,7 +119,7 @@ MTPInputMedia PrepareUploadedDocument( ComposeSendingDocumentAttributes(document), MTP_vector( ranges::to>(info.attachedStickers)), - MTP_int(0)); + MTP_int(ttlSeconds)); } bool HasAttachedStickers(MTPInputMedia media) { diff --git a/Telegram/SourceFiles/api/api_messages_search.cpp b/Telegram/SourceFiles/api/api_messages_search.cpp index 09f9d6a2159ead..4fbcaebf018901 100644 --- a/Telegram/SourceFiles/api/api_messages_search.cpp +++ b/Telegram/SourceFiles/api/api_messages_search.cpp @@ -10,6 +10,7 @@ For license and copyright information please follow this link: #include "apiwrap.h" #include "data/data_channel.h" #include "data/data_histories.h" +#include "data/data_message_reaction_id.h" #include "data/data_peer.h" #include "data/data_session.h" #include "history/history.h" @@ -43,6 +44,23 @@ constexpr auto kSearchPerPage = 50; return result; } +[[nodiscard]] QString RequestToToken( + const MessagesSearch::Request &request) { + auto result = request.query; + if (request.from) { + result += '\n' + QString::number(request.from->id.value); + } + for (const auto &tag : request.tags) { + result += '\n'; + if (const auto customId = tag.custom()) { + result += u"custom"_q + QString::number(customId); + } else { + result += u"emoji"_q + tag.emoji(); + } + } + return result; +} + } // namespace MessagesSearch::MessagesSearch(not_null history) @@ -54,9 +72,8 @@ MessagesSearch::~MessagesSearch() { base::take(_searchInHistoryRequest)); } -void MessagesSearch::searchMessages(const QString &query, PeerData *from) { - _query = query; - _from = from; +void MessagesSearch::searchMessages(Request request) { + _request = std::move(request); _offsetId = {}; searchRequest(); } @@ -69,8 +86,7 @@ void MessagesSearch::searchMore() { } void MessagesSearch::searchRequest() { - const auto nextToken = _query - + QString::number(_from ? _from->id.value : 0); + const auto nextToken = RequestToToken(_request); if (!_offsetId) { const auto it = _cacheOfStartByToken.find(nextToken); if (it != end(_cacheOfStartByToken)) { @@ -80,16 +96,21 @@ void MessagesSearch::searchRequest() { } } auto callback = [=](Fn finish) { - const auto flags = _from - ? MTP_flags(MTPmessages_Search::Flag::f_from_id) - : MTP_flags(0); + using Flag = MTPmessages_Search::Flag; + const auto from = _request.from; + const auto fromPeer = _history->peer->isUser() ? nullptr : from; + const auto savedPeer = _history->peer->isSelf() ? from : nullptr; _requestId = _history->session().api().request(MTPmessages_Search( - flags, + MTP_flags((fromPeer ? Flag::f_from_id : Flag()) + | (savedPeer ? Flag::f_saved_peer_id : Flag()) + | (_request.tags.empty() ? Flag() : Flag::f_saved_reaction)), _history->peer->input, - MTP_string(_query), - (_from - ? _from->input - : MTP_inputPeerEmpty()), + MTP_string(_request.query), + (fromPeer ? fromPeer->input : MTP_inputPeerEmpty()), + (savedPeer ? savedPeer->input : MTP_inputPeerEmpty()), + MTP_vector_from_range(_request.tags | ranges::views::transform( + Data::ReactionToMTP + )), MTPint(), // top_msg_id MTP_inputMessagesFilterEmpty(), MTP_int(0), // min_date diff --git a/Telegram/SourceFiles/api/api_messages_search.h b/Telegram/SourceFiles/api/api_messages_search.h index b97ec69aae73af..76046aaa91aacd 100644 --- a/Telegram/SourceFiles/api/api_messages_search.h +++ b/Telegram/SourceFiles/api/api_messages_search.h @@ -7,10 +7,17 @@ For license and copyright information please follow this link: */ #pragma once +#include "base/qt/qt_compare.h" +#include "data/data_message_reaction_id.h" + class HistoryItem; class History; class PeerData; +namespace Data { +struct ReactionId; +} // namespace Data + namespace Api { struct FoundMessages { @@ -21,10 +28,23 @@ struct FoundMessages { class MessagesSearch final { public: + struct Request { + QString query; + PeerData *from = nullptr; + std::vector tags; + + friend inline bool operator==( + const Request &, + const Request &) = default; + friend inline auto operator<=>( + const Request &, + const Request &) = default; + }; + explicit MessagesSearch(not_null history); ~MessagesSearch(); - void searchMessages(const QString &query, PeerData *from); + void searchMessages(Request request); void searchMore(); [[nodiscard]] rpl::producer messagesFounds() const; @@ -41,8 +61,7 @@ class MessagesSearch final { base::flat_map _cacheOfStartByToken; - QString _query; - PeerData *_from = nullptr; + Request _request; MsgId _offsetId; int _searchInHistoryRequest = 0; // Not real mtpRequestId. diff --git a/Telegram/SourceFiles/api/api_messages_search_merged.cpp b/Telegram/SourceFiles/api/api_messages_search_merged.cpp index a1cb69d7741e5b..8451232f6089d8 100644 --- a/Telegram/SourceFiles/api/api_messages_search_merged.cpp +++ b/Telegram/SourceFiles/api/api_messages_search_merged.cpp @@ -11,12 +11,6 @@ For license and copyright information please follow this link: namespace Api { -bool MessagesSearchMerged::RequestCompare::operator()( - const Request &a, - const Request &b) const { - return (a.query < b.query) && (a.from < b.from); -} - MessagesSearchMerged::MessagesSearchMerged(not_null history) : _apiSearch(history) { if (const auto migrated = history->migrateFrom()) { @@ -88,9 +82,9 @@ void MessagesSearchMerged::clear() { void MessagesSearchMerged::search(const Request &search) { if (_migratedSearch) { _waitingForTotal = true; - _migratedSearch->searchMessages(search.query, search.from); + _migratedSearch->searchMessages(search); } - _apiSearch.searchMessages(search.query, search.from); + _apiSearch.searchMessages(search); } void MessagesSearchMerged::searchMore() { diff --git a/Telegram/SourceFiles/api/api_messages_search_merged.h b/Telegram/SourceFiles/api/api_messages_search_merged.h index d7c67713fb65da..d4c6fbc1c83cf6 100644 --- a/Telegram/SourceFiles/api/api_messages_search_merged.h +++ b/Telegram/SourceFiles/api/api_messages_search_merged.h @@ -12,19 +12,17 @@ For license and copyright information please follow this link: class History; class PeerData; +namespace Data { +struct ReactionId; +} // namespace Data + namespace Api { // Search in both of history and migrated history, if it exists. class MessagesSearchMerged final { public: - struct Request { - QString query; - PeerData *from = nullptr; - }; - struct RequestCompare { - bool operator()(const Request &a, const Request &b) const; - }; - using CachedRequests = std::set; + using Request = MessagesSearch::Request; + using CachedRequests = base::flat_set; MessagesSearchMerged(not_null history); diff --git a/Telegram/SourceFiles/api/api_peer_colors.cpp b/Telegram/SourceFiles/api/api_peer_colors.cpp new file mode 100644 index 00000000000000..5f3fe12d202bef --- /dev/null +++ b/Telegram/SourceFiles/api/api_peer_colors.cpp @@ -0,0 +1,168 @@ +/* +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_peer_colors.h" + +#include "apiwrap.h" +#include "data/data_peer.h" +#include "ui/chat/chat_style.h" + +namespace Api { +namespace { + +constexpr auto kRequestEach = 3600 * crl::time(1000); + +} // namespace + +PeerColors::PeerColors(not_null api) +: _api(&api->instance()) +, _timer([=] { request(); }) { + request(); + _timer.callEach(kRequestEach); +} + +PeerColors::~PeerColors() = default; + +void PeerColors::request() { + if (_requestId) { + return; + } + _requestId = _api.request(MTPhelp_GetPeerColors( + MTP_int(_hash) + )).done([=](const MTPhelp_PeerColors &result) { + _requestId = 0; + result.match([&](const MTPDhelp_peerColors &data) { + _hash = data.vhash().v; + apply(data); + }, [](const MTPDhelp_peerColorsNotModified &) { + }); + }).fail([=] { + _requestId = 0; + }).send(); +} + +std::vector PeerColors::suggested() const { + return _suggested.current(); +} + +rpl::producer> PeerColors::suggestedValue() const { + return _suggested.value(); +} + +auto PeerColors::indicesValue() const +-> rpl::producer { + return rpl::single( + indicesCurrent() + ) | rpl::then(_colorIndicesChanged.events() | rpl::map([=] { + return indicesCurrent(); + })); +} + +Ui::ColorIndicesCompressed PeerColors::indicesCurrent() const { + return _colorIndicesCurrent + ? *_colorIndicesCurrent + : Ui::ColorIndicesCompressed(); +} + +const base::flat_map &PeerColors::requiredLevelsGroup() const { + return _requiredLevelsGroup; +} + +const base::flat_map &PeerColors::requiredLevelsChannel() const { + return _requiredLevelsChannel; +} + +int PeerColors::requiredGroupLevelFor(PeerId channel, uint8 index) const { + if (Data::DecideColorIndex(channel) == index) { + return 0; + } else if (const auto i = _requiredLevelsGroup.find(index) + ; i != end(_requiredLevelsGroup)) { + return i->second; + } + return 1; +} + +int PeerColors::requiredChannelLevelFor(PeerId channel, uint8 index) const { + if (Data::DecideColorIndex(channel) == index) { + return 0; + } else if (const auto i = _requiredLevelsChannel.find(index) + ; i != end(_requiredLevelsChannel)) { + return i->second; + } + return 1; +} + +void PeerColors::apply(const MTPDhelp_peerColors &data) { + auto suggested = std::vector(); + auto colors = std::make_shared< + std::array>(); + + using ParsedColor = std::array; + const auto parseColors = [](const MTPhelp_PeerColorSet &set) { + return set.match([&](const MTPDhelp_peerColorSet &data) { + auto result = ParsedColor(); + const auto &list = data.vcolors().v; + if (list.empty() || list.size() > Ui::kColorPatternsCount) { + LOG(("API Error: Bad count for PeerColorSet.colors: %1" + ).arg(list.size())); + return ParsedColor(); + } + auto fill = result.data(); + for (const auto &color : list) { + *fill++ = (uint32(1) << 24) | uint32(color.v); + } + return result; + }, [](const MTPDhelp_peerColorProfileSet &) { + LOG(("API Error: peerColorProfileSet in colors result!")); + return ParsedColor(); + }); + }; + + const auto &list = data.vcolors().v; + _requiredLevelsGroup.clear(); + _requiredLevelsChannel.clear(); + suggested.reserve(list.size()); + for (const auto &color : list) { + const auto &data = color.data(); + const auto colorIndexBare = data.vcolor_id().v; + if (colorIndexBare < 0 || colorIndexBare >= Ui::kColorIndexCount) { + LOG(("API Error: Bad color index: %1").arg(colorIndexBare)); + continue; + } + const auto colorIndex = uint8(colorIndexBare); + if (const auto min = data.vgroup_min_level()) { + _requiredLevelsGroup[colorIndex] = min->v; + } + if (const auto min = data.vchannel_min_level()) { + _requiredLevelsChannel[colorIndex] = min->v; + } + if (!data.is_hidden()) { + suggested.push_back(colorIndex); + } + if (const auto light = data.vcolors()) { + auto &fields = (*colors)[colorIndex]; + fields.light = parseColors(*light); + if (const auto dark = data.vdark_colors()) { + fields.dark = parseColors(*dark); + } else { + fields.dark = fields.light; + } + } + } + + if (!_colorIndicesCurrent) { + _colorIndicesCurrent = std::make_unique( + Ui::ColorIndicesCompressed{ std::move(colors) }); + _colorIndicesChanged.fire({}); + } else if (*_colorIndicesCurrent->colors != *colors) { + _colorIndicesCurrent->colors = std::move(colors); + _colorIndicesChanged.fire({}); + } + _suggested = std::move(suggested); +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_peer_colors.h b/Telegram/SourceFiles/api/api_peer_colors.h new file mode 100644 index 00000000000000..f8d379020bfa1d --- /dev/null +++ b/Telegram/SourceFiles/api/api_peer_colors.h @@ -0,0 +1,61 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/timer.h" +#include "mtproto/sender.h" + +class ApiWrap; + +namespace Ui { +struct ColorIndicesCompressed; +} // namespace Ui + +namespace Api { + +class PeerColors final { +public: + explicit PeerColors(not_null api); + ~PeerColors(); + + [[nodiscard]] std::vector suggested() const; + [[nodiscard]] rpl::producer> suggestedValue() const; + [[nodiscard]] Ui::ColorIndicesCompressed indicesCurrent() const; + [[nodiscard]] auto indicesValue() const + -> rpl::producer; + + [[nodiscard]] auto requiredLevelsGroup() const + -> const base::flat_map &; + [[nodiscard]] auto requiredLevelsChannel() const + -> const base::flat_map &; + + [[nodiscard]] int requiredGroupLevelFor( + PeerId channel, + uint8 index) const; + [[nodiscard]] int requiredChannelLevelFor( + PeerId channel, + uint8 index) const; + +private: + void request(); + void apply(const MTPDhelp_peerColors &data); + + MTP::Sender _api; + int32 _hash = 0; + + mtpRequestId _requestId = 0; + base::Timer _timer; + rpl::variable> _suggested; + base::flat_map _requiredLevelsGroup; + base::flat_map _requiredLevelsChannel; + rpl::event_stream<> _colorIndicesChanged; + std::unique_ptr _colorIndicesCurrent; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_peer_photo.cpp b/Telegram/SourceFiles/api/api_peer_photo.cpp index 71512172b14164..c70ef4491f82e3 100644 --- a/Telegram/SourceFiles/api/api_peer_photo.cpp +++ b/Telegram/SourceFiles/api/api_peer_photo.cpp @@ -510,40 +510,60 @@ void PeerPhoto::requestUserPhotos( _userPhotosRequests.emplace(user, requestId); } +auto PeerPhoto::emojiList(EmojiListType type) -> EmojiListData & { + switch (type) { + case EmojiListType::Profile: return _profileEmojiList; + case EmojiListType::Group: return _groupEmojiList; + case EmojiListType::Background: return _backgroundEmojiList; + case EmojiListType::NoChannelStatus: return _noChannelStatusEmojiList; + } + Unexpected("Type in PeerPhoto::emojiList."); +} + +auto PeerPhoto::emojiList(EmojiListType type) const +-> const EmojiListData & { + return const_cast(this)->emojiList(type); +} + void PeerPhoto::requestEmojiList(EmojiListType type) { - if (_requestIdEmojiList) { + auto &list = emojiList(type); + if (list.requestId) { return; } - const auto isGroup = (type == EmojiListType::Group); - const auto d = [=](const MTPEmojiList &result) { - _requestIdEmojiList = 0; - result.match([](const MTPDemojiListNotModified &data) { - }, [&](const MTPDemojiList &data) { - auto &list = isGroup ? _profileEmojiList : _groupEmojiList; - list = ranges::views::all( - data.vdocument_id().v - ) | ranges::views::transform(&MTPlong::v) | ranges::to_vector; - }); + const auto send = [&](auto &&request) { + return _api.request( + std::move(request) + ).done([=](const MTPEmojiList &result) { + auto &list = emojiList(type); + list.requestId = 0; + result.match([](const MTPDemojiListNotModified &data) { + }, [&](const MTPDemojiList &data) { + list.list = ranges::views::all( + data.vdocument_id().v + ) | ranges::views::transform( + &MTPlong::v + ) | ranges::to_vector; + }); + }).fail([=] { + emojiList(type).requestId = 0; + }).send(); }; - const auto f = [=] { _requestIdEmojiList = 0; }; - _requestIdEmojiList = isGroup - ? _api.request( - MTPaccount_GetDefaultGroupPhotoEmojis() - ).done(d).fail(f).send() - : _api.request( - MTPaccount_GetDefaultProfilePhotoEmojis() - ).done(d).fail(f).send(); + list.requestId = (type == EmojiListType::Profile) + ? send(MTPaccount_GetDefaultProfilePhotoEmojis()) + : (type == EmojiListType::Group) + ? send(MTPaccount_GetDefaultGroupPhotoEmojis()) + : (type == EmojiListType::NoChannelStatus) + ? send(MTPaccount_GetChannelRestrictedStatusEmojis()) + : send(MTPaccount_GetDefaultBackgroundEmojis()); } rpl::producer PeerPhoto::emojiListValue( EmojiListType type) { - auto &list = (type == EmojiListType::Group) - ? _profileEmojiList - : _groupEmojiList; - if (list.current().empty() && !_requestIdEmojiList) { + auto &list = emojiList(type); + if (list.list.current().empty() && !list.requestId) { requestEmojiList(type); } - return list.value(); + return list.list.value(); } // Non-personal photo in case a personal photo is set. diff --git a/Telegram/SourceFiles/api/api_peer_photo.h b/Telegram/SourceFiles/api/api_peer_photo.h index 03920d3ef58b22..71d340a031f6b1 100644 --- a/Telegram/SourceFiles/api/api_peer_photo.h +++ b/Telegram/SourceFiles/api/api_peer_photo.h @@ -31,6 +31,8 @@ class PeerPhoto final { enum class EmojiListType { Profile, Group, + Background, + NoChannelStatus, }; struct UserPhoto { @@ -73,6 +75,10 @@ class PeerPhoto final { Suggestion, Fallback, }; + struct EmojiListData { + rpl::variable list; + mtpRequestId requestId = 0; + }; void ready( const FullMsgId &msgId, @@ -84,6 +90,9 @@ class PeerPhoto final { UploadType type, Fn done); + [[nodiscard]] EmojiListData &emojiList(EmojiListType type); + [[nodiscard]] const EmojiListData &emojiList(EmojiListType type) const; + const not_null _session; MTP::Sender _api; @@ -101,9 +110,10 @@ class PeerPhoto final { not_null, not_null> _nonPersonalPhotos; - mtpRequestId _requestIdEmojiList = 0; - rpl::variable _profileEmojiList; - rpl::variable _groupEmojiList; + EmojiListData _profileEmojiList; + EmojiListData _groupEmojiList; + EmojiListData _backgroundEmojiList; + EmojiListData _noChannelStatusEmojiList; }; diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp index 227b6e9dba1c68..7699894cfb33da 100644 --- a/Telegram/SourceFiles/api/api_polls.cpp +++ b/Telegram/SourceFiles/api/api_polls.cpp @@ -43,7 +43,7 @@ void Polls::create( const auto history = action.history; const auto peer = history->peer; - const auto topicRootId = action.replyTo.msgId + const auto topicRootId = action.replyTo.messageId ? action.replyTo.topicRootId : 0; auto sendFlags = MTPmessages_SendMedia::Flags(0); diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 975d36a656a67b..72da2a4893d709 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -9,14 +9,56 @@ For license and copyright information please follow this link: #include "api/api_premium_option.h" #include "api/api_text_entities.h" -#include "main/main_session.h" -#include "data/data_peer_values.h" +#include "apiwrap.h" +#include "base/random.h" #include "data/data_document.h" -#include "data/data_session.h" #include "data/data_peer.h" -#include "apiwrap.h" +#include "data/data_peer_values.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "history/view/history_view_element.h" +#include "history/history.h" +#include "history/history_item.h" +#include "main/main_account.h" +#include "main/main_app_config.h" +#include "main/main_session.h" +#include "payments/payments_form.h" +#include "ui/text/format_values.h" namespace Api { +namespace { + +[[nodiscard]] GiftCode Parse(const MTPDpayments_checkedGiftCode &data) { + return { + .from = data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(), + .to = data.vto_id() ? peerFromUser(*data.vto_id()) : PeerId(), + .giveawayId = data.vgiveaway_msg_id().value_or_empty(), + .date = data.vdate().v, + .used = data.vused_date().value_or_empty(), + .months = data.vmonths().v, + .giveaway = data.is_via_giveaway(), + }; +} + +[[nodiscard]] Data::SubscriptionOptions GiftCodesFromTL( + const QVector &tlOptions) { + auto options = SubscriptionOptionsFromTL(tlOptions); + for (auto i = 0; i < options.size(); i++) { + const auto &tlOption = tlOptions[i].data(); + const auto perUserText = Ui::FillAmountAndCurrency( + tlOption.vamount().v / float64(tlOption.vusers().v), + qs(tlOption.vcurrency()), + false); + options[i].costPerMonth = perUserText + + ' ' + + QChar(0x00D7) + + ' ' + + QString::number(tlOption.vusers().v); + } + return options; +} + +} // namespace Premium::Premium(not_null api) : _session(&api->session()) @@ -183,8 +225,388 @@ void Premium::reloadCloudSet() { }).send(); } +void Premium::checkGiftCode( + const QString &slug, + Fn done) { + if (_giftCodeRequestId) { + if (_giftCodeSlug == slug) { + return; + } + _api.request(_giftCodeRequestId).cancel(); + } + _giftCodeSlug = slug; + _giftCodeRequestId = _api.request(MTPpayments_CheckGiftCode( + MTP_string(slug) + )).done([=](const MTPpayments_CheckedGiftCode &result) { + _giftCodeRequestId = 0; + + const auto &data = result.data(); + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + done(updateGiftCode(slug, Parse(data))); + }).fail([=](const MTP::Error &error) { + _giftCodeRequestId = 0; + + done(updateGiftCode(slug, {})); + }).send(); +} + +GiftCode Premium::updateGiftCode( + const QString &slug, + const GiftCode &code) { + auto &now = _giftCodes[slug]; + if (now != code) { + now = code; + _giftCodeUpdated.fire_copy(slug); + } + return code; +} + +rpl::producer Premium::giftCodeValue(const QString &slug) const { + return _giftCodeUpdated.events_starting_with_copy( + slug + ) | rpl::filter(rpl::mappers::_1 == slug) | rpl::map([=] { + const auto i = _giftCodes.find(slug); + return (i != end(_giftCodes)) ? i->second : GiftCode(); + }); +} + +void Premium::applyGiftCode(const QString &slug, Fn done) { + _api.request(MTPpayments_ApplyGiftCode( + MTP_string(slug) + )).done([=](const MTPUpdates &result) { + _session->api().applyUpdates(result); + done({}); + }).fail([=](const MTP::Error &error) { + done(error.type()); + }).send(); +} + +void Premium::resolveGiveawayInfo( + not_null peer, + MsgId messageId, + Fn done) { + Expects(done != nullptr); + + _giveawayInfoDone = std::move(done); + if (_giveawayInfoRequestId) { + if (_giveawayInfoPeer == peer + && _giveawayInfoMessageId == messageId) { + return; + } + _api.request(_giveawayInfoRequestId).cancel(); + } + _giveawayInfoPeer = peer; + _giveawayInfoMessageId = messageId; + _giveawayInfoRequestId = _api.request(MTPpayments_GetGiveawayInfo( + _giveawayInfoPeer->input, + MTP_int(_giveawayInfoMessageId.bare) + )).done([=](const MTPpayments_GiveawayInfo &result) { + _giveawayInfoRequestId = 0; + + auto info = GiveawayInfo(); + result.match([&](const MTPDpayments_giveawayInfo &data) { + info.participating = data.is_participating(); + info.state = data.is_preparing_results() + ? GiveawayState::Preparing + : GiveawayState::Running; + info.adminChannelId = data.vadmin_disallowed_chat_id() + ? ChannelId(*data.vadmin_disallowed_chat_id()) + : ChannelId(); + info.disallowedCountry = qs( + data.vdisallowed_country().value_or_empty()); + info.tooEarlyDate + = data.vjoined_too_early_date().value_or_empty(); + info.startDate = data.vstart_date().v; + }, [&](const MTPDpayments_giveawayInfoResults &data) { + info.state = data.is_refunded() + ? GiveawayState::Refunded + : GiveawayState::Finished; + info.giftCode = qs(data.vgift_code_slug().value_or_empty()); + info.activatedCount = data.vactivated_count().v; + info.finishDate = data.vfinish_date().v; + info.startDate = data.vstart_date().v; + }); + _giveawayInfoDone(std::move(info)); + }).fail([=] { + _giveawayInfoRequestId = 0; + _giveawayInfoDone({}); + }).send(); +} + const Data::SubscriptionOptions &Premium::subscriptionOptions() const { return _subscriptionOptions; } +rpl::producer<> Premium::somePremiumRequiredResolved() const { + return _somePremiumRequiredResolved.events(); +} + +void Premium::resolvePremiumRequired(not_null user) { + _resolvePremiumRequiredUsers.emplace(user); + if (!_premiumRequiredRequestScheduled + && _resolvePremiumRequestedUsers.empty()) { + _premiumRequiredRequestScheduled = true; + crl::on_main(_session, [=] { + requestPremiumRequiredSlice(); + }); + } +} + +void Premium::requestPremiumRequiredSlice() { + _premiumRequiredRequestScheduled = false; + if (!_resolvePremiumRequestedUsers.empty() + || _resolvePremiumRequiredUsers.empty()) { + return; + } + constexpr auto kPerRequest = 100; + auto users = MTP_vector_from_range(_resolvePremiumRequiredUsers + | ranges::views::transform(&UserData::inputUser)); + if (users.v.size() > kPerRequest) { + auto shortened = users.v; + shortened.resize(kPerRequest); + users = MTP_vector(std::move(shortened)); + const auto from = begin(_resolvePremiumRequiredUsers); + _resolvePremiumRequestedUsers = { from, from + kPerRequest }; + _resolvePremiumRequiredUsers.erase(from, from + kPerRequest); + } else { + _resolvePremiumRequestedUsers + = base::take(_resolvePremiumRequiredUsers); + } + const auto finish = [=](const QVector &list) { + constexpr auto me = UserDataFlag::MeRequiresPremiumToWrite; + constexpr auto known = UserDataFlag::RequirePremiumToWriteKnown; + constexpr auto mask = me | known; + + auto index = 0; + for (const auto &user : base::take(_resolvePremiumRequestedUsers)) { + const auto require = (index < list.size()) + && mtpIsTrue(list[index++]); + user->setFlags((user->flags() & ~mask) + | known + | (require ? me : UserDataFlag())); + } + if (!_premiumRequiredRequestScheduled + && !_resolvePremiumRequiredUsers.empty()) { + _premiumRequiredRequestScheduled = true; + crl::on_main(_session, [=] { + requestPremiumRequiredSlice(); + }); + } + _somePremiumRequiredResolved.fire({}); + }; + _session->api().request( + MTPusers_GetIsPremiumRequiredToContact(std::move(users)) + ).done([=](const MTPVector &result) { + finish(result.v); + }).fail([=] { + finish({}); + }).send(); +} + +PremiumGiftCodeOptions::PremiumGiftCodeOptions(not_null peer) +: _peer(peer) +, _api(&peer->session().api().instance()) { +} + +rpl::producer PremiumGiftCodeOptions::request() { + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + using TLOption = MTPPremiumGiftCodeOption; + _api.request(MTPpayments_GetPremiumGiftCodeOptions( + MTP_flags(_peer->isChannel() + ? MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer + : MTPpayments_GetPremiumGiftCodeOptions::Flag(0)), + _peer->input + )).done([=](const MTPVector &result) { + auto tlMapOptions = base::flat_map>(); + for (const auto &tlOption : result.v) { + const auto &data = tlOption.data(); + tlMapOptions[data.vusers().v].push_back(tlOption); + + const auto token = Token{ data.vusers().v, data.vmonths().v }; + _stores[token] = Store{ + .amount = data.vamount().v, + .product = qs(data.vstore_product().value_or_empty()), + .quantity = data.vstore_quantity().value_or_empty(), + }; + if (!ranges::contains(_availablePresets, data.vusers().v)) { + _availablePresets.push_back(data.vusers().v); + } + } + for (const auto &[amount, tlOptions] : tlMapOptions) { + if (amount == 1 && _optionsForOnePerson.currency.isEmpty()) { + _optionsForOnePerson.currency = qs( + tlOptions.front().data().vcurrency()); + for (const auto &option : tlOptions) { + _optionsForOnePerson.months.push_back( + option.data().vmonths().v); + _optionsForOnePerson.totalCosts.push_back( + option.data().vamount().v); + } + } + _subscriptionOptions[amount] = GiftCodesFromTL(tlOptions); + } + consumer.put_done(); + }).fail([=](const MTP::Error &error) { + consumer.put_error_copy(error.type()); + }).send(); + + return lifetime; + }; +} + +rpl::producer PremiumGiftCodeOptions::applyPrepaid( + const Payments::InvoicePremiumGiftCode &invoice, + uint64 prepaidId) { + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + const auto channel = _peer->asChannel(); + if (!channel) { + return lifetime; + } + + _api.request(MTPpayments_LaunchPrepaidGiveaway( + _peer->input, + MTP_long(prepaidId), + Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice) + )).done([=](const MTPUpdates &result) { + _peer->session().api().applyUpdates(result); + consumer.put_done(); + }).fail([=](const MTP::Error &error) { + consumer.put_error_copy(error.type()); + }).send(); + + return lifetime; + }; +} + +const std::vector &PremiumGiftCodeOptions::availablePresets() const { + return _availablePresets; +} + +[[nodiscard]] int PremiumGiftCodeOptions::monthsFromPreset(int monthsIndex) { + Expects(monthsIndex >= 0 && monthsIndex < _availablePresets.size()); + + return _optionsForOnePerson.months[monthsIndex]; +} + +Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice( + int users, + int months) { + const auto randomId = base::RandomValue(); + const auto token = Token{ users, months }; + const auto &store = _stores[token]; + return Payments::InvoicePremiumGiftCode{ + .randomId = randomId, + .currency = _optionsForOnePerson.currency, + .amount = store.amount, + .storeProduct = store.product, + .storeQuantity = store.quantity, + .users = token.users, + .months = token.months, + }; +} + +Data::SubscriptionOptions PremiumGiftCodeOptions::options(int amount) { + const auto it = _subscriptionOptions.find(amount); + if (it != end(_subscriptionOptions)) { + return it->second; + } else { + auto tlOptions = QVector(); + for (auto i = 0; i < _optionsForOnePerson.months.size(); i++) { + tlOptions.push_back(MTP_premiumGiftCodeOption( + MTP_flags(MTPDpremiumGiftCodeOption::Flags(0)), + MTP_int(amount), + MTP_int(_optionsForOnePerson.months[i]), + MTPstring(), + MTPint(), + MTP_string(_optionsForOnePerson.currency), + MTP_long(_optionsForOnePerson.totalCosts[i] * amount))); + } + _subscriptionOptions[amount] = GiftCodesFromTL(tlOptions); + return _subscriptionOptions[amount]; + } +} + +int PremiumGiftCodeOptions::giveawayBoostsPerPremium() const { + constexpr auto kFallbackCount = 4; + return _peer->session().account().appConfig().get( + u"giveaway_boosts_per_premium"_q, + kFallbackCount); +} + +int PremiumGiftCodeOptions::giveawayCountriesMax() const { + constexpr auto kFallbackCount = 10; + return _peer->session().account().appConfig().get( + u"giveaway_countries_max"_q, + kFallbackCount); +} + +int PremiumGiftCodeOptions::giveawayAddPeersMax() const { + constexpr auto kFallbackCount = 10; + return _peer->session().account().appConfig().get( + u"giveaway_add_peers_max"_q, + kFallbackCount); +} + +int PremiumGiftCodeOptions::giveawayPeriodMax() const { + constexpr auto kFallbackCount = 3600 * 24 * 7; + return _peer->session().account().appConfig().get( + u"giveaway_period_max"_q, + kFallbackCount); +} + +bool PremiumGiftCodeOptions::giveawayGiftsPurchaseAvailable() const { + return _peer->session().account().appConfig().get( + u"giveaway_gifts_purchase_available"_q, + false); +} + +RequirePremiumState ResolveRequiresPremiumToWrite( + not_null peer, + History *maybeHistory) { + const auto user = peer->asUser(); + if (!user + || !user->someRequirePremiumToWrite() + || user->session().premium()) { + return RequirePremiumState::No; + } else if (user->requirePremiumToWriteKnown()) { + return user->meRequiresPremiumToWrite() + ? RequirePremiumState::Yes + : RequirePremiumState::No; + } else if (user->flags() & UserDataFlag::MutualContact) { + return RequirePremiumState::No; + } else if (!maybeHistory) { + return RequirePremiumState::Unknown; + } + + const auto update = [&](bool require) { + using Flag = UserDataFlag; + constexpr auto known = Flag::RequirePremiumToWriteKnown; + constexpr auto me = Flag::MeRequiresPremiumToWrite; + user->setFlags((user->flags() & ~me) + | known + | (require ? me : Flag())); + }; + // We allow this potentially-heavy loop because in case we've opened + // the chat and have a lot of messages `requires_premium` will be known. + for (const auto &block : maybeHistory->blocks) { + for (const auto &view : block->messages) { + const auto item = view->data(); + if (!item->out() && !item->isService()) { + update(false); + return RequirePremiumState::No; + } + } + } + if (user->isContact() // Here we know, that we're not in his contacts. + && maybeHistory->loadedAtTop() // And no incoming messages. + && maybeHistory->loadedAtBottom()) { + update(true); + } + return RequirePremiumState::Unknown; +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 3582833607cf4a..e1c3a7b41228c4 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -10,14 +10,62 @@ For license and copyright information please follow this link: #include "data/data_subscription_option.h" #include "mtproto/sender.h" +class History; class ApiWrap; namespace Main { class Session; } // namespace Main +namespace Payments { +struct InvoicePremiumGiftCode; +} // namespace Payments + namespace Api { +struct GiftCode { + PeerId from = 0; + PeerId to = 0; + MsgId giveawayId = 0; + TimeId date = 0; + TimeId used = 0; // 0 if not used. + int months = 0; + bool giveaway = false; + + explicit operator bool() const { + return months != 0; + } + + friend inline bool operator==( + const GiftCode&, + const GiftCode&) = default; +}; + +enum class GiveawayState { + Invalid, + Running, + Preparing, + Finished, + Refunded, +}; + +struct GiveawayInfo { + QString giftCode; + QString disallowedCountry; + ChannelId adminChannelId = 0; + GiveawayState state = GiveawayState::Invalid; + TimeId tooEarlyDate = 0; + TimeId finishDate = 0; + TimeId startDate = 0; + int winnersCount = 0; + int activatedCount = 0; + bool participating = false; + + explicit operator bool() const { + return state != GiveawayState::Invalid; + } +}; + class Premium final { public: explicit Premium(not_null api); @@ -40,13 +88,30 @@ class Premium final { [[nodiscard]] int64 monthlyAmount() const; [[nodiscard]] QString monthlyCurrency() const; + void checkGiftCode( + const QString &slug, + Fn done); + GiftCode updateGiftCode(const QString &slug, const GiftCode &code); + [[nodiscard]] rpl::producer giftCodeValue( + const QString &slug) const; + void applyGiftCode(const QString &slug, Fn done); + + void resolveGiveawayInfo( + not_null peer, + MsgId messageId, + Fn done); + [[nodiscard]] auto subscriptionOptions() const -> const Data::SubscriptionOptions &; + [[nodiscard]] rpl::producer<> somePremiumRequiredResolved() const; + void resolvePremiumRequired(not_null user); + private: void reloadPromo(); void reloadStickers(); void reloadCloudSet(); + void requestPremiumRequiredSlice(); const not_null _session; MTP::Sender _api; @@ -71,8 +136,83 @@ class Premium final { int64 _monthlyAmount = 0; QString _monthlyCurrency; + mtpRequestId _giftCodeRequestId = 0; + QString _giftCodeSlug; + base::flat_map _giftCodes; + rpl::event_stream _giftCodeUpdated; + + mtpRequestId _giveawayInfoRequestId = 0; + PeerData *_giveawayInfoPeer = nullptr; + MsgId _giveawayInfoMessageId = 0; + Fn _giveawayInfoDone; + Data::SubscriptionOptions _subscriptionOptions; + rpl::event_stream<> _somePremiumRequiredResolved; + base::flat_set> _resolvePremiumRequiredUsers; + base::flat_set> _resolvePremiumRequestedUsers; + bool _premiumRequiredRequestScheduled = false; + +}; + +class PremiumGiftCodeOptions final { +public: + PremiumGiftCodeOptions(not_null peer); + + [[nodiscard]] rpl::producer request(); + [[nodiscard]] Data::SubscriptionOptions options(int amount); + [[nodiscard]] const std::vector &availablePresets() const; + [[nodiscard]] int monthsFromPreset(int monthsIndex); + [[nodiscard]] Payments::InvoicePremiumGiftCode invoice( + int users, + int months); + [[nodiscard]] rpl::producer applyPrepaid( + const Payments::InvoicePremiumGiftCode &invoice, + uint64 prepaidId); + + [[nodiscard]] int giveawayBoostsPerPremium() const; + [[nodiscard]] int giveawayCountriesMax() const; + [[nodiscard]] int giveawayAddPeersMax() const; + [[nodiscard]] int giveawayPeriodMax() const; + [[nodiscard]] bool giveawayGiftsPurchaseAvailable() const; + +private: + struct Token final { + int users = 0; + int months = 0; + + friend inline constexpr auto operator<=>(Token, Token) = default; + + }; + struct Store final { + uint64 amount = 0; + QString product; + int quantity = 0; + }; + using Amount = int; + const not_null _peer; + base::flat_map _subscriptionOptions; + struct { + std::vector months; + std::vector totalCosts; + QString currency; + } _optionsForOnePerson; + + std::vector _availablePresets; + + base::flat_map _stores; + + MTP::Sender _api; + +}; + +enum class RequirePremiumState { + Unknown, + Yes, + No, }; +[[nodiscard]] RequirePremiumState ResolveRequiresPremiumToWrite( + not_null peer, + History *maybeHistory); } // namespace Api diff --git a/Telegram/SourceFiles/api/api_premium_option.h b/Telegram/SourceFiles/api/api_premium_option.h index c23eb787647cd2..5758a8cb8f9dbe 100644 --- a/Telegram/SourceFiles/api/api_premium_option.h +++ b/Telegram/SourceFiles/api/api_premium_option.h @@ -36,7 +36,10 @@ template result.reserve(tlOptions.size()); for (const auto &tlOption : tlOptions) { const auto &option = tlOption.data(); - const auto botUrl = qs(option.vbot_url()); + auto botUrl = QString(); + if constexpr (!std::is_same_v) { + botUrl = qs(option.vbot_url()); + } const auto months = option.vmonths().v; const auto amount = option.vamount().v; const auto currency = qs(option.vcurrency()); diff --git a/Telegram/SourceFiles/api/api_report.cpp b/Telegram/SourceFiles/api/api_report.cpp index 50187018071a6b..8691527371932d 100644 --- a/Telegram/SourceFiles/api/api_report.cpp +++ b/Telegram/SourceFiles/api/api_report.cpp @@ -78,12 +78,8 @@ void SendReport( MTP_string(comment) )).done(std::move(done)).send(); }, [&](StoryId id) { - const auto user = peer->asUser(); - if (!user) { - return; - } peer->session().api().request(MTPstories_Report( - user->inputUser, + peer->input, MTP_vector(1, MTP_int(id)), ReasonToTL(reason), MTP_string(comment) diff --git a/Telegram/SourceFiles/api/api_send_progress.cpp b/Telegram/SourceFiles/api/api_send_progress.cpp index 54d3c4e00e289c..93d28792652ae3 100644 --- a/Telegram/SourceFiles/api/api_send_progress.cpp +++ b/Telegram/SourceFiles/api/api_send_progress.cpp @@ -161,14 +161,13 @@ bool SendProgressManager::skipRequest(const Key &key) const { return true; } const auto recently = base::unixtime::now() - kSendTypingsToOfflineFor; - const auto online = user->onlineTill; - if (online == -2) { // last seen recently + const auto lastseen = user->lastseen(); + if (lastseen.isRecently()) { return false; - } else if (online < 0) { - return (-online < recently); - } else { - return (online < recently); + } else if (const auto value = lastseen.onlineTill()) { + return (value < recently); } + return true; } void SendProgressManager::done(mtpRequestId requestId) { diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 291a2675a8f8c9..0e5d7c5855eb2b 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -42,6 +42,9 @@ void InnerFillMessagePostFlags( not_null peer, MessageFlags &flags) { const auto anonymousPost = peer->amAnonymous(); + if (ShouldSendSilent(peer, options)) { + flags |= MessageFlag::Silent; + } if (!anonymousPost || options.sendAs) { flags |= MessageFlag::HasFromId; return; @@ -270,7 +273,6 @@ bool SendDice(MessageToSend &message) { flags |= MessageFlag::HasReplyInfo; sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to; } - const auto replyHeader = NewMessageReplyHeader(message.action); const auto anonymousPost = peer->amAnonymous(); const auto silentPost = ShouldSendSilent(peer, message.action.options); InnerFillMessagePostFlags(message.action.options, peer, flags); @@ -368,9 +370,9 @@ void SendConfirmedFile( if (!isEditing) { const auto histories = &session->data().histories(); - file->to.replyTo.msgId = histories->convertTopicReplyToId( + file->to.replyTo.messageId = histories->convertTopicReplyToId( history, - file->to.replyTo.msgId); + file->to.replyTo.messageId); file->to.replyTo.topicRootId = histories->convertTopicReplyToId( history, file->to.replyTo.topicRootId); @@ -399,13 +401,8 @@ void SendConfirmedFile( if (file->to.replyTo) { flags |= MessageFlag::HasReplyInfo; } - const auto replyHeader = NewMessageReplyHeader(action); const auto anonymousPost = peer->amAnonymous(); - const auto silentPost = ShouldSendSilent(peer, file->to.options); FillMessagePostFlags(action, peer, flags); - if (silentPost) { - flags |= MessageFlag::Silent; - } if (file->to.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; @@ -445,11 +442,30 @@ void SendConfirmedFile( MTPDocument(), // alt_document MTPint()); } else if (file->type == SendMediaType::Audio) { + const auto ttlSeconds = file->to.options.ttlSeconds; + const auto isVoice = [&] { + return file->document.match([](const MTPDdocumentEmpty &d) { + return false; + }, [](const MTPDdocument &d) { + return ranges::any_of(d.vattributes().v, [&]( + const MTPDocumentAttribute &attribute) { + using Att = MTPDdocumentAttributeAudio; + return attribute.match([](const Att &data) -> bool { + return data.vflags().v & Att::Flag::f_voice; + }, [](const auto &) { + return false; + }); + }); + }); + }(); + using Flag = MTPDmessageMediaDocument::Flag; return MTP_messageMediaDocument( - MTP_flags(MTPDmessageMediaDocument::Flag::f_document), + MTP_flags(Flag::f_document + | (isVoice ? Flag::f_voice : Flag()) + | (ttlSeconds ? Flag::f_ttl_seconds : Flag())), file->document, MTPDocument(), // alt_document - MTPint()); + MTP_int(ttlSeconds)); } else { Unexpected("Type in sendFilesConfirmed."); } diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp new file mode 100644 index 00000000000000..789211f33652bc --- /dev/null +++ b/Telegram/SourceFiles/api/api_statistics.cpp @@ -0,0 +1,749 @@ +/* +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_statistics.h" + +#include "apiwrap.h" +#include "data/data_channel.h" +#include "data/data_session.h" +#include "data/data_stories.h" +#include "data/data_story.h" +#include "history/history.h" +#include "main/main_session.h" +#include "statistics/statistics_data_deserialize.h" + +namespace Api { +namespace { + +constexpr auto kCheckRequestsTimer = 10 * crl::time(1000); + +[[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL( + const MTPStatsGraph &tl) { + return tl.match([&](const MTPDstatsGraph &d) { + using namespace Statistic; + const auto zoomToken = d.vzoom_token().has_value() + ? qs(*d.vzoom_token()).toUtf8() + : QByteArray(); + return Data::StatisticalGraph{ + StatisticalChartFromJSON(qs(d.vjson().data().vdata()).toUtf8()), + zoomToken, + }; + }, [&](const MTPDstatsGraphAsync &data) { + return Data::StatisticalGraph{ + .zoomToken = qs(data.vtoken()).toUtf8(), + }; + }, [&](const MTPDstatsGraphError &data) { + return Data::StatisticalGraph{ .error = qs(data.verror()) }; + }); +} + +[[nodiscard]] Data::StatisticalValue StatisticalValueFromTL( + const MTPStatsAbsValueAndPrev &tl) { + const auto current = tl.data().vcurrent().v; + const auto previous = tl.data().vprevious().v; + return Data::StatisticalValue{ + .value = current, + .previousValue = previous, + .growthRatePercentage = previous + ? std::abs((current - previous) / float64(previous) * 100.) + : 0, + }; +} + +[[nodiscard]] Data::ChannelStatistics ChannelStatisticsFromTL( + const MTPDstats_broadcastStats &data) { + const auto &tlUnmuted = data.venabled_notifications().data(); + const auto unmuted = (!tlUnmuted.vtotal().v) + ? 0. + : std::clamp( + tlUnmuted.vpart().v / tlUnmuted.vtotal().v * 100., + 0., + 100.); + using Recent = MTPPostInteractionCounters; + auto recentMessages = ranges::views::all( + data.vrecent_posts_interactions().v + ) | ranges::views::transform([&](const Recent &tl) { + return tl.match([&](const MTPDpostInteractionCountersStory &data) { + return Data::StatisticsMessageInteractionInfo{ + .storyId = data.vstory_id().v, + .viewsCount = data.vviews().v, + .forwardsCount = data.vforwards().v, + .reactionsCount = data.vreactions().v, + }; + }, [&](const MTPDpostInteractionCountersMessage &data) { + return Data::StatisticsMessageInteractionInfo{ + .messageId = data.vmsg_id().v, + .viewsCount = data.vviews().v, + .forwardsCount = data.vforwards().v, + .reactionsCount = data.vreactions().v, + }; + }); + }) | ranges::to_vector; + + return { + .startDate = data.vperiod().data().vmin_date().v, + .endDate = data.vperiod().data().vmax_date().v, + + .memberCount = StatisticalValueFromTL(data.vfollowers()), + .meanViewCount = StatisticalValueFromTL(data.vviews_per_post()), + .meanShareCount = StatisticalValueFromTL(data.vshares_per_post()), + .meanReactionCount = StatisticalValueFromTL( + data.vreactions_per_post()), + + .meanStoryViewCount = StatisticalValueFromTL( + data.vviews_per_story()), + .meanStoryShareCount = StatisticalValueFromTL( + data.vshares_per_story()), + .meanStoryReactionCount = StatisticalValueFromTL( + data.vreactions_per_story()), + + .enabledNotificationsPercentage = unmuted, + + .memberCountGraph = StatisticalGraphFromTL( + data.vgrowth_graph()), + + .joinGraph = StatisticalGraphFromTL( + data.vfollowers_graph()), + + .muteGraph = StatisticalGraphFromTL( + data.vmute_graph()), + + .viewCountByHourGraph = StatisticalGraphFromTL( + data.vtop_hours_graph()), + + .viewCountBySourceGraph = StatisticalGraphFromTL( + data.vviews_by_source_graph()), + + .joinBySourceGraph = StatisticalGraphFromTL( + data.vnew_followers_by_source_graph()), + + .languageGraph = StatisticalGraphFromTL( + data.vlanguages_graph()), + + .messageInteractionGraph = StatisticalGraphFromTL( + data.vinteractions_graph()), + + .instantViewInteractionGraph = StatisticalGraphFromTL( + data.viv_interactions_graph()), + + .reactionsByEmotionGraph = StatisticalGraphFromTL( + data.vreactions_by_emotion_graph()), + + .storyInteractionsGraph = StatisticalGraphFromTL( + data.vstory_interactions_graph()), + + .storyReactionsByEmotionGraph = StatisticalGraphFromTL( + data.vstory_reactions_by_emotion_graph()), + + .recentMessageInteractions = std::move(recentMessages), + }; +} + +[[nodiscard]] Data::SupergroupStatistics SupergroupStatisticsFromTL( + const MTPDstats_megagroupStats &data) { + using Senders = MTPStatsGroupTopPoster; + using Administrators = MTPStatsGroupTopAdmin; + using Inviters = MTPStatsGroupTopInviter; + + auto topSenders = ranges::views::all( + data.vtop_posters().v + ) | ranges::views::transform([&](const Senders &tl) { + return Data::StatisticsMessageSenderInfo{ + .userId = UserId(tl.data().vuser_id().v), + .sentMessageCount = tl.data().vmessages().v, + .averageCharacterCount = tl.data().vavg_chars().v, + }; + }) | ranges::to_vector; + auto topAdministrators = ranges::views::all( + data.vtop_admins().v + ) | ranges::views::transform([&](const Administrators &tl) { + return Data::StatisticsAdministratorActionsInfo{ + .userId = UserId(tl.data().vuser_id().v), + .deletedMessageCount = tl.data().vdeleted().v, + .bannedUserCount = tl.data().vkicked().v, + .restrictedUserCount = tl.data().vbanned().v, + }; + }) | ranges::to_vector; + auto topInviters = ranges::views::all( + data.vtop_inviters().v + ) | ranges::views::transform([&](const Inviters &tl) { + return Data::StatisticsInviterInfo{ + .userId = UserId(tl.data().vuser_id().v), + .addedMemberCount = tl.data().vinvitations().v, + }; + }) | ranges::to_vector; + + return { + .startDate = data.vperiod().data().vmin_date().v, + .endDate = data.vperiod().data().vmax_date().v, + + .memberCount = StatisticalValueFromTL(data.vmembers()), + .messageCount = StatisticalValueFromTL(data.vmessages()), + .viewerCount = StatisticalValueFromTL(data.vviewers()), + .senderCount = StatisticalValueFromTL(data.vposters()), + + .memberCountGraph = StatisticalGraphFromTL( + data.vgrowth_graph()), + + .joinGraph = StatisticalGraphFromTL( + data.vmembers_graph()), + + .joinBySourceGraph = StatisticalGraphFromTL( + data.vnew_members_by_source_graph()), + + .languageGraph = StatisticalGraphFromTL( + data.vlanguages_graph()), + + .messageContentGraph = StatisticalGraphFromTL( + data.vmessages_graph()), + + .actionGraph = StatisticalGraphFromTL( + data.vactions_graph()), + + .dayGraph = StatisticalGraphFromTL( + data.vtop_hours_graph()), + + .weekGraph = StatisticalGraphFromTL( + data.vweekdays_graph()), + + .topSenders = std::move(topSenders), + .topAdministrators = std::move(topAdministrators), + .topInviters = std::move(topInviters), + }; +} + +} // namespace + +Statistics::Statistics(not_null channel) +: StatisticsRequestSender(channel) { +} + +StatisticsRequestSender::StatisticsRequestSender(not_null channel) +: _channel(channel) +, _api(&_channel->session().api().instance()) +, _timer([=] { checkRequests(); }) { +} + +StatisticsRequestSender::~StatisticsRequestSender() { + for (const auto &[dcId, ids] : _requests) { + for (const auto id : ids) { + _channel->session().api().unregisterStatsRequest(dcId, id); + } + } +} + +void StatisticsRequestSender::checkRequests() { + for (auto i = begin(_requests); i != end(_requests);) { + for (auto j = begin(i->second); j != end(i->second);) { + if (_api.pending(*j)) { + ++j; + } else { + _channel->session().api().unregisterStatsRequest( + i->first, + *j); + j = i->second.erase(j); + } + } + if (i->second.empty()) { + i = _requests.erase(i); + } else { + ++i; + } + } + if (_requests.empty()) { + _timer.cancel(); + } +} + +template +auto StatisticsRequestSender::makeRequest(Request &&request) { + const auto id = _api.allocateRequestId(); + const auto dcId = _channel->owner().statsDcId(_channel); + if (dcId) { + _channel->session().api().registerStatsRequest(dcId, id); + _requests[dcId].emplace(id); + if (!_timer.isActive()) { + _timer.callEach(kCheckRequestsTimer); + } + } + return std::move(_api.request( + std::forward(request) + ).toDC( + dcId ? MTP::ShiftDcId(dcId, MTP::kStatsDcShift) : 0 + ).overrideId(id)); +} + +rpl::producer Statistics::request() { + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + if (!channel()->isMegagroup()) { + makeRequest(MTPstats_GetBroadcastStats( + MTP_flags(MTPstats_GetBroadcastStats::Flags(0)), + channel()->inputChannel + )).done([=](const MTPstats_BroadcastStats &result) { + _channelStats = ChannelStatisticsFromTL(result.data()); + consumer.put_done(); + }).fail([=](const MTP::Error &error) { + consumer.put_error_copy(error.type()); + }).send(); + } else { + makeRequest(MTPstats_GetMegagroupStats( + MTP_flags(MTPstats_GetMegagroupStats::Flags(0)), + channel()->inputChannel + )).done([=](const MTPstats_MegagroupStats &result) { + const auto &data = result.data(); + _supergroupStats = SupergroupStatisticsFromTL(data); + channel()->owner().processUsers(data.vusers()); + consumer.put_done(); + }).fail([=](const MTP::Error &error) { + consumer.put_error_copy(error.type()); + }).send(); + } + + return lifetime; + }; +} + +Statistics::GraphResult Statistics::requestZoom( + const QString &token, + float64 x) { + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + const auto wasEmpty = _zoomDeque.empty(); + _zoomDeque.push_back([=] { + makeRequest(MTPstats_LoadAsyncGraph( + MTP_flags(x + ? MTPstats_LoadAsyncGraph::Flag::f_x + : MTPstats_LoadAsyncGraph::Flag(0)), + MTP_string(token), + MTP_long(x) + )).done([=](const MTPStatsGraph &result) { + consumer.put_next(StatisticalGraphFromTL(result)); + consumer.put_done(); + if (!_zoomDeque.empty()) { + _zoomDeque.pop_front(); + if (!_zoomDeque.empty()) { + _zoomDeque.front()(); + } + } + }).fail([=](const MTP::Error &error) { + consumer.put_error_copy(error.type()); + }).send(); + }); + if (wasEmpty) { + _zoomDeque.front()(); + } + + return lifetime; + }; +} + +Data::ChannelStatistics Statistics::channelStats() const { + return _channelStats; +} + +Data::SupergroupStatistics Statistics::supergroupStats() const { + return _supergroupStats; +} + +PublicForwards::PublicForwards( + not_null channel, + Data::RecentPostId fullId) +: StatisticsRequestSender(channel) +, _fullId(fullId) { +} + +void PublicForwards::request( + const Data::PublicForwardsSlice::OffsetToken &token, + Fn done) { + if (_requestId) { + return; + } + const auto channel = StatisticsRequestSender::channel(); + const auto processResult = [=](const MTPstats_PublicForwards &tl) { + using Messages = QVector; + _requestId = 0; + + const auto &data = tl.data(); + auto &owner = channel->owner(); + + owner.processUsers(data.vusers()); + owner.processChats(data.vchats()); + + const auto nextToken = data.vnext_offset() + ? qs(*data.vnext_offset()) + : Data::PublicForwardsSlice::OffsetToken(); + + const auto fullCount = data.vcount().v; + + auto recentList = Messages(data.vforwards().v.size()); + for (const auto &tlForward : data.vforwards().v) { + tlForward.match([&](const MTPDpublicForwardMessage &data) { + const auto &message = data.vmessage(); + const auto msgId = IdFromMessage(message); + const auto peerId = PeerFromMessage(message); + const auto lastDate = DateFromMessage(message); + if (const auto peer = owner.peerLoaded(peerId)) { + if (!lastDate) { + return; + } + owner.addNewMessage( + message, + MessageFlags(), + NewMessageType::Existing); + recentList.push_back({ .messageId = { peerId, msgId } }); + } + }, [&](const MTPDpublicForwardStory &data) { + const auto story = owner.stories().applySingle( + peerFromMTP(data.vpeer()), + data.vstory()); + if (story) { + recentList.push_back({ .storyId = story->fullId() }); + } + }); + } + + const auto allLoaded = nextToken.isEmpty() || (nextToken == token); + _lastTotal = std::max(_lastTotal, fullCount); + done({ + .list = std::move(recentList), + .total = _lastTotal, + .allLoaded = allLoaded, + .token = nextToken, + }); + }; + + constexpr auto kLimit = tl::make_int(100); + if (_fullId.messageId) { + _requestId = makeRequest(MTPstats_GetMessagePublicForwards( + channel->inputChannel, + MTP_int(_fullId.messageId.msg), + MTP_string(token), + kLimit + )).done(processResult).fail([=] { _requestId = 0; }).send(); + } else if (_fullId.storyId) { + _requestId = makeRequest(MTPstats_GetStoryPublicForwards( + channel->input, + MTP_int(_fullId.storyId.story), + MTP_string(token), + kLimit + )).done(processResult).fail([=] { _requestId = 0; }).send(); + } +} + +MessageStatistics::MessageStatistics( + not_null channel, + FullMsgId fullId) +: StatisticsRequestSender(channel) +, _publicForwards(channel, { .messageId = fullId }) +, _fullId(fullId) { +} + +MessageStatistics::MessageStatistics( + not_null channel, + FullStoryId storyId) +: StatisticsRequestSender(channel) +, _publicForwards(channel, { .storyId = storyId }) +, _storyId(storyId) { +} + +Data::PublicForwardsSlice MessageStatistics::firstSlice() const { + return _firstSlice; +} + +void MessageStatistics::request(Fn done) { + if (channel()->isMegagroup()) { + return; + } + const auto requestFirstPublicForwards = [=]( + const Data::StatisticalGraph &messageGraph, + const Data::StatisticalGraph &reactionsGraph, + const Data::StatisticsMessageInteractionInfo &info) { + const auto callback = [=](Data::PublicForwardsSlice slice) { + const auto total = slice.total; + _firstSlice = std::move(slice); + done({ + .messageInteractionGraph = messageGraph, + .reactionsByEmotionGraph = reactionsGraph, + .publicForwards = total, + .privateForwards = info.forwardsCount - total, + .views = info.viewsCount, + .reactions = info.reactionsCount, + }); + }; + _publicForwards.request({}, callback); + }; + + const auto requestPrivateForwards = [=]( + const Data::StatisticalGraph &messageGraph, + const Data::StatisticalGraph &reactionsGraph) { + api().request(MTPchannels_GetMessages( + channel()->inputChannel, + MTP_vector( + 1, + MTP_inputMessageID(MTP_int(_fullId.msg)))) + ).done([=](const MTPmessages_Messages &result) { + const auto process = [&](const MTPVector &messages) { + const auto &message = messages.v.front(); + return message.match([&](const MTPDmessage &data) { + auto reactionsCount = 0; + if (const auto tlReactions = data.vreactions()) { + const auto &tlCounts = tlReactions->data().vresults(); + for (const auto &tlCount : tlCounts.v) { + reactionsCount += tlCount.data().vcount().v; + } + } + return Data::StatisticsMessageInteractionInfo{ + .messageId = IdFromMessage(message), + .viewsCount = data.vviews() + ? data.vviews()->v + : 0, + .forwardsCount = data.vforwards() + ? data.vforwards()->v + : 0, + .reactionsCount = reactionsCount, + }; + }, [](const MTPDmessageEmpty &) { + return Data::StatisticsMessageInteractionInfo(); + }, [](const MTPDmessageService &) { + return Data::StatisticsMessageInteractionInfo(); + }); + }; + + auto info = result.match([&](const MTPDmessages_messages &data) { + return process(data.vmessages()); + }, [&](const MTPDmessages_messagesSlice &data) { + return process(data.vmessages()); + }, [&](const MTPDmessages_channelMessages &data) { + return process(data.vmessages()); + }, [](const MTPDmessages_messagesNotModified &) { + return Data::StatisticsMessageInteractionInfo(); + }); + + requestFirstPublicForwards( + messageGraph, + reactionsGraph, + std::move(info)); + }).fail([=](const MTP::Error &error) { + requestFirstPublicForwards(messageGraph, reactionsGraph, {}); + }).send(); + }; + + const auto requestStoryPrivateForwards = [=]( + const Data::StatisticalGraph &messageGraph, + const Data::StatisticalGraph &reactionsGraph) { + api().request(MTPstories_GetStoriesByID( + channel()->input, + MTP_vector(1, MTP_int(_storyId.story))) + ).done([=](const MTPstories_Stories &result) { + const auto &storyItem = result.data().vstories().v.front(); + auto info = storyItem.match([&](const MTPDstoryItem &data) { + if (!data.vviews()) { + return Data::StatisticsMessageInteractionInfo(); + } + const auto &tlViews = data.vviews()->data(); + return Data::StatisticsMessageInteractionInfo{ + .storyId = data.vid().v, + .viewsCount = tlViews.vviews_count().v, + .forwardsCount = tlViews.vforwards_count().value_or(0), + .reactionsCount = tlViews.vreactions_count().value_or(0), + }; + }, [](const auto &) { + return Data::StatisticsMessageInteractionInfo(); + }); + + requestFirstPublicForwards( + messageGraph, + reactionsGraph, + std::move(info)); + }).fail([=](const MTP::Error &error) { + requestFirstPublicForwards(messageGraph, reactionsGraph, {}); + }).send(); + }; + + if (_storyId) { + makeRequest(MTPstats_GetStoryStats( + MTP_flags(MTPstats_GetStoryStats::Flags(0)), + channel()->input, + MTP_int(_storyId.story) + )).done([=](const MTPstats_StoryStats &result) { + const auto &data = result.data(); + requestStoryPrivateForwards( + StatisticalGraphFromTL(data.vviews_graph()), + StatisticalGraphFromTL(data.vreactions_by_emotion_graph())); + }).fail([=](const MTP::Error &error) { + requestStoryPrivateForwards({}, {}); + }).send(); + } else { + makeRequest(MTPstats_GetMessageStats( + MTP_flags(MTPstats_GetMessageStats::Flags(0)), + channel()->inputChannel, + MTP_int(_fullId.msg.bare) + )).done([=](const MTPstats_MessageStats &result) { + const auto &data = result.data(); + requestPrivateForwards( + StatisticalGraphFromTL(data.vviews_graph()), + StatisticalGraphFromTL(data.vreactions_by_emotion_graph())); + }).fail([=](const MTP::Error &error) { + requestPrivateForwards({}, {}); + }).send(); + } +} + +Boosts::Boosts(not_null peer) +: _peer(peer) +, _api(&peer->session().api().instance()) { +} + +rpl::producer Boosts::request() { + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + const auto channel = _peer->asChannel(); + if (!channel) { + return lifetime; + } + + _api.request(MTPpremium_GetBoostsStatus( + _peer->input + )).done([=](const MTPpremium_BoostsStatus &result) { + const auto &data = result.data(); + channel->updateLevelHint(data.vlevel().v); + const auto hasPremium = !!data.vpremium_audience(); + const auto premiumMemberCount = hasPremium + ? std::max(0, int(data.vpremium_audience()->data().vpart().v)) + : 0; + const auto participantCount = hasPremium + ? std::max( + int(data.vpremium_audience()->data().vtotal().v), + premiumMemberCount) + : 0; + const auto premiumMemberPercentage = (participantCount > 0) + ? (100. * premiumMemberCount / participantCount) + : 0; + + const auto slots = data.vmy_boost_slots(); + _boostStatus.overview = Data::BoostsOverview{ + .group = channel->isMegagroup(), + .mine = slots ? int(slots->v.size()) : 0, + .level = std::max(data.vlevel().v, 0), + .boostCount = std::max( + data.vboosts().v, + data.vcurrent_level_boosts().v), + .currentLevelBoostCount = data.vcurrent_level_boosts().v, + .nextLevelBoostCount = data.vnext_level_boosts() + ? data.vnext_level_boosts()->v + : 0, + .premiumMemberCount = premiumMemberCount, + .premiumMemberPercentage = premiumMemberPercentage, + }; + _boostStatus.link = qs(data.vboost_url()); + + if (data.vprepaid_giveaways()) { + _boostStatus.prepaidGiveaway = ranges::views::all( + data.vprepaid_giveaways()->v + ) | ranges::views::transform([](const MTPPrepaidGiveaway &r) { + return Data::BoostPrepaidGiveaway{ + .months = r.data().vmonths().v, + .id = r.data().vid().v, + .quantity = r.data().vquantity().v, + .date = QDateTime::fromSecsSinceEpoch( + r.data().vdate().v), + }; + }) | ranges::to_vector; + } + + using namespace Data; + requestBoosts({ .gifts = false }, [=](BoostsListSlice &&slice) { + _boostStatus.firstSliceBoosts = std::move(slice); + requestBoosts({ .gifts = true }, [=](BoostsListSlice &&s) { + _boostStatus.firstSliceGifts = std::move(s); + consumer.put_done(); + }); + }); + }).fail([=](const MTP::Error &error) { + consumer.put_error_copy(error.type()); + }).send(); + + return lifetime; + }; +} + +void Boosts::requestBoosts( + const Data::BoostsListSlice::OffsetToken &token, + Fn done) { + if (_requestId) { + return; + } + constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice); + constexpr auto kTlLimit = tl::make_int(kLimit); + const auto gifts = token.gifts; + _requestId = _api.request(MTPpremium_GetBoostsList( + gifts + ? MTP_flags(MTPpremium_GetBoostsList::Flag::f_gifts) + : MTP_flags(0), + _peer->input, + MTP_string(token.next), + token.next.isEmpty() ? kTlFirstSlice : kTlLimit + )).done([=](const MTPpremium_BoostsList &result) { + _requestId = 0; + + const auto &data = result.data(); + _peer->owner().processUsers(data.vusers()); + + auto list = std::vector(); + list.reserve(data.vboosts().v.size()); + constexpr auto kMonthsDivider = int(30 * 86400); + for (const auto &boost : data.vboosts().v) { + const auto &data = boost.data(); + const auto path = data.vused_gift_slug() + ? (u"giftcode/"_q + qs(data.vused_gift_slug()->v)) + : QString(); + auto giftCodeLink = !path.isEmpty() + ? Data::GiftCodeLink{ + _peer->session().createInternalLink(path), + _peer->session().createInternalLinkFull(path), + qs(data.vused_gift_slug()->v), + } + : Data::GiftCodeLink(); + list.push_back({ + data.is_gift(), + data.is_giveaway(), + data.is_unclaimed(), + qs(data.vid()), + data.vuser_id().value_or_empty(), + data.vgiveaway_msg_id() + ? FullMsgId{ _peer->id, data.vgiveaway_msg_id()->v } + : FullMsgId(), + QDateTime::fromSecsSinceEpoch(data.vdate().v), + QDateTime::fromSecsSinceEpoch(data.vexpires().v), + (data.vexpires().v - data.vdate().v) / kMonthsDivider, + std::move(giftCodeLink), + data.vmultiplier().value_or_empty(), + }); + } + done(Data::BoostsListSlice{ + .list = std::move(list), + .multipliedTotal = data.vcount().v, + .allLoaded = (data.vcount().v == data.vboosts().v.size()), + .token = Data::BoostsListSlice::OffsetToken{ + .next = data.vnext_offset() + ? qs(*data.vnext_offset()) + : QString(), + .gifts = gifts, + }, + }); + }).fail([=] { + _requestId = 0; + }).send(); +} + +Data::BoostStatus Boosts::boostStatus() const { + return _boostStatus; +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_statistics.h b/Telegram/SourceFiles/api/api_statistics.h new file mode 100644 index 00000000000000..f5360adb21600d --- /dev/null +++ b/Telegram/SourceFiles/api/api_statistics.h @@ -0,0 +1,133 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/timer.h" +#include "data/data_boosts.h" +#include "data/data_statistics.h" +#include "mtproto/sender.h" + +class ChannelData; +class PeerData; + +namespace Api { + +class StatisticsRequestSender { +protected: + explicit StatisticsRequestSender(not_null channel); + ~StatisticsRequestSender(); + + template < + typename Request, + typename = std::enable_if_t>, + typename = typename Request::Unboxed> + [[nodiscard]] auto makeRequest(Request &&request); + + [[nodiscard]] MTP::Sender &api() { + return _api; + } + [[nodiscard]] not_null channel() { + return _channel; + } + +private: + void checkRequests(); + + const not_null _channel; + MTP::Sender _api; + base::Timer _timer; + base::flat_map> _requests; + +}; + +class Statistics final : public StatisticsRequestSender { +public: + explicit Statistics(not_null channel); + + [[nodiscard]] rpl::producer request(); + using GraphResult = rpl::producer; + [[nodiscard]] GraphResult requestZoom( + const QString &token, + float64 x); + + [[nodiscard]] Data::ChannelStatistics channelStats() const; + [[nodiscard]] Data::SupergroupStatistics supergroupStats() const; + +private: + Data::ChannelStatistics _channelStats; + Data::SupergroupStatistics _supergroupStats; + + std::deque> _zoomDeque; + +}; + +class PublicForwards final : public StatisticsRequestSender { +public: + PublicForwards( + not_null channel, + Data::RecentPostId fullId); + + void request( + const Data::PublicForwardsSlice::OffsetToken &token, + Fn done); + +private: + const Data::RecentPostId _fullId; + mtpRequestId _requestId = 0; + int _lastTotal = 0; + +}; + +class MessageStatistics final : public StatisticsRequestSender { +public: + explicit MessageStatistics( + not_null channel, + FullMsgId fullId); + explicit MessageStatistics( + not_null channel, + FullStoryId storyId); + + void request(Fn done); + + [[nodiscard]] Data::PublicForwardsSlice firstSlice() const; + +private: + PublicForwards _publicForwards; + const FullMsgId _fullId; + const FullStoryId _storyId; + + Data::PublicForwardsSlice _firstSlice; + + mtpRequestId _requestId = 0; + +}; + +class Boosts final { +public: + explicit Boosts(not_null peer); + + [[nodiscard]] rpl::producer request(); + void requestBoosts( + const Data::BoostsListSlice::OffsetToken &token, + Fn done); + + [[nodiscard]] Data::BoostStatus boostStatus() const; + + static constexpr auto kFirstSlice = int(10); + static constexpr auto kLimit = int(40); + +private: + const not_null _peer; + Data::BoostStatus _boostStatus; + + MTP::Sender _api; + mtpRequestId _requestId = 0; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_text_entities.cpp b/Telegram/SourceFiles/api/api_text_entities.cpp index 1bfe1142f396af..6c434bc9bab332 100644 --- a/Telegram/SourceFiles/api/api_text_entities.cpp +++ b/Telegram/SourceFiles/api/api_text_entities.cpp @@ -114,6 +114,7 @@ EntitiesInText EntitiesFromMTP( 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, qs(d.vlanguage()) }); } break; + case mtpc_messageEntityBlockquote: { auto &d = entity.c_messageEntityBlockquote(); result.push_back({ EntityType::Blockquote, d.voffset().v, d.vlength().v }); } break; case mtpc_messageEntityBankCard: break; // Skipping cards. // #TODO entities case mtpc_messageEntitySpoiler: { auto &d = entity.c_messageEntitySpoiler(); result.push_back({ EntityType::Spoiler, d.voffset().v, d.vlength().v }); } break; case mtpc_messageEntityCustomEmoji: { @@ -142,6 +143,7 @@ MTPVector EntitiesToMTP( && entity.type() != EntityType::StrikeOut && entity.type() != EntityType::Code // #TODO entities && entity.type() != EntityType::Pre + && entity.type() != EntityType::Blockquote && entity.type() != EntityType::Spoiler && entity.type() != EntityType::MentionName && entity.type() != EntityType::CustomUrl @@ -170,6 +172,7 @@ MTPVector EntitiesToMTP( case EntityType::StrikeOut: v.push_back(MTP_messageEntityStrike(offset, length)); break; case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break; // #TODO entities case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break; + case EntityType::Blockquote: v.push_back(MTP_messageEntityBlockquote(offset, length)); break; case EntityType::Spoiler: v.push_back(MTP_messageEntitySpoiler(offset, length)); break; case EntityType::CustomEmoji: { if (const auto valid = CustomEmojiEntity(offset, length, entity.data())) { diff --git a/Telegram/SourceFiles/api/api_transcribes.cpp b/Telegram/SourceFiles/api/api_transcribes.cpp index 6a69b53d6bf132..d7064abcdfe8a0 100644 --- a/Telegram/SourceFiles/api/api_transcribes.cpp +++ b/Telegram/SourceFiles/api/api_transcribes.cpp @@ -7,13 +7,17 @@ For license and copyright information please follow this link: */ #include "api/api_transcribes.h" -#include "history/history_item.h" -#include "history/history.h" -#include "main/main_session.h" +#include "apiwrap.h" +#include "data/data_channel.h" #include "data/data_document.h" -#include "data/data_session.h" #include "data/data_peer.h" -#include "apiwrap.h" +#include "data/data_session.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_helpers.h" +#include "main/main_account.h" +#include "main/main_app_config.h" +#include "main/main_session.h" namespace Api { @@ -22,6 +26,52 @@ Transcribes::Transcribes(not_null api) , _api(&api->instance()) { } +bool Transcribes::freeFor(not_null item) const { + if (const auto channel = item->history()->peer->asMegagroup()) { + const auto owner = &channel->owner(); + return channel->levelHint() >= owner->groupFreeTranscribeLevel(); + } + return false; +} + +bool Transcribes::trialsSupport() { + if (!_trialsSupport) { + const auto count = _session->account().appConfig().get( + u"transcribe_audio_trial_weekly_number"_q, + 0); + const auto until = _session->account().appConfig().get( + u"transcribe_audio_trial_cooldown_until"_q, + 0); + _trialsSupport = (count > 0) || (until > 0); + } + return *_trialsSupport; +} + +TimeId Transcribes::trialsRefreshAt() { + if (_trialsRefreshAt < 0) { + _trialsRefreshAt = _session->account().appConfig().get( + u"transcribe_audio_trial_cooldown_until"_q, + 0); + } + return _trialsRefreshAt; +} + +int Transcribes::trialsCount() { + if (_trialsCount < 0) { + _trialsCount = _session->account().appConfig().get( + u"transcribe_audio_trial_weekly_number"_q, + -1); + return std::max(_trialsCount, 0); + } + return _trialsCount; +} + +crl::time Transcribes::trialsMaxLengthMs() const { + return 1000 * _session->account().appConfig().get( + u"transcribe_audio_trial_duration_max"_q, + 300); +} + void Transcribes::toggle(not_null item) { const auto id = item->fullId(); auto i = _map.find(id); @@ -86,6 +136,23 @@ void Transcribes::load(not_null item) { MTP_int(item->id) )).done([=](const MTPmessages_TranscribedAudio &result) { const auto &data = result.data(); + + { + const auto trialsCountChanged = data.vtrial_remains_num() + && (_trialsCount != data.vtrial_remains_num()->v); + if (trialsCountChanged) { + _trialsCount = data.vtrial_remains_num()->v; + } + const auto refreshAtChanged = data.vtrial_remains_until_date() + && (_trialsRefreshAt != data.vtrial_remains_until_date()->v); + if (refreshAtChanged) { + _trialsRefreshAt = data.vtrial_remains_until_date()->v; + } + if (trialsCountChanged) { + ShowTrialTranscribesToast(_trialsCount, _trialsRefreshAt); + } + } + auto &entry = _map[id]; entry.requestId = 0; entry.pending = data.is_pending(); diff --git a/Telegram/SourceFiles/api/api_transcribes.h b/Telegram/SourceFiles/api/api_transcribes.h index 2f8bb22349058b..b074e42f13f3b5 100644 --- a/Telegram/SourceFiles/api/api_transcribes.h +++ b/Telegram/SourceFiles/api/api_transcribes.h @@ -36,12 +36,23 @@ class Transcribes final { void apply(const MTPDupdateTranscribedAudio &update); + [[nodiscard]] bool freeFor(not_null item) const; + + [[nodiscard]] bool trialsSupport(); + [[nodiscard]] TimeId trialsRefreshAt(); + [[nodiscard]] int trialsCount(); + [[nodiscard]] crl::time trialsMaxLengthMs() const; + private: void load(not_null item); const not_null _session; MTP::Sender _api; + int _trialsCount = -1; + std::optional _trialsSupport; + TimeId _trialsRefreshAt = -1; + base::flat_map _map; base::flat_map _ids; diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 78b286db074d1a..2b0450374f7c93 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -22,6 +22,7 @@ For license and copyright information please follow this link: #include "mtproto/mtproto_dc_options.h" #include "data/notify/data_notify_settings.h" #include "data/stickers/data_stickers.h" +#include "data/data_saved_messages.h" #include "data/data_session.h" #include "data/data_user.h" #include "data/data_chat.h" @@ -44,6 +45,7 @@ For license and copyright information please follow this link: #include "lang/lang_cloud_manager.h" #include "history/history.h" #include "history/history_item.h" +#include "history/history_item_helpers.h" #include "history/history_unread_things.h" #include "core/application.h" #include "storage/storage_account.h" @@ -942,7 +944,9 @@ void Updates::updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline) { } const auto self = session().user(); - self->onlineTill = base::unixtime::now() + (isOnline ? (config.onlineUpdatePeriod / 1000) : -1); + const auto onlineFor = (config.onlineUpdatePeriod / 1000); + self->updateLastseen(Data::LastseenStatus::OnlineTill( + base::unixtime::now() + (isOnline ? onlineFor : -1))); session().changes().peerUpdated( self, Data::PeerUpdate::Flag::OnlineStatus); @@ -1110,7 +1114,9 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { (d.is_out() ? peerToMTP(_session->userPeerId()) : MTP_peerUser(d.vuser_id())), + MTPint(), // from_boosts_applied MTP_peerUser(d.vuser_id()), + MTPPeer(), // saved_peer_id d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(), MTP_long(d.vvia_bot_id().value_or_empty()), d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(), @@ -1141,7 +1147,9 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { MTP_flags(flags), d.vid(), MTP_peerUser(d.vfrom_id()), + MTPint(), // from_boosts_applied MTP_peerChat(d.vchat_id()), + MTPPeer(), // saved_peer_id d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(), MTP_long(d.vvia_bot_id().value_or_empty()), d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(), @@ -1202,12 +1210,13 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) { item->markMediaAndMentionRead(); _session->data().requestItemRepaint(item); - if (item->out() - && item->history()->peer->isUser() - && !requestingDifference()) { - item->history()->peer->asUser()->madeAction( - base::unixtime::now()); + if (item->out()) { + const auto user = item->history()->peer->asUser(); + if (user && !requestingDifference()) { + user->madeAction(base::unixtime::now()); + } } + ClearMediaAsExpired(item); } } else { // Perhaps it was an unread mention! @@ -1845,23 +1854,13 @@ void Updates::feedUpdate(const MTPUpdate &update) { case mtpc_updateUserStatus: { auto &d = update.c_updateUserStatus(); - if (auto user = session().data().userLoaded(d.vuser_id())) { - switch (d.vstatus().type()) { - case mtpc_userStatusEmpty: user->onlineTill = 0; break; - case mtpc_userStatusRecently: - if (user->onlineTill > -10) { // don't modify pseudo-online - user->onlineTill = -2; - } - break; - case mtpc_userStatusLastWeek: user->onlineTill = -3; break; - case mtpc_userStatusLastMonth: user->onlineTill = -4; break; - case mtpc_userStatusOffline: user->onlineTill = d.vstatus().c_userStatusOffline().vwas_online().v; break; - case mtpc_userStatusOnline: user->onlineTill = d.vstatus().c_userStatusOnline().vexpires().v; break; + if (const auto user = session().data().userLoaded(d.vuser_id())) { + const auto now = LastseenFromMTP(d.vstatus(), user->lastseen()); + if (user->updateLastseen(now)) { + session().changes().peerUpdated( + user, + Data::PeerUpdate::Flag::OnlineStatus); } - session().changes().peerUpdated( - user, - Data::PeerUpdate::Flag::OnlineStatus); - session().data().maybeStopWatchForOffline(user); } if (UserId(d.vuser_id()) == session().userId()) { if (d.vstatus().type() == mtpc_userStatusOffline @@ -1991,6 +1990,19 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } break; + case mtpc_updatePeerWallpaper: { + const auto &d = update.c_updatePeerWallpaper(); + if (const auto peer = session().data().peerLoaded(peerFromMTP(d.vpeer()))) { + if (const auto paper = d.vwallpaper()) { + peer->setWallPaper( + Data::WallPaper::Create(&session(), *paper), + d.is_wallpaper_overridden()); + } else { + peer->setWallPaper({}); + } + } + } break; + case mtpc_updateBotCommands: { const auto &d = update.c_updateBotCommands(); if (const auto peer = session().data().peerLoaded(peerFromMTP(d.vpeer()))) { @@ -2071,7 +2083,10 @@ void Updates::feedUpdate(const MTPUpdate &update) { windows.front()->window().show(Ui::MakeInformBox(text)); } } else { - session().data().serviceNotification(text, d.vmedia()); + session().data().serviceNotification( + text, + d.vmedia(), + d.is_invert_media()); session().api().authorizations().reload(); } } break; @@ -2188,6 +2203,16 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } break; + case mtpc_updatePinnedSavedDialogs: { + session().data().savedMessages().apply( + update.c_updatePinnedSavedDialogs()); + } break; + + case mtpc_updateSavedDialogPinned: { + session().data().savedMessages().apply( + update.c_updateSavedDialogPinned()); + } break; + case mtpc_updateChannel: { auto &d = update.c_updateChannel(); if (const auto channel = session().data().channelLoaded(d.vchannel_id())) { @@ -2323,6 +2348,14 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } break; + case mtpc_updateChannelViewForumAsMessages: { + const auto &d = update.c_updateChannelViewForumAsMessages(); + const auto id = ChannelId(d.vchannel_id()); + if (const auto channel = session().data().channelLoaded(id)) { + channel->setViewAsMessagesFlag(mtpIsTrue(d.venabled())); + } + } break; + // Pinned message. case mtpc_updatePinnedMessages: { const auto &d = update.c_updatePinnedMessages(); @@ -2469,6 +2502,10 @@ void Updates::feedUpdate(const MTPUpdate &update) { session().data().reactions().refreshRecentDelayed(); } break; + case mtpc_updateSavedReactionTags: { + session().data().reactions().refreshMyTagsDelayed(); + } break; + ////// Cloud saved GIFs case mtpc_updateSavedGifs: { session().data().stickers().setLastSavedGifsUpdate(0); diff --git a/Telegram/SourceFiles/api/api_websites.cpp b/Telegram/SourceFiles/api/api_websites.cpp index 855056675f5237..2ea6b2a094a1f0 100644 --- a/Telegram/SourceFiles/api/api_websites.cpp +++ b/Telegram/SourceFiles/api/api_websites.cpp @@ -17,11 +17,7 @@ For license and copyright information please follow this link: namespace Api { namespace { -constexpr auto TestApiId = 17349; -constexpr auto SnapApiId = 611335; -constexpr auto DesktopApiId = 2040; - -Websites::Entry ParseEntry( +[[nodiscard]] Websites::Entry ParseEntry( not_null owner, const MTPDwebAuthorization &data) { auto result = Websites::Entry{ diff --git a/Telegram/SourceFiles/api/api_who_reacted.cpp b/Telegram/SourceFiles/api/api_who_reacted.cpp index f32358c0604b3a..27134dea98fe8c 100644 --- a/Telegram/SourceFiles/api/api_who_reacted.cpp +++ b/Telegram/SourceFiles/api/api_who_reacted.cpp @@ -7,6 +7,7 @@ For license and copyright information please follow this link: */ #include "api/api_who_reacted.h" +#include "api/api_global_privacy.h" #include "history/history_item.h" #include "history/history.h" #include "data/stickers/data_custom_emoji.h" @@ -19,6 +20,7 @@ For license and copyright information please follow this link: #include "data/data_session.h" #include "data/data_media_types.h" #include "data/data_message_reaction_id.h" +#include "data/data_peer_values.h" #include "lang/lang_keys.h" #include "main/main_app_config.h" #include "main/main_session.h" @@ -36,10 +38,11 @@ namespace { constexpr auto kContextReactionsLimit = 50; using Data::ReactionId; +using WhoReadState = Ui::WhoReadState; struct Peers { std::vector list; - bool unknown = false; + WhoReadState state = WhoReadState::Empty; friend inline bool operator==( const Peers &a, @@ -59,7 +62,7 @@ struct PeersWithReactions { std::vector list; std::vector read; int fullReactionsCount = 0; - bool unknown = false; + WhoReadState state = WhoReadState::Empty; friend inline bool operator==( const PeersWithReactions &a, @@ -68,7 +71,7 @@ struct PeersWithReactions { struct CachedRead { CachedRead() - : data(Peers{ .unknown = true }) { + : data(Peers{ .state = WhoReadState::Unknown }) { } rpl::variable data; mtpRequestId requestId = 0; @@ -76,7 +79,7 @@ struct CachedRead { struct CachedReacted { CachedReacted() - : data(PeersWithReactions{ .unknown = true }) { + : data(PeersWithReactions{ .state = WhoReadState::Unknown }) { } rpl::variable data; mtpRequestId requestId = 0; @@ -186,6 +189,27 @@ struct State { context->cachedReacted.erase(j); } }, context->subscriptions[session]); + Data::AmPremiumValue( + session + ) | rpl::skip(1) | rpl::filter( + rpl::mappers::_1 + ) | rpl::start_with_next([=] { + for (auto &[item, cache] : context->cachedRead) { + if (cache.data.current().state == Ui::WhoReadState::MyHidden) { + cache.data = Peers{ .state = Ui::WhoReadState::Unknown }; + } + } + }, context->subscriptions[session]); + session->api().globalPrivacy().hideReadTime( + ) | rpl::skip(1) | rpl::filter( + !rpl::mappers::_1 + ) | rpl::start_with_next([=] { + for (auto &[item, cache] : context->cachedRead) { + if (cache.data.current().state == Ui::WhoReadState::MyHidden) { + cache.data = Peers{ .state = Ui::WhoReadState::Unknown }; + } + } + }, context->subscriptions[session]); return context; } @@ -222,7 +246,38 @@ struct State { } const auto context = PreparedContextAt(weak.data(), session); auto &entry = context->cacheRead(item); - if (!entry.requestId) { + if (entry.requestId) { + } else if (const auto user = item->history()->peer->asUser()) { + entry.requestId = session->api().request( + MTPmessages_GetOutboxReadDate( + user->input, + MTP_int(item->id) + ) + ).done([=](const MTPOutboxReadDate &result) { + const auto &data = result.data(); + auto &entry = context->cacheRead(item); + entry.requestId = 0; + auto parsed = Peers(); + parsed.list.push_back({ + .peer = user->id, + .date = data.vdate().v, + }); + entry.data = std::move(parsed); + }).fail([=](const MTP::Error &error) { + auto &entry = context->cacheRead(item); + entry.requestId = 0; + if (entry.data.current().state == WhoReadState::Unknown) { + const auto &text = error.type(); + entry.data = (text == u"YOUR_PRIVACY_RESTRICTED"_q) + ? Peers{ .state = WhoReadState::MyHidden } + : (text == u"USER_PRIVACY_RESTRICTED"_q) + ? Peers{ .state = WhoReadState::HisHidden } + : (text == u"MESSAGE_TOO_OLD"_q) + ? Peers{ .state = WhoReadState::TooOld } + : Peers{ .state = WhoReadState::Empty }; + } + }).send(); + } else { entry.requestId = session->api().request( MTPmessages_GetMessageReadParticipants( item->history()->peer->input, @@ -243,8 +298,8 @@ struct State { }).fail([=] { auto &entry = context->cacheRead(item); entry.requestId = 0; - if (entry.data.current().unknown) { - entry.data = Peers(); + if (entry.data.current().state == WhoReadState::Unknown) { + entry.data = Peers{ .state = WhoReadState::Empty }; } }).send(); } @@ -258,7 +313,7 @@ struct State { .list = peers.list | ranges::views::transform([](WhoReadPeer peer) { return PeerWithReaction{ .peerWithDate = peer }; }) | ranges::to_vector, - .unknown = peers.unknown, + .state = peers.state, }; result.read = std::move(peers.list); return result; @@ -319,8 +374,10 @@ struct State { }).fail([=] { auto &entry = context->cacheReacted(item, reaction); entry.requestId = 0; - if (entry.data.current().unknown) { - entry.data = PeersWithReactions(); + if (entry.data.current().state == WhoReadState::Unknown) { + entry.data = PeersWithReactions{ + .state = WhoReadState::Empty, + }; } }).send(); } @@ -336,8 +393,9 @@ struct State { WhoReactedIds(item, {}, context), WhoReadIds(item, context) ) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) { - if (reacted.unknown || read.unknown) { - return PeersWithReactions{ .unknown = true }; + if (reacted.state == WhoReadState::Unknown + || read.state == WhoReadState::Unknown) { + return PeersWithReactions{ .state = WhoReadState::Unknown}; } auto &list = reacted.list; for (const auto &peerWithDate : read.list) { @@ -531,16 +589,17 @@ rpl::producer WhoReacted( std::move( idsWithReactions ) | rpl::start_with_next([=](PeersWithReactions &&peers) { - if (peers.unknown) { + if (peers.state == WhoReadState::Unknown) { state->userpics.clear(); consumer.put_next(Ui::WhoReadContent{ .type = state->current.type, .fullReactionsCount = state->current.fullReactionsCount, .fullReadCount = state->current.fullReadCount, - .unknown = true, + .state = WhoReadState::Unknown, }); return; } + state->current.state = peers.state; state->current.fullReadCount = int(peers.read.size()); state->current.fullReactionsCount = peers.fullReactionsCount; if (whoReadIds) { @@ -631,6 +690,22 @@ bool WhoReadExists(not_null item) { } const auto history = item->history(); const auto peer = history->peer; + if (const auto user = peer->asUser()) { + if (user->isSelf() + || user->isBot() + || user->isServiceUser() + || user->readDatesPrivate()) { + return false; + } + const auto &appConfig = peer->session().account().appConfig(); + const auto expirePeriod = appConfig.get( + "pm_read_date_expire_period", + 7 * 86400); + if (item->date() + int64(expirePeriod) <= int64(base::unixtime::now())) { + return false; + } + return true; + } const auto chat = peer->asChat(); const auto megagroup = peer->asMegagroup(); if ((!chat && !megagroup) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index aa83e5f8d4ea70..6766efe8529db4 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -15,6 +15,7 @@ For license and copyright information please follow this link: #include "api/api_hash.h" #include "api/api_invite_links.h" #include "api/api_media.h" +#include "api/api_peer_colors.h" #include "api/api_peer_photo.h" #include "api/api_polls.h" #include "api/api_sending.h" @@ -33,50 +34,36 @@ For license and copyright information please follow this link: #include "api/api_user_names.h" #include "api/api_websites.h" #include "data/notify/data_notify_settings.h" -#include "data/stickers/data_stickers.h" -#include "data/data_drafts.h" #include "data/data_changes.h" -#include "data/data_photo.h" #include "data/data_web_page.h" #include "data/data_folder.h" #include "data/data_forum_topic.h" #include "data/data_forum.h" -#include "data/data_media_types.h" -#include "data/data_sparse_ids.h" +#include "data/data_saved_sublist.h" #include "data/data_search_controller.h" #include "data/data_scheduled_messages.h" -#include "data/data_channel_admins.h" #include "data/data_session.h" -#include "data/data_stories.h" #include "data/data_channel.h" #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 "data/data_wall_paper.h" -#include "data/stickers/data_stickers.h" -#include "dialogs/dialogs_key.h" #include "core/core_cloud_password.h" #include "core/application.h" #include "base/unixtime.h" #include "base/random.h" -#include "base/qt/qt_common_adapters.h" #include "base/call_delayed.h" #include "lang/lang_keys.h" -#include "mainwindow.h" #include "mainwidget.h" #include "boxes/add_contact_box.h" #include "mtproto/mtproto_config.h" #include "history/history.h" -#include "history/history_item.h" #include "history/history_item_components.h" #include "history/history_item_helpers.h" #include "main/main_session.h" #include "main/main_session_settings.h" #include "main/main_account.h" #include "ui/boxes/confirm_box.h" -#include "boxes/stickers_box.h" #include "boxes/sticker_set_box.h" #include "boxes/premium_limits_box.h" #include "window/notifications_manager.h" @@ -87,7 +74,6 @@ For license and copyright information please follow this link: #include "chat_helpers/message_field.h" #include "ui/item_text_options.h" #include "ui/text/text_utilities.h" -#include "ui/emoji_config.h" #include "ui/chat/attach/attach_prepare.h" #include "ui/toast/toast.h" #include "support/support_helper.h" @@ -95,9 +81,6 @@ For license and copyright information please follow this link: #include "storage/localimageloader.h" #include "storage/download_manager_mtproto.h" #include "storage/file_upload.h" -#include "storage/storage_facade.h" -#include "storage/storage_shared_media.h" -#include "storage/storage_media_prepare.h" #include "storage/storage_account.h" namespace { @@ -114,6 +97,7 @@ constexpr auto kStickersByEmojiInvalidateTimeout = crl::time(6 * 1000); constexpr auto kNotifySettingSaveTimeout = crl::time(1000); constexpr auto kDialogsFirstLoad = 20; constexpr auto kDialogsPerPage = 500; +constexpr auto kStatsSessionKillTimeout = 10 * crl::time(1000); using PhotoFileLocationId = Data::PhotoFileLocationId; using DocumentFileLocationId = Data::DocumentFileLocationId; @@ -146,6 +130,15 @@ void ShowChannelsLimitBox(not_null peer) { } } +[[nodiscard]] FileLoadTo FileLoadTaskOptions(const Api::SendAction &action) { + const auto peer = action.history->peer; + return FileLoadTo( + peer->id, + action.options, + action.replyTo, + action.replaceMediaOf); +} + } // namespace ApiWrap::ApiWrap(not_null session) @@ -159,6 +152,7 @@ ApiWrap::ApiWrap(not_null session) , _fileLoader(std::make_unique(kFileLoaderQueueStopTimeout)) , _topPromotionTimer([=] { refreshTopPromotion(); }) , _updateNotifyTimer([=] { sendNotifySettingsUpdates(); }) +, _statsSessionKillTimer([=] { checkStatsSessions(); }) , _authorizations(std::make_unique(this)) , _attachedStickers(std::make_unique(this)) , _blockedPeers(std::make_unique(this)) @@ -178,7 +172,8 @@ ApiWrap::ApiWrap(not_null session) , _transcribes(std::make_unique(this)) , _premium(std::make_unique(this)) , _usernames(std::make_unique(this)) -, _websites(std::make_unique(this)) { +, _websites(std::make_unique(this)) +, _peerColors(std::make_unique(this)) { crl::on_main(session, [=] { // You can't use _session->lifetime() in the constructor, // only queued, because it is not constructed yet. @@ -227,11 +222,11 @@ void ApiWrap::setupSupportMode() { void ApiWrap::requestChangelog( const QString &sinceVersion, Fn callback) { - request(MTPhelp_GetAppChangelog( - MTP_string(sinceVersion) - )).done( - callback - ).send(); + //request(MTPhelp_GetAppChangelog( + // MTP_string(sinceVersion) + //)).done( + // callback + //).send(); } void ApiWrap::refreshTopPromotion() { @@ -446,6 +441,26 @@ void ApiWrap::savePinnedOrder(not_null forum) { }).send(); } +void ApiWrap::savePinnedOrder(not_null saved) { + const auto &order = _session->data().pinnedChatsOrder(saved); + const auto input = [](Dialogs::Key key) { + if (const auto sublist = key.sublist()) { + return MTP_inputDialogPeer(sublist->peer()->input); + } + Unexpected("Key type in pinnedDialogsOrder()."); + }; + auto peers = QVector(); + peers.reserve(order.size()); + ranges::transform( + order, + ranges::back_inserter(peers), + input); + request(MTPmessages_ReorderPinnedSavedDialogs( + MTP_flags(MTPmessages_ReorderPinnedSavedDialogs::Flag::f_force), + MTP_vector(peers) + )).send(); +} + void ApiWrap::toggleHistoryArchived( not_null history, bool archived, @@ -776,10 +791,9 @@ QString ApiWrap::exportDirectMessageLink( QString ApiWrap::exportDirectStoryLink(not_null story) { const auto storyId = story->fullId(); - const auto user = story->peer()->asUser(); - Assert(user != nullptr); + const auto peer = story->peer(); const auto fallback = [&] { - const auto base = user->username(); + const auto base = peer->userName(); const auto story = QString::number(storyId.story); const auto query = base + "/s/" + story; return session().createInternalLinkFull(query); @@ -789,7 +803,7 @@ QString ApiWrap::exportDirectStoryLink(not_null story) { ? i->second : fallback(); request(MTPstories_ExportStoryLink( - story->peer()->asUser()->inputUser, + peer->input, MTP_int(story->id()) )).done([=](const MTPExportedStoryLink &result) { const auto link = qs(result.data().vlink()); @@ -1738,6 +1752,10 @@ void ApiWrap::joinChannel(not_null channel) { }).send(); _channelAmInRequests.emplace(channel, requestId); + + using Flag = ChannelDataFlag; + chatParticipants().loadSimilarChannels(channel); + channel->setFlags(channel->flags() | Flag::SimilarExpanded); } } @@ -1904,28 +1922,28 @@ void ApiWrap::saveDraftToCloudDelayed(not_null thread) { void ApiWrap::updatePrivacyLastSeens() { const auto now = base::unixtime::now(); - _session->data().enumerateUsers([&](UserData *user) { - if (user->isSelf() || !user->isLoaded()) { - return; - } - if (user->onlineTill <= 0) { - return; - } + if (!_session->premium()) { + _session->data().enumerateUsers([&](not_null user) { + if (user->isSelf() + || !user->isLoaded() + || user->lastseen().isHidden()) { + return; + } - if (user->onlineTill + 3 * 86400 >= now) { - user->onlineTill = -2; // recently - } else if (user->onlineTill + 7 * 86400 >= now) { - user->onlineTill = -3; // last week - } else if (user->onlineTill + 30 * 86400 >= now) { - user->onlineTill = -4; // last month - } else { - user->onlineTill = 0; - } - session().changes().peerUpdated( - user, - Data::PeerUpdate::Flag::OnlineStatus); - session().data().maybeStopWatchForOffline(user); - }); + const auto till = user->lastseen().onlineTill(); + user->updateLastseen((till + 3 * 86400 >= now) + ? Data::LastseenStatus::Recently(true) + : (till + 7 * 86400 >= now) + ? Data::LastseenStatus::WithinWeek(true) + : (till + 30 * 86400 >= now) + ? Data::LastseenStatus::WithinMonth(true) + : Data::LastseenStatus::LongAgo(true)); + session().changes().peerUpdated( + user, + Data::PeerUpdate::Flag::OnlineStatus); + session().data().maybeStopWatchForOffline(user); + }); + } if (_contactsStatusesRequestId) { request(_contactsStatusesRequestId).cancel(); @@ -1933,20 +1951,17 @@ void ApiWrap::updatePrivacyLastSeens() { _contactsStatusesRequestId = request(MTPcontacts_GetStatuses( )).done([=](const MTPVector &result) { _contactsStatusesRequestId = 0; - for (const auto &item : result.v) { - Assert(item.type() == mtpc_contactStatus); - auto &data = item.c_contactStatus(); - if (auto user = _session->data().userLoaded(data.vuser_id())) { - auto oldOnlineTill = user->onlineTill; - auto newOnlineTill = OnlineTillFromStatus( + for (const auto &status : result.v) { + const auto &data = status.data(); + const auto userId = UserId(data.vuser_id()); + if (const auto user = _session->data().userLoaded(userId)) { + const auto status = LastseenFromMTP( data.vstatus(), - oldOnlineTill); - if (oldOnlineTill != newOnlineTill) { - user->onlineTill = newOnlineTill; + user->lastseen()); + if (user->updateLastseen(status)) { session().changes().peerUpdated( user, Data::PeerUpdate::Flag::OnlineStatus); - session().data().maybeStopWatchForOffline(user); } } } @@ -1955,22 +1970,6 @@ void ApiWrap::updatePrivacyLastSeens() { }).send(); } -int ApiWrap::OnlineTillFromStatus( - const MTPUserStatus &status, - int currentOnlineTill) { - switch (status.type()) { - case mtpc_userStatusEmpty: return 0; - case mtpc_userStatusRecently: - // Don't modify pseudo-online. - return (currentOnlineTill > -10) ? -2 : currentOnlineTill; - case mtpc_userStatusLastWeek: return -3; - case mtpc_userStatusLastMonth: return -4; - case mtpc_userStatusOffline: return status.c_userStatusOffline().vwas_online().v; - case mtpc_userStatusOnline: return status.c_userStatusOnline().vexpires().v; - } - Unexpected("Bad UserStatus type."); -} - void ApiWrap::clearHistory(not_null peer, bool revoke) { deleteHistory(peer, true, revoke); } @@ -2054,13 +2053,13 @@ void ApiWrap::deleteHistory( void ApiWrap::applyUpdates( const MTPUpdates &updates, - uint64 sentMessageRandomId) { + uint64 sentMessageRandomId) const { this->updates().applyUpdates(updates, sentMessageRandomId); } int ApiWrap::applyAffectedHistory( PeerData *peer, - const MTPmessages_AffectedHistory &result) { + const MTPmessages_AffectedHistory &result) const { const auto &data = result.c_messages_affectedHistory(); if (const auto channel = peer ? peer->asChannel() : nullptr) { channel->ptsUpdateAndApply(data.vpts().v, data.vpts_count().v); @@ -2082,7 +2081,7 @@ void ApiWrap::applyAffectedMessages( } void ApiWrap::applyAffectedMessages( - const MTPmessages_AffectedMessages &result) { + const MTPmessages_AffectedMessages &result) const { const auto &data = result.c_messages_affectedMessages(); updates().updateAndApply(data.vpts().v, data.vpts_count().v); } @@ -2136,14 +2135,13 @@ void ApiWrap::saveDraftsToCloud() { auto flags = MTPmessages_SaveDraft::Flags(0); auto &textWithTags = cloudDraft->textWithTags; - if (cloudDraft->previewState != Data::PreviewState::Allowed) { + if (cloudDraft->webpage.removed) { flags |= MTPmessages_SaveDraft::Flag::f_no_webpage; + } else if (!cloudDraft->webpage.url.isEmpty()) { + flags |= MTPmessages_SaveDraft::Flag::f_media; } - if (cloudDraft->msgId) { - flags |= MTPmessages_SaveDraft::Flag::f_reply_to_msg_id; - } - if (cloudDraft->topicRootId) { - flags |= MTPmessages_SaveDraft::Flag::f_top_msg_id; + if (cloudDraft->reply.messageId || cloudDraft->reply.topicRootId) { + flags |= MTPmessages_SaveDraft::Flag::f_reply_to; } if (!textWithTags.tags.isEmpty()) { flags |= MTPmessages_SaveDraft::Flag::f_entities; @@ -2156,11 +2154,13 @@ void ApiWrap::saveDraftsToCloud() { history->startSavingCloudDraft(topicRootId); cloudDraft->saveRequestId = request(MTPmessages_SaveDraft( MTP_flags(flags), - MTP_int(cloudDraft->msgId), - MTP_int(cloudDraft->topicRootId), + ReplyToForMTP(history, cloudDraft->reply), history->peer->input, MTP_string(textWithTags.text), - entities + entities, + Data::WebPageForMTP( + cloudDraft->webpage, + textWithTags.text.isEmpty()) )).done([=](const MTPBool &result, const MTP::Response &response) { const auto requestId = response.requestId; history->finishSavingCloudDraft( @@ -2247,7 +2247,7 @@ void ApiWrap::gotStickerSet( } void ApiWrap::requestWebPageDelayed(not_null page) { - if (page->pendingTill <= 0) { + if (page->failed || !page->pendingTill) { return; } _webPagesPending.emplace(page, 0); @@ -2436,7 +2436,13 @@ void ApiWrap::refreshFileReference( }; v::match(origin.data, [&](Data::FileOriginMessage data) { if (const auto item = _session->data().message(data)) { - if (item->isScheduled()) { + const auto media = item->media(); + const auto storyId = media ? media->storyId() : FullStoryId(); + if (storyId) { + request(MTPstories_GetStoriesByID( + _session->data().peer(storyId.peer)->input, + MTP_vector(1, MTP_int(storyId.story)))); + } else if (item->isScheduled()) { const auto &scheduled = _session->data().scheduledMessages(); const auto realId = scheduled.lookupId(item); request(MTPmessages_GetScheduledMessages( @@ -2533,14 +2539,9 @@ void ApiWrap::refreshFileReference( }, [&](Data::FileOriginPremiumPreviews data) { request(MTPhelp_GetPremiumPromo()); }, [&](Data::FileOriginStory data) { - const auto user = _session->data().peer(data.peerId)->asUser(); - if (user) { - request(MTPstories_GetStoriesByID( - user->inputUser, - MTP_vector(1, MTP_int(data.storyId)))); - } else { - fail(); - } + request(MTPstories_GetStoriesByID( + _session->data().peer(data.peer)->input, + MTP_vector(1, MTP_int(data.story)))); }, [&](v::null_t) { fail(); }); @@ -2551,7 +2552,8 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &resu for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend();) { if (i->second == req) { if (i->first->pendingTill > 0) { - i->first->pendingTill = -1; + i->first->pendingTill = 0; + i->first->failed = 1; _session->data().notifyWebPageUpdateDelayed(i->first); } i = _webPagesPending.erase(i); @@ -2604,6 +2606,22 @@ void ApiWrap::setGroupStickerSet( _session->data().stickers().notifyUpdated(Data::StickersType::Stickers); } +void ApiWrap::setGroupEmojiSet( + not_null megagroup, + const StickerSetIdentifier &set) { + Expects(megagroup->mgInfo != nullptr); + + megagroup->mgInfo->emojiSet = set; + request(MTPchannels_SetEmojiStickers( + megagroup->inputChannel, + Data::InputStickerSet(set) + )).send(); + _session->changes().peerUpdated( + megagroup, + Data::PeerUpdate::Flag::EmojiSet); + _session->data().stickers().notifyUpdated(Data::StickersType::Emoji); +} + std::vector> *ApiWrap::stickersByEmoji( const QString &key) { const auto it = _stickersByEmoji.find(key); @@ -3374,7 +3392,6 @@ void ApiWrap::sendSharedContact( if (action.replyTo) { flags |= MessageFlag::HasReplyInfo; } - const auto replyHeader = NewMessageReplyHeader(action); FillMessagePostFlags(action, peer, flags); if (action.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; @@ -3426,7 +3443,7 @@ void ApiWrap::sendVoiceMessage( crl::time duration, const SendAction &action) { const auto caption = TextWithTags(); - const auto to = fileLoadTaskOptions(action); + const auto to = FileLoadTaskOptions(action); _fileLoader->addTask(std::make_unique( &session(), result, @@ -3444,7 +3461,7 @@ void ApiWrap::editMedia( if (list.files.empty()) return; auto &file = list.files.front(); - const auto to = fileLoadTaskOptions(action); + const auto to = FileLoadTaskOptions(action); _fileLoader->addTask(std::make_unique( &session(), file.path, @@ -3473,7 +3490,7 @@ void ApiWrap::sendFiles( sendMessage(std::move(message)); } - const auto to = fileLoadTaskOptions(action); + const auto to = FileLoadTaskOptions(action); if (album) { album->options = to.options; } @@ -3512,7 +3529,7 @@ void ApiWrap::sendFile( const QByteArray &fileContent, SendMediaType type, const SendAction &action) { - const auto to = fileLoadTaskOptions(action); + const auto to = FileLoadTaskOptions(action); auto caption = TextWithTags(); const auto spoiler = false; const auto information = nullptr; @@ -3578,14 +3595,15 @@ void ApiWrap::sendMessage(MessageToSend &&message) { action.generateLocal = true; sendAction(action); - const auto replyToId = action.replyTo.msgId; - const auto replyTo = replyToId - ? peer->owner().message(peer, replyToId) + const auto clearCloudDraft = action.clearDraft; + const auto draftTopicRootId = action.replyTo.topicRootId; + const auto replyTo = action.replyTo.messageId + ? peer->owner().message(action.replyTo.messageId) : nullptr; - const auto topicRootId = replyTo + const auto topicRootId = draftTopicRootId + ? draftTopicRootId + : replyTo ? replyTo->topicRootId() - : action.replyTo.topicRootId - ? action.replyTo.topicRootId : Data::ForumTopic::kGeneralId; const auto topic = peer->forumTopicFor(topicRootId); if (!(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer)) @@ -3608,7 +3626,13 @@ void ApiWrap::sendMessage(MessageToSend &&message) { auto &histories = history->owner().histories(); - while (TextUtilities::CutPart(sending, left, MaxMessageSize)) { + const auto exactWebPage = !message.webPage.url.isEmpty(); + auto isFirst = true; + while (TextUtilities::CutPart(sending, left, MaxMessageSize) + || (isFirst && exactWebPage)) { + TextUtilities::Trim(left); + const auto isLast = left.empty(); + auto newId = FullMsgId( peer->id, _session->data().nextLocalMessageId()); @@ -3617,31 +3641,59 @@ void ApiWrap::sendMessage(MessageToSend &&message) { TextUtilities::Trim(sending); _session->data().registerMessageRandomId(randomId, newId); - _session->data().registerMessageSentData(randomId, peer->id, sending.text); + _session->data().registerMessageSentData( + randomId, + peer->id, + sending.text); MTPstring msgText(MTP_string(sending.text)); auto flags = NewMessageFlags(peer); auto sendFlags = MTPmessages_SendMessage::Flags(0); + auto mediaFlags = MTPmessages_SendMedia::Flags(0); if (action.replyTo) { flags |= MessageFlag::HasReplyInfo; sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to; + mediaFlags |= MTPmessages_SendMedia::Flag::f_reply_to; } - const auto replyHeader = NewMessageReplyHeader(action); + const auto ignoreWebPage = message.webPage.removed + || (exactWebPage && !isLast); + const auto manualWebPage = exactWebPage + && !ignoreWebPage + && (message.webPage.manual || (isLast && !isFirst)); MTPMessageMedia media = MTP_messageMediaEmpty(); - if (message.webPageId == CancelledWebPageId) { + if (ignoreWebPage) { sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage; - } else if (message.webPageId) { - auto page = _session->data().webpage(message.webPageId); + } else if (exactWebPage) { + using PageFlag = MTPDmessageMediaWebPage::Flag; + using PendingFlag = MTPDwebPagePending::Flag; + const auto &fields = message.webPage; + const auto page = _session->data().webpage(fields.id); media = MTP_messageMediaWebPage( + MTP_flags(PageFlag() + | (manualWebPage ? PageFlag::f_manual : PageFlag()) + | (fields.forceLargeMedia + ? PageFlag::f_force_large_media + : PageFlag()) + | (fields.forceSmallMedia + ? PageFlag::f_force_small_media + : PageFlag())), MTP_webPagePending( - MTP_long(page->id), + MTP_flags(PendingFlag::f_url), + MTP_long(fields.id), + MTP_string(fields.url), MTP_int(page->pendingTill))); } const auto anonymousPost = peer->amAnonymous(); const auto silentPost = ShouldSendSilent(peer, action.options); FillMessagePostFlags(action, peer, flags); + if (exactWebPage && !ignoreWebPage && message.webPage.invert) { + flags |= MessageFlag::InvertMedia; + sendFlags |= MTPmessages_SendMessage::Flag::f_invert_media; + mediaFlags |= MTPmessages_SendMedia::Flag::f_invert_media; + } if (silentPost) { sendFlags |= MTPmessages_SendMessage::Flag::f_silent; + mediaFlags |= MTPmessages_SendMedia::Flag::f_silent; } const auto sentEntities = Api::EntitiesToMTP( _session, @@ -3649,13 +3701,13 @@ void ApiWrap::sendMessage(MessageToSend &&message) { Api::ConvertOption::SkipLocal); if (!sentEntities.v.isEmpty()) { sendFlags |= MTPmessages_SendMessage::Flag::f_entities; + mediaFlags |= MTPmessages_SendMedia::Flag::f_entities; } - const auto clearCloudDraft = action.clearDraft; - const auto topicRootId = action.replyTo.topicRootId; if (clearCloudDraft) { sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft; - history->clearCloudDraft(topicRootId); - history->startSavingCloudDraft(topicRootId); + mediaFlags |= MTPmessages_SendMedia::Flag::f_clear_draft; + history->clearCloudDraft(draftTopicRootId); + history->startSavingCloudDraft(draftTopicRootId); } const auto sendAs = action.options.sendAs; const auto messageFromId = sendAs @@ -3665,6 +3717,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { : _session->userPeerId(); if (sendAs) { sendFlags |= MTPmessages_SendMessage::Flag::f_send_as; + mediaFlags |= MTPmessages_SendMedia::Flag::f_send_as; } const auto messagePostAuthor = peer->isBroadcast() ? _session->user()->name() @@ -3672,6 +3725,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { if (action.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date; + mediaFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; } const auto viaBotId = UserId(); lastMessage = history->addNewLocalMessage( @@ -3685,27 +3739,18 @@ void ApiWrap::sendMessage(MessageToSend &&message) { sending, media, HistoryMessageMarkupData()); - histories.sendPreparedMessage( - history, - action.replyTo, - randomId, - Data::Histories::PrepareMessage( - MTP_flags(sendFlags), - peer->input, - Data::Histories::ReplyToPlaceholder(), - msgText, - MTP_long(randomId), - MTPReplyMarkup(), - sentEntities, - MTP_int(action.options.scheduled), - (sendAs ? sendAs->input : MTP_inputPeerEmpty()) - ), [=](const MTPUpdates &result, const MTP::Response &response) { + const auto done = [=]( + const MTPUpdates &result, + const MTP::Response &response) { if (clearCloudDraft) { history->finishSavingCloudDraft( - topicRootId, + draftTopicRootId, UnixtimeFromMsgId(response.outerMsgId)); } - }, [=](const MTP::Error &error, const MTP::Response &response) { + }; + const auto fail = [=]( + const MTP::Error &error, + const MTP::Response &response) { if (error.type() == u"MESSAGE_EMPTY"_q) { lastMessage->destroy(); } else { @@ -3713,10 +3758,47 @@ void ApiWrap::sendMessage(MessageToSend &&message) { } if (clearCloudDraft) { history->finishSavingCloudDraft( - topicRootId, + draftTopicRootId, UnixtimeFromMsgId(response.outerMsgId)); } - }); + }; + if (exactWebPage + && !ignoreWebPage + && (manualWebPage || sending.empty())) { + histories.sendPreparedMessage( + history, + action.replyTo, + randomId, + Data::Histories::PrepareMessage( + MTP_flags(mediaFlags), + peer->input, + Data::Histories::ReplyToPlaceholder(), + Data::WebPageForMTP(message.webPage, true), + msgText, + MTP_long(randomId), + MTPReplyMarkup(), + sentEntities, + MTP_int(message.action.options.scheduled), + (sendAs ? sendAs->input : MTP_inputPeerEmpty()) + ), done, fail); + } else { + histories.sendPreparedMessage( + history, + action.replyTo, + randomId, + Data::Histories::PrepareMessage( + MTP_flags(sendFlags), + peer->input, + Data::Histories::ReplyToPlaceholder(), + msgText, + MTP_long(randomId), + MTPReplyMarkup(), + sentEntities, + MTP_int(action.options.scheduled), + (sendAs ? sendAs->input : MTP_inputPeerEmpty()) + ), done, fail); + } + isFirst = false; } finishForwarding(action); @@ -3781,7 +3863,7 @@ void ApiWrap::sendInlineResult( ? (*localMessageId) : _session->data().nextLocalMessageId()); const auto randomId = base::RandomValue(); - const auto topicRootId = action.replyTo.msgId + const auto topicRootId = action.replyTo.messageId ? action.replyTo.topicRootId : 0; @@ -4106,15 +4188,6 @@ void ApiWrap::sendAlbumIfReady(not_null album) { }); } -FileLoadTo ApiWrap::fileLoadTaskOptions(const SendAction &action) const { - const auto peer = action.history->peer; - return FileLoadTo( - peer->id, - action.options, - action.replyTo, - action.replaceMediaOf); -} - void ApiWrap::reloadContactSignupSilent() { if (_contactSignupSilentRequestId) { return; @@ -4225,6 +4298,32 @@ void ApiWrap::saveSelfBio(const QString &text) { }).send(); } +void ApiWrap::registerStatsRequest(MTP::DcId dcId, mtpRequestId id) { + _statsRequests[dcId].emplace(id); +} + +void ApiWrap::unregisterStatsRequest(MTP::DcId dcId, mtpRequestId id) { + const auto i = _statsRequests.find(dcId); + Assert(i != end(_statsRequests)); + const auto removed = i->second.remove(id); + Assert(removed); + if (i->second.empty()) { + _statsSessionKillTimer.callOnce(kStatsSessionKillTimeout); + } +} + +void ApiWrap::checkStatsSessions() { + for (auto i = begin(_statsRequests); i != end(_statsRequests);) { + if (i->second.empty()) { + instance().killSession( + MTP::ShiftDcId(i->first, MTP::kStatsDcShift)); + i = _statsRequests.erase(i); + } else { + ++i; + } + } +} + Api::Authorizations &ApiWrap::authorizations() { return *_authorizations; } @@ -4304,3 +4403,7 @@ Api::Usernames &ApiWrap::usernames() { Api::Websites &ApiWrap::websites() { return *_websites; } + +Api::PeerColors &ApiWrap::peerColors() { + return *_peerColors; +} diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index f61eb0957e2b76..615960126c18a5 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -9,8 +9,6 @@ For license and copyright information please follow this link: #include "api/api_common.h" #include "base/timer.h" -#include "base/flat_map.h" -#include "base/flat_set.h" #include "mtproto/sender.h" #include "data/stickers/data_stickers_set.h" #include "data/data_messages.h" @@ -36,6 +34,7 @@ class Forum; class ForumTopic; class Thread; class Story; +class SavedMessages; } // namespace Data namespace InlineBots { @@ -73,6 +72,7 @@ class InviteLinks; class ViewsManager; class ConfirmPhone; class PeerPhoto; +class PeerColors; class Polls; class ChatParticipants; class UnreadThings; @@ -141,10 +141,10 @@ class ApiWrap final : public MTP::Sender { void applyUpdates( const MTPUpdates &updates, - uint64 sentMessageRandomId = 0); + uint64 sentMessageRandomId = 0) const; int applyAffectedHistory( PeerData *peer, // May be nullptr, like for deletePhoneCallHistory. - const MTPmessages_AffectedHistory &result); + const MTPmessages_AffectedHistory &result) const; void registerModifyRequest(const QString &key, mtpRequestId requestId); void clearModifyRequest(const QString &key); @@ -153,6 +153,7 @@ class ApiWrap final : public MTP::Sender { void savePinnedOrder(Data::Folder *folder); void savePinnedOrder(not_null forum); + void savePinnedOrder(not_null saved); void toggleHistoryArchived( not_null history, bool archived, @@ -245,6 +246,9 @@ class ApiWrap final : public MTP::Sender { void setGroupStickerSet( not_null megagroup, const StickerSetIdentifier &set); + void setGroupEmojiSet( + not_null megagroup, + const StickerSetIdentifier &set); [[nodiscard]] std::vector> *stickersByEmoji( const QString &key); @@ -257,10 +261,6 @@ class ApiWrap final : public MTP::Sender { void updateNotifySettingsDelayed(Data::DefaultNotify type); void saveDraftToCloudDelayed(not_null thread); - static int OnlineTillFromStatus( - const MTPUserStatus &status, - int currentOnlineTill); - void clearHistory(not_null peer, bool revoke); void deleteConversation(not_null peer, bool revoke); @@ -369,6 +369,9 @@ class ApiWrap final : public MTP::Sender { void saveSelfBio(const QString &text); + void registerStatsRequest(MTP::DcId dcId, mtpRequestId id); + void unregisterStatsRequest(MTP::DcId dcId, mtpRequestId id); + [[nodiscard]] Api::Authorizations &authorizations(); [[nodiscard]] Api::AttachedStickers &attachedStickers(); [[nodiscard]] Api::BlockedPeers &blockedPeers(); @@ -389,6 +392,7 @@ class ApiWrap final : public MTP::Sender { [[nodiscard]] Api::Premium &premium(); [[nodiscard]] Api::Usernames &usernames(); [[nodiscard]] Api::Websites &websites(); + [[nodiscard]] Api::PeerColors &peerColors(); void updatePrivacyLastSeens(); @@ -500,7 +504,7 @@ class ApiWrap final : public MTP::Sender { not_null peer, bool justClear, bool revoke); - void applyAffectedMessages(const MTPmessages_AffectedMessages &result); + void applyAffectedMessages(const MTPmessages_AffectedMessages &result) const; void deleteAllFromParticipantSend( not_null channel, @@ -529,7 +533,6 @@ class ApiWrap final : public MTP::Sender { Api::SendOptions options, uint64 randomId, Fn done = nullptr); - FileLoadTo fileLoadTaskOptions(const SendAction &action) const; void getTopPromotionDelayed(TimeId now, TimeId next); void topPromotionDone(const MTPhelp_PromoData &proxy); @@ -547,6 +550,8 @@ class ApiWrap final : public MTP::Sender { not_null channel); void migrateFail(not_null peer, const QString &error); + void checkStatsSessions(); + const not_null _session; base::flat_map _modifyRequests; @@ -683,6 +688,9 @@ class ApiWrap final : public MTP::Sender { QString requestedText; } _bio; + base::flat_map> _statsRequests; + base::Timer _statsSessionKillTimer; + const std::unique_ptr _authorizations; const std::unique_ptr _attachedStickers; const std::unique_ptr _blockedPeers; @@ -703,6 +711,7 @@ class ApiWrap final : public MTP::Sender { const std::unique_ptr _premium; const std::unique_ptr _usernames; const std::unique_ptr _websites; + const std::unique_ptr _peerColors; mtpRequestId _wallPaperRequestId = 0; QString _wallPaperSlug; diff --git a/Telegram/SourceFiles/boxes/about_box.h b/Telegram/SourceFiles/boxes/about_box.h index 3ec637de26c942..e03b9970eca45f 100644 --- a/Telegram/SourceFiles/boxes/about_box.h +++ b/Telegram/SourceFiles/boxes/about_box.h @@ -7,7 +7,7 @@ For license and copyright information please follow this link: */ #pragma once -#include "boxes/abstract_box.h" +#include "ui/layers/box_content.h" namespace Ui { class LinkButton; diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index aab01656dfbd6a..9819c855cbde64 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -11,12 +11,11 @@ For license and copyright information please follow this link: #include "base/call_delayed.h" #include "base/random.h" #include "ui/boxes/confirm_box.h" -#include "boxes/peer_list_controllers.h" +#include "boxes/abstract_box.h" #include "boxes/premium_limits_box.h" #include "boxes/peers/add_participants_box.h" #include "boxes/peers/edit_peer_common.h" #include "boxes/peers/edit_participant_box.h" -#include "boxes/peers/edit_participants_box.h" #include "core/application.h" #include "core/core_settings.h" #include "chat_helpers/emoji_suggestions_widget.h" @@ -26,34 +25,26 @@ For license and copyright information please follow this link: #include "menu/menu_ttl.h" #include "ui/controls/userpic_button.h" #include "ui/widgets/checkbox.h" -#include "ui/widgets/buttons.h" -#include "ui/widgets/labels.h" #include "ui/toast/toast.h" #include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/special_fields.h" #include "ui/widgets/popup_menu.h" #include "ui/text/format_values.h" -#include "ui/text/text_options.h" #include "ui/text/text_utilities.h" -#include "ui/unread_badge.h" -#include "ui/ui_utility.h" #include "ui/painter.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" #include "data/data_session.h" #include "data/data_changes.h" -#include "data/data_cloud_file.h" #include "apiwrap.h" #include "api/api_invite_links.h" #include "api/api_peer_photo.h" +#include "api/api_self_destruct.h" #include "main/main_session.h" #include "styles/style_info.h" #include "styles/style_layers.h" #include "styles/style_menu_icons.h" -#include "styles/style_boxes.h" -#include "styles/style_dialogs.h" -#include "styles/style_widgets.h" #include #include @@ -598,6 +589,8 @@ void GroupInfoBox::prepare() { addButton(tr::lng_cancel(), [this] { closeBox(); }); if (_type == Type::Group) { + _navigation->session().api().selfDestruct().reload(); + const auto top = addTopButton(st::infoTopBarMenu); const auto menu = top->lifetime().make_state>(); @@ -606,21 +599,21 @@ void GroupInfoBox::prepare() { top, st::popupMenuWithIcons); + const auto ttl = ttlPeriod(); const auto text = tr::lng_manage_messages_ttl_menu(tr::now) - + (_ttlPeriod - ? ('\t' + Ui::FormatTTLTiny(_ttlPeriod)) - : QString()); + + (ttl ? ('\t' + Ui::FormatTTLTiny(ttl)) : QString()); (*menu)->addAction( text, [=, show = uiShow()] { show->showBox(Box(TTLMenu::TTLBox, TTLMenu::Args{ .show = show, - .startTtl = _ttlPeriod, + .startTtl = ttlPeriod(), .about = nullptr, .callback = crl::guard(this, [=]( TimeId t, Fn close) { _ttlPeriod = t; + _ttlPeriodOverridden = true; close(); }), })); @@ -686,6 +679,13 @@ void GroupInfoBox::submitName() { } } +TimeId GroupInfoBox::ttlPeriod() const { + return _ttlPeriodOverridden + ? _ttlPeriod + : _navigation->session().api().selfDestruct() + .periodDefaultHistoryTTLCurrent(); +} + void GroupInfoBox::createGroup( QPointer selectUsersBox, const QString &title, @@ -704,15 +704,13 @@ void GroupInfoBox::createGroup( } } _creationRequestId = _api.request(MTPmessages_CreateChat( - MTP_flags(_ttlPeriod - ? MTPmessages_CreateChat::Flag::f_ttl_period - : MTPmessages_CreateChat::Flags(0)), + MTP_flags(MTPmessages_CreateChat::Flag::f_ttl_period), MTP_vector(inputs), MTP_string(title), - MTP_int(_ttlPeriod) + MTP_int(ttlPeriod()) )).done([=](const MTPUpdates &result) { auto image = _photo->takeResultImage(); - const auto period = _ttlPeriod; + const auto period = ttlPeriod(); const auto navigation = _navigation; const auto done = _done; @@ -798,16 +796,17 @@ void GroupInfoBox::createChannel( ? Flag::f_megagroup : Flag::f_broadcast) | ((_type == Type::Forum) ? Flag::f_forum : Flag()) - | ((_type == Type::Megagroup && _ttlPeriod) + | ((_type == Type::Megagroup) ? MTPchannels_CreateChannel::Flag::f_ttl_period : MTPchannels_CreateChannel::Flags(0)); + const auto ttl = ttlPeriod(); _creationRequestId = _api.request(MTPchannels_CreateChannel( MTP_flags(flags), MTP_string(title), MTP_string(description), MTPInputGeoPoint(), // geo_point MTPstring(), // address - MTP_int((_type == Type::Megagroup) ? _ttlPeriod : 0) + MTP_int((_type == Type::Megagroup) ? ttl : 0) )).done([=](const MTPUpdates &result) { _navigation->session().api().applyUpdates(result); @@ -840,8 +839,8 @@ void GroupInfoBox::createChannel( channel, { std::move(image) }); } - if (_ttlPeriod && channel->isMegagroup()) { - channel->setMessagesTTL(_ttlPeriod); + if (ttl && channel->isMegagroup()) { + channel->setMessagesTTL(ttl); } channel->session().api().requestFullPeer(channel); _createdChannel = channel; diff --git a/Telegram/SourceFiles/boxes/add_contact_box.h b/Telegram/SourceFiles/boxes/add_contact_box.h index 5e62331aac55b3..6d19a2d7f79019 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.h +++ b/Telegram/SourceFiles/boxes/add_contact_box.h @@ -7,7 +7,7 @@ For license and copyright information please follow this link: */ #pragma once -#include "boxes/abstract_box.h" +#include "ui/layers/box_content.h" #include "base/timer.h" #include "mtproto/sender.h" @@ -132,6 +132,8 @@ class GroupInfoBox : public Ui::BoxContent { void descriptionResized(); void updateMaxHeight(); + [[nodiscard]] TimeId ttlPeriod() const; + const not_null _navigation; MTP::Sender _api; @@ -150,6 +152,7 @@ class GroupInfoBox : public Ui::BoxContent { bool _creatingInviteLink = false; ChannelData *_createdChannel = nullptr; TimeId _ttlPeriod = 0; + bool _ttlPeriodOverridden = false; }; diff --git a/Telegram/SourceFiles/boxes/auto_download_box.cpp b/Telegram/SourceFiles/boxes/auto_download_box.cpp index b77cf4a4f158c6..3617a9eb65eb58 100644 --- a/Telegram/SourceFiles/boxes/auto_download_box.cpp +++ b/Telegram/SourceFiles/boxes/auto_download_box.cpp @@ -12,6 +12,7 @@ For license and copyright information please follow this link: #include "main/main_session_settings.h" #include "data/data_session.h" #include "data/data_auto_download.h" +#include "ui/vertical_list.h" #include "ui/widgets/continuous_sliders.h" #include "ui/widgets/buttons.h" #include "ui/wrap/vertical_layout.h" @@ -35,7 +36,6 @@ not_null AddSizeLimitSlider( not_null container, const base::flat_map &values, int64 defaultValue) { - using namespace Settings; using Pair = base::flat_map::value_type; const auto limits = Ui::CreateChild>( @@ -46,7 +46,7 @@ not_null AddSizeLimitSlider( [](Pair pair) { return pair.second; })->second; const auto startLimit = currentLimit ? currentLimit : defaultValue; const auto result = Ui::CreateChild(container.get(), startLimit); - AddButtonWithLabel( + Settings::AddButtonWithLabel( container, tr::lng_media_size_limit(), limits->events_starting_with_copy( @@ -109,11 +109,11 @@ void AutoDownloadBox::setupContent() { Type type, rpl::producer label) { const auto value = settings->bytesLimit(_source, type); - AddButton( + content->add(object_ptr( content, std::move(label), st::settingsButtonNoIcon - )->toggleOn( + ))->toggleOn( rpl::single(value > 0) )->toggledChanges( ) | rpl::start_with_next([=](bool enabled) { @@ -162,7 +162,7 @@ void AutoDownloadBox::setupContent() { *downloadValues, *autoPlayValues); auto allowMore = values | ranges::views::filter([&](Pair pair) { - const auto [type, enabled] = pair; + const auto &[type, enabled] = pair; const auto value = enabled ? limitByType(type) : 0; const auto old = settings->bytesLimit(_source, type); return (old < value); @@ -170,7 +170,7 @@ void AutoDownloadBox::setupContent() { return pair.first; }); const auto less = ranges::any_of(*autoPlayValues, [&](Pair pair) { - const auto [type, enabled] = pair; + const auto &[type, enabled] = pair; const auto value = enabled ? limitByType(type) : 0; return value < settings->bytesLimit(_source, type); }); @@ -179,7 +179,7 @@ void AutoDownloadBox::setupContent() { allowMore.end()); const auto changed = ranges::any_of(values, [&](Pair pair) { - const auto [type, enabled] = pair; + const auto &[type, enabled] = pair; const auto value = enabled ? limitByType(type) : 0; return value != settings->bytesLimit(_source, type); }); diff --git a/Telegram/SourceFiles/boxes/auto_download_box.h b/Telegram/SourceFiles/boxes/auto_download_box.h index 617a4e2038d367..d48b0da859d369 100644 --- a/Telegram/SourceFiles/boxes/auto_download_box.h +++ b/Telegram/SourceFiles/boxes/auto_download_box.h @@ -7,7 +7,7 @@ For license and copyright information please follow this link: */ #pragma once -#include "boxes/abstract_box.h" +#include "ui/layers/box_content.h" namespace Main { class Session; diff --git a/Telegram/SourceFiles/boxes/auto_lock_box.h b/Telegram/SourceFiles/boxes/auto_lock_box.h index b20cbfa2ba1b17..9243e802f21426 100644 --- a/Telegram/SourceFiles/boxes/auto_lock_box.h +++ b/Telegram/SourceFiles/boxes/auto_lock_box.h @@ -7,7 +7,7 @@ For license and copyright information please follow this link: */ #pragma once -#include "boxes/abstract_box.h" +#include "ui/layers/box_content.h" namespace Ui { class Radiobutton; diff --git a/Telegram/SourceFiles/boxes/background_box.cpp b/Telegram/SourceFiles/boxes/background_box.cpp index a7b92aa328af73..877cf556e65624 100644 --- a/Telegram/SourceFiles/boxes/background_box.cpp +++ b/Telegram/SourceFiles/boxes/background_box.cpp @@ -13,6 +13,7 @@ For license and copyright information please follow this link: #include "ui/chat/attach/attach_extensions.h" #include "ui/chat/chat_theme.h" #include "ui/ui_utility.h" +#include "ui/vertical_list.h" #include "main/main_session.h" #include "apiwrap.h" #include "mtproto/sender.h" @@ -24,7 +25,6 @@ For license and copyright information please follow this link: #include "data/data_document_media.h" #include "boxes/background_preview_box.h" #include "info/profile/info_profile_icon.h" -#include "settings/settings_common.h" #include "ui/boxes/confirm_box.h" #include "ui/widgets/buttons.h" #include "window/window_session_controller.h" @@ -129,6 +129,8 @@ class BackgroundBox::Inner final : public Ui::RpWidget { int row) const; void validatePaperThumbnail(const Paper &paper) const; + [[nodiscard]] bool forChannel() const; + const not_null _session; PeerData * const _forPeer = nullptr; @@ -165,9 +167,9 @@ void BackgroundBox::prepare() { auto wrap = object_ptr(this); const auto container = wrap.data(); - Settings::AddSkip(container); + Ui::AddSkip(container); - const auto button = container->add(Settings::CreateButton( + const auto button = container->add(object_ptr( container, tr::lng_settings_bg_from_file(), st::infoProfileButton)); @@ -176,12 +178,29 @@ void BackgroundBox::prepare() { st::infoIconMediaPhoto, st::infoSharedMediaButtonIconPosition); + if (forChannel() && _forPeer->wallPaper()) { + const auto remove = container->add(object_ptr( + container, + tr::lng_settings_bg_remove(), + st::infoBlockButton)); + object_ptr( + remove, + st::infoIconDeleteRed, + st::infoSharedMediaButtonIconPosition); + + remove->setClickedCallback([=] { + if (const auto resolved = _inner->resolveResetCustomPaper()) { + chosen(*resolved); + } + }); + } + button->setClickedCallback([=] { chooseFromFile(); }); - Settings::AddSkip(container); - Settings::AddDivider(container); + Ui::AddSkip(container); + Ui::AddDivider(container); _inner = container->add( object_ptr(this, &_controller->session(), _forPeer)); @@ -290,6 +309,23 @@ void BackgroundBox::chosen(const Data::WallPaper &paper) { closeBox(); } return; + } else if (forChannel()) { + if (_forPeer->wallPaper() && _forPeer->wallPaper()->equals(paper)) { + closeBox(); + return; + } + const auto &themes = _forPeer->owner().cloudThemes(); + for (const auto &theme : themes.chatThemes()) { + for (const auto &[type, themed] : theme.settings) { + if (themed.paper && themed.paper->equals(paper)) { + _controller->show(Box( + _controller, + Data::WallPaper::FromEmojiId(theme.emoticon), + BackgroundPreviewArgs{ _forPeer })); + return; + } + } + } } _controller->show(Box( _controller, @@ -310,12 +346,16 @@ void BackgroundBox::resetForPeer() { }).send(); const auto weak = Ui::MakeWeak(this); - _forPeer->setWallPaper(std::nullopt); + _forPeer->setWallPaper({}); if (weak) { _controller->finishChatThemeEdit(_forPeer); } } +bool BackgroundBox::forChannel() const { + return _forPeer && _forPeer->isChannel(); +} + void BackgroundBox::removePaper(const Data::WallPaper &paper) { const auto session = &_controller->session(); const auto remove = [=, weak = Ui::MakeWeak(this)](Fn &&close) { @@ -345,9 +385,16 @@ BackgroundBox::Inner::Inner( , _session(session) , _forPeer(forPeer) , _api(&_session->mtp()) -, _check(std::make_unique(st::overviewCheck, [=] { update(); })) { +, _check( + std::make_unique( + st::overviewCheck, + [=] { update(); })) { _check->setChecked(true, anim::type::instant); - resize(st::boxWideWidth, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding); + resize( + st::boxWideWidth, + (2 * (st::backgroundSize.height() + st::backgroundPadding) + + st::backgroundPadding)); + Window::Theme::IsNightModeValue( ) | rpl::start_with_next([=] { updatePapers(); @@ -364,21 +411,31 @@ BackgroundBox::Inner::Inner( _check->invalidateCache(); }, lifetime()); - using Update = Window::Theme::BackgroundUpdate; - Window::Theme::Background()->updates( - ) | rpl::start_with_next([=](const Update &update) { - if (update.type == Update::Type::New) { - sortPapers(); - requestPapers(); - this->update(); - } - }, lifetime()); - + if (forChannel()) { + _session->data().cloudThemes().chatThemesUpdated( + ) | rpl::start_with_next([=] { + updatePapers(); + }, lifetime()); + } else { + using Update = Window::Theme::BackgroundUpdate; + Window::Theme::Background()->updates( + ) | rpl::start_with_next([=](const Update &update) { + if (update.type == Update::Type::New) { + sortPapers(); + requestPapers(); + this->update(); + } + }, lifetime()); + } setMouseTracking(true); } void BackgroundBox::Inner::requestPapers() { + if (forChannel()) { + _session->data().cloudThemes().refreshChatThemes(); + return; + } _api.request(MTPaccount_GetWallPapers( MTP_long(_session->data().wallpapersHash()) )).done([=](const MTPaccount_WallPapers &result) { @@ -395,7 +452,7 @@ auto BackgroundBox::Inner::resolveResetCustomPaper() const } const auto nonCustom = Window::Theme::Background()->paper(); const auto themeEmoji = _forPeer->themeEmoji(); - if (themeEmoji.isEmpty()) { + if (forChannel() || themeEmoji.isEmpty()) { return nonCustom; } const auto &themes = _forPeer->owner().cloudThemes(); @@ -443,6 +500,8 @@ void BackgroundBox::Inner::pushCustomPapers() { } void BackgroundBox::Inner::sortPapers() { + Expects(!forChannel()); + const auto currentCustom = _forPeer ? _forPeer->wallPaper() : nullptr; _currentId = currentCustom ? currentCustom->id() @@ -472,23 +531,60 @@ void BackgroundBox::Inner::sortPapers() { } void BackgroundBox::Inner::updatePapers() { - if (_session->data().wallpapers().empty()) { - return; + if (forChannel()) { + if (_session->data().cloudThemes().chatThemes().empty()) { + return; + } + } else { + if (_session->data().wallpapers().empty()) { + return; + } } _over = _overDown = Selection(); - _papers = _session->data().wallpapers( - ) | ranges::views::filter([&](const Data::WallPaper &paper) { - return (!paper.isPattern() || !paper.backgroundColors().empty()) - && (!_forPeer - || (!Data::IsDefaultWallPaper(paper) - && (Data::IsCloudWallPaper(paper) - || Data::IsCustomWallPaper(paper)))); - }) | ranges::views::transform([](const Data::WallPaper &paper) { - return Paper{ paper }; - }) | ranges::to_vector; - pushCustomPapers(); - sortPapers(); + const auto was = base::take(_papers); + if (forChannel()) { + const auto now = _forPeer->wallPaper(); + const auto &list = _session->data().cloudThemes().chatThemes(); + if (list.empty()) { + return; + } + using Type = Data::CloudThemeType; + const auto type = Window::Theme::IsNightMode() + ? Type::Dark + : Type::Light; + _papers.reserve(list.size() + 1); + const auto nowEmojiId = now ? now->emojiId() : QString(); + if (!now || !now->emojiId().isEmpty()) { + _papers.push_back({ Window::Theme::Background()->paper() }); + _currentId = _papers.back().data.id(); + } else { + _papers.push_back({ *now }); + _currentId = now->id(); + } + for (const auto &theme : list) { + const auto i = theme.settings.find(type); + if (i != end(theme.settings) && i->second.paper) { + _papers.push_back({ *i->second.paper }); + if (nowEmojiId == theme.emoticon) { + _currentId = _papers.back().data.id(); + } + } + } + } else { + _papers = _session->data().wallpapers( + ) | ranges::views::filter([&](const Data::WallPaper &paper) { + return (!paper.isPattern() || !paper.backgroundColors().empty()) + && (!_forPeer + || (!Data::IsDefaultWallPaper(paper) + && (Data::IsCloudWallPaper(paper) + || Data::IsCustomWallPaper(paper)))); + }) | ranges::views::transform([](const Data::WallPaper &paper) { + return Paper{ paper }; + }) | ranges::to_vector; + pushCustomPapers(); + sortPapers(); + } resizeToContentAndPreload(); } @@ -587,6 +683,10 @@ void BackgroundBox::Inner::validatePaperThumbnail( paper.thumbnail.setDevicePixelRatio(cRetinaFactor()); } +bool BackgroundBox::Inner::forChannel() const { + return _forPeer && _forPeer->isChannel(); +} + void BackgroundBox::Inner::paintPaper( QPainter &p, const Paper &paper, @@ -604,7 +704,8 @@ void BackgroundBox::Inner::paintPaper( const auto checkLeft = x + st::backgroundSize.width() - st::overviewCheckSkip - st::overviewCheck.size; const auto checkTop = y + st::backgroundSize.height() - st::overviewCheckSkip - st::overviewCheck.size; _check->paint(p, checkLeft, checkTop, width()); - } else if (Data::IsCloudWallPaper(paper.data) + } else if (!forChannel() + && Data::IsCloudWallPaper(paper.data) && !Data::IsDefaultWallPaper(paper.data) && !Data::IsLegacy2DefaultWallPaper(paper.data) && !Data::IsLegacy3DefaultWallPaper(paper.data) @@ -642,7 +743,8 @@ void BackgroundBox::Inner::mouseMoveEvent(QMouseEvent *e) { - st::stickerPanDeleteIconBg.width(); const auto deleteBottom = row * (height + skip) + skip + st::stickerPanDeleteIconBg.height(); - const auto inDelete = (x >= deleteLeft) + const auto inDelete = !forChannel() + && (x >= deleteLeft) && (y < deleteBottom) && Data::IsCloudWallPaper(data) && !Data::IsDefaultWallPaper(data) diff --git a/Telegram/SourceFiles/boxes/background_box.h b/Telegram/SourceFiles/boxes/background_box.h index 4e0bc3727f5b3e..8aa63ff9164ab8 100644 --- a/Telegram/SourceFiles/boxes/background_box.h +++ b/Telegram/SourceFiles/boxes/background_box.h @@ -7,7 +7,7 @@ For license and copyright information please follow this link: */ #pragma once -#include "boxes/abstract_box.h" +#include "ui/layers/box_content.h" class PeerData; @@ -38,6 +38,7 @@ class BackgroundBox : public Ui::BoxContent { const Data::WallPaper &paper) const; void removePaper(const Data::WallPaper &paper); void resetForPeer(); + [[nodiscard]] bool forChannel() const; void chooseFromFile(); diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index c83af953d3ca0f..6dadab562fd748 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -7,10 +7,14 @@ For license and copyright information please follow this link: */ #include "boxes/background_preview_box.h" +#include "base/unixtime.h" +#include "boxes/peers/edit_peer_color_box.h" +#include "boxes/premium_preview_box.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "window/themes/window_theme.h" #include "ui/boxes/confirm_box.h" +#include "ui/boxes/boost_box.h" #include "ui/controls/chat_service_checkbox.h" #include "ui/chat/chat_theme.h" #include "ui/chat/chat_style.h" @@ -18,13 +22,17 @@ For license and copyright information please follow this link: #include "ui/image/image.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/continuous_sliders.h" +#include "ui/wrap/fade_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/painter.h" +#include "ui/vertical_list.h" #include "ui/ui_utility.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_item_helpers.h" #include "history/view/history_view_message.h" +#include "main/main_account.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "apiwrap.h" #include "data/data_session.h" @@ -33,17 +41,15 @@ For license and copyright information please follow this link: #include "data/data_document_media.h" #include "data/data_document_resolver.h" #include "data/data_file_origin.h" -#include "base/unixtime.h" -#include "boxes/background_preview_box.h" -#include "window/window_session_controller.h" -#include "window/themes/window_themes_embedded.h" -#include "settings/settings_common.h" +#include "data/data_peer_values.h" +#include "settings/settings_premium.h" #include "storage/file_upload.h" #include "storage/localimageloader.h" +#include "window/window_session_controller.h" +#include "window/themes/window_themes_embedded.h" #include "styles/style_chat.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" -#include "styles/style_settings.h" #include #include @@ -51,7 +57,6 @@ For license and copyright information please follow this link: namespace { constexpr auto kMaxWallPaperSlugLength = 255; -constexpr auto kDefaultDimming = 50; [[nodiscard]] bool IsValidWallPaperSlug(const QString &slug) { if (slug.isEmpty() || slug.size() > kMaxWallPaperSlugLength) { @@ -157,6 +162,25 @@ constexpr auto kDefaultDimming = 50; return result; } +[[nodiscard]] Data::WallPaper Resolve( + not_null session, + const Data::WallPaper &paper, + bool dark) { + if (paper.emojiId().isEmpty()) { + return paper; + } + const auto &themes = session->data().cloudThemes(); + if (const auto theme = themes.themeForEmoji(paper.emojiId())) { + using Type = Data::CloudThemeType; + const auto type = dark ? Type::Dark : Type::Light; + const auto i = theme->settings.find(type); + if (i != end(theme->settings) && i->second.paper) { + return *i->second.paper; + } + } + return paper; +} + } // namespace struct BackgroundPreviewBox::OverridenStyle { @@ -175,7 +199,8 @@ BackgroundPreviewBox::BackgroundPreviewBox( , _controller(controller) , _forPeer(args.forPeer) , _fromMessageId(args.fromMessageId) -, _chatStyle(std::make_unique()) +, _chatStyle(std::make_unique( + controller->session().colorIndicesValue())) , _serviceHistory(_controller->session().data().history( PeerData::kServiceNotificationsId)) , _service(nullptr) @@ -193,15 +218,17 @@ BackgroundPreviewBox::BackgroundPreviewBox( ? tr::lng_background_apply2(tr::now) : tr::lng_background_text2(tr::now)), true)) -, _paper(paper) +, _paperEmojiId(paper.emojiId()) +, _paper( + Resolve(&controller->session(), paper, Window::Theme::IsNightMode())) , _media(_paper.document() ? _paper.document()->createMediaView() : nullptr) , _radial([=](crl::time now) { radialAnimationCallback(now); }) , _appNightMode(Window::Theme::IsNightModeValue()) , _boxDarkMode(_appNightMode.current()) -, _dimmingIntensity(std::clamp(paper.patternIntensity(), 0, 100)) +, _dimmingIntensity(std::clamp(_paper.patternIntensity(), 0, 100)) , _dimmed(_forPeer - && (paper.document() || paper.localThumbnail()) - && !paper.isPattern()) { + && (_paper.document() || _paper.localThumbnail()) + && !_paper.isPattern()) { if (_media) { _media->thumbnailWanted(_paper.fileOrigin()); } @@ -241,7 +268,36 @@ BackgroundPreviewBox::BackgroundPreviewBox( BackgroundPreviewBox::~BackgroundPreviewBox() = default; +void BackgroundPreviewBox::recreate(bool dark) { + _paper = Resolve( + &_controller->session(), + Data::WallPaper::FromEmojiId(_paperEmojiId), + dark); + _media = _paper.document() + ? _paper.document()->createMediaView() + : nullptr; + if (_media) { + _media->thumbnailWanted(_paper.fileOrigin()); + } + _full = QImage(); + _generated = _scaled = _blurred = _fadeOutThumbnail = QPixmap(); + _generating = {}; + generateBackground(); + _paper.loadDocument(); + if (const auto document = _paper.document()) { + if (document->loading()) { + _radial.start(_media->progress()); + } + } + checkLoadedDocument(); + updateServiceBg(_paper.backgroundColors()); + update(); +} + void BackgroundPreviewBox::applyDarkMode(bool dark) { + if (!_paperEmojiId.isEmpty()) { + recreate(dark); + } const auto equals = (dark == Window::Theme::IsNightMode()); const auto &palette = (dark ? _darkPalette : _lightPalette); if (!equals && !palette) { @@ -292,10 +348,10 @@ void BackgroundPreviewBox::createDimmingSlider(bool dark) { const auto equals = (dark == Window::Theme::IsNightMode()); const auto inner = Ui::CreateChild(_dimmingContent); inner->show(); - Settings::AddSubsectionTitle( + Ui::AddSubsectionTitle( inner, tr::lng_background_dimming(), - style::margins(0, st::settingsSectionSkip, 0, 0), + style::margins(0, st::defaultVerticalListSkip, 0, 0), equals ? nullptr : dark ? &_dark->subtitle : &_light->subtitle); _dimmingSlider = inner->add( object_ptr( @@ -377,7 +433,7 @@ auto BackgroundPreviewBox::prepareOverridenStyle(bool dark) .box = st::defaultBox, .toggle = toggle, .slider = st::defaultContinuousSlider, - .subtitle = st::settingsSubsectionTitle, + .subtitle = st::defaultSubsectionTitle, }; result.box.button.textFg = p->lightButtonFg(); result.box.button.textFgOver = p->lightButtonFgOver(); @@ -407,6 +463,14 @@ auto BackgroundPreviewBox::prepareOverridenStyle(bool dark) return result; } +bool BackgroundPreviewBox::forChannel() const { + return _forPeer && _forPeer->isChannel(); +} + +bool BackgroundPreviewBox::forGroup() const { + return forChannel() && _forPeer->isMegagroup(); +} + void BackgroundPreviewBox::generateBackground() { if (_paper.backgroundColors().empty()) { return; @@ -432,9 +496,13 @@ void BackgroundPreviewBox::resetTitle() { void BackgroundPreviewBox::rebuildButtons(bool dark) { clearButtons(); - addButton(_forPeer + addButton(forGroup() + ? tr::lng_background_apply_group() + : forChannel() + ? tr::lng_background_apply_channel() + : _forPeer ? tr::lng_background_apply_button() - : tr::lng_background_apply(), [=] { apply(); }); + : tr::lng_settings_apply(), [=] { apply(); }); addButton(tr::lng_cancel(), [=] { closeBox(); }); if (!_forPeer && _paper.hasShareUrl()) { addLeftButton(tr::lng_background_share(), [=] { share(); }); @@ -509,6 +577,10 @@ void BackgroundPreviewBox::recreateBlurCheckbox() { }, _blur->lifetime()); _blur->setDisabled(_paper.document() && _full.isNull()); + + if (_forBothOverlay) { + _forBothOverlay->raise(); + } } void BackgroundPreviewBox::apply() { @@ -519,7 +591,7 @@ void BackgroundPreviewBox::apply() { } } -void BackgroundPreviewBox::uploadForPeer() { +void BackgroundPreviewBox::uploadForPeer(bool both) { Expects(_forPeer != nullptr); if (_uploadId) { @@ -578,7 +650,7 @@ void BackgroundPreviewBox::uploadForPeer() { "Got wallPaperNoFile after account.UploadWallPaper.")); }); if (const auto paper = Data::WallPaper::Create(session, result)) { - setExistingForPeer(*paper); + setExistingForPeer(*paper, both); } }).send(); }, _uploadLifetime); @@ -587,7 +659,9 @@ void BackgroundPreviewBox::uploadForPeer() { _radial.start(_uploadProgress); } -void BackgroundPreviewBox::setExistingForPeer(const Data::WallPaper &paper) { +void BackgroundPreviewBox::setExistingForPeer( + const Data::WallPaper &paper, + bool both) { Expects(_forPeer != nullptr); if (const auto already = _forPeer->wallPaper()) { @@ -601,6 +675,7 @@ void BackgroundPreviewBox::setExistingForPeer(const Data::WallPaper &paper) { api->request(MTPmessages_SetChatWallPaper( MTP_flags((_fromMessageId ? Flag::f_id : Flag()) | (_fromMessageId ? Flag() : Flag::f_wallpaper) + | (both ? Flag::f_for_both : Flag()) | Flag::f_settings), _forPeer->input, paper.mtpInput(&_controller->session()), @@ -614,13 +689,155 @@ void BackgroundPreviewBox::setExistingForPeer(const Data::WallPaper &paper) { _controller->finishChatThemeEdit(_forPeer); } +void BackgroundPreviewBox::checkLevelForChannel() { + Expects(forChannel()); + + const auto show = _controller->uiShow(); + _forPeerLevelCheck = true; + const auto weak = Ui::MakeWeak(this); + CheckBoostLevel(show, _forPeer, [=](int level) { + if (!weak) { + return std::optional(); + } + const auto appConfig = &_forPeer->session().account().appConfig(); + const auto defaultRequired = appConfig->get( + "channel_wallpaper_level_min", + 9); + const auto customRequired = appConfig->get( + "channel_custom_wallpaper_level_min", + 10); + const auto required = _paperEmojiId.isEmpty() + ? customRequired + : defaultRequired; + if (level >= required) { + applyForPeer(false); + return std::optional(); + } + return std::make_optional(Ui::AskBoostReason{ + Ui::AskBoostWallpaper{ required, _forPeer->isMegagroup()} + }); + }, [=] { _forPeerLevelCheck = false; }); +} + void BackgroundPreviewBox::applyForPeer() { Expects(_forPeer != nullptr); - if (Data::IsCustomWallPaper(_paper)) { - uploadForPeer(); + if (!Data::IsCustomWallPaper(_paper)) { + if (const auto already = _forPeer->wallPaper()) { + if (already->equals(_paper)) { + _controller->finishChatThemeEdit(_forPeer); + return; + } + } + } + + if (forChannel()) { + checkLevelForChannel(); + return; + } else if (_fromMessageId || !_forPeer->session().premiumPossible()) { + applyForPeer(false); + return; + } else if (_forBothOverlay) { + return; + } + const auto size = this->size() * style::DevicePixelRatio(); + const auto bg = Images::DitherImage( + Images::BlurLargeImage( + Ui::GrabWidgetToImage(this).scaled( + size / style::ConvertScale(4), + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation), + 24).scaled( + size, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation)); + + _forBothOverlay = std::make_unique>( + this, + object_ptr(this)); + const auto overlay = _forBothOverlay->entity(); + + sizeValue() | rpl::start_with_next([=](QSize size) { + _forBothOverlay->setGeometry({ QPoint(), size }); + overlay->setGeometry({ QPoint(), size }); + }, _forBothOverlay->lifetime()); + + overlay->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + auto p = QPainter(overlay); + p.drawImage(0, 0, bg); + p.fillRect(clip, QColor(0, 0, 0, 64)); + }, overlay->lifetime()); + + using namespace Ui; + const auto forMe = CreateChild( + overlay, + tr::lng_background_apply_me(), + st::backgroundConfirm); + forMe->setClickedCallback([=] { + applyForPeer(false); + }); + using namespace rpl::mappers; + const auto forBoth = ::Settings::CreateLockedButton( + overlay, + tr::lng_background_apply_both( + lt_user, + rpl::single(_forPeer->shortName())), + st::backgroundConfirm, + Data::AmPremiumValue(&_forPeer->session()) | rpl::map(!_1)); + forBoth->setClickedCallback([=] { + if (_forPeer->session().premium()) { + applyForPeer(true); + } else { + ShowPremiumPreviewBox( + _controller->uiShow(), + PremiumPreview::Wallpapers); + } + }); + const auto cancel = CreateChild( + overlay, + tr::lng_cancel(), + st::backgroundConfirmCancel); + cancel->setClickedCallback([=] { + const auto raw = _forBothOverlay.release(); + raw->shownValue() | rpl::filter( + !rpl::mappers::_1 + ) | rpl::take(1) | rpl::start_with_next(crl::guard(raw, [=] { + delete raw; + }), raw->lifetime()); + raw->toggle(false, anim::type::normal); + }); + forMe->setTextTransform(RoundButton::TextTransform::NoTransform); + forBoth->setTextTransform(RoundButton::TextTransform::NoTransform); + cancel->setTextTransform(RoundButton::TextTransform::NoTransform); + + overlay->sizeValue( + ) | rpl::start_with_next([=](QSize size) { + const auto padding = st::backgroundConfirmPadding; + const auto width = size.width() + - padding.left() + - padding.right(); + const auto height = cancel->height(); + auto top = size.height() - padding.bottom() - height; + cancel->setGeometry(padding.left(), top, width, height); + top -= height + padding.top(); + forBoth->setGeometry(padding.left(), top, width, height); + top -= height + padding.top(); + forMe->setGeometry(padding.left(), top, width, height); + }, _forBothOverlay->lifetime()); + + _forBothOverlay->hide(anim::type::instant); + _forBothOverlay->show(anim::type::normal); +} + +void BackgroundPreviewBox::applyForPeer(bool both) { + using namespace Data; + if (forChannel() && !_paperEmojiId.isEmpty()) { + setExistingForPeer(WallPaper::FromEmojiId(_paperEmojiId), both); + } else if (IsCustomWallPaper(_paper)) { + uploadForPeer(both); } else { - setExistingForPeer(_paper); + setExistingForPeer(_paper, both); } } @@ -738,7 +955,7 @@ int BackgroundPreviewBox::textsTop() const { - st::historyPaddingBottom - (_service ? _service->height() : 0) - _text1->height() - - _text2->height(); + - (forChannel() ? _text2->height() : 0); } QRect BackgroundPreviewBox::radialRect() const { @@ -768,10 +985,11 @@ void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) { context.outbg = _text1->hasOutLayout(); _text1->draw(p, context); p.translate(0, height1); - - context.outbg = _text2->hasOutLayout(); - _text2->draw(p, context); - p.translate(0, height2); + if (!forChannel()) { + context.outbg = _text2->hasOutLayout(); + _text2->draw(p, context); + p.translate(0, height2); + } } void BackgroundPreviewBox::radialAnimationCallback(crl::time now) { @@ -871,7 +1089,11 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector &bg) { _service = GenerateServiceItem( delegate(), _serviceHistory, - ((_forPeer && !_fromMessageId) + (forGroup() + ? tr::lng_background_other_group(tr::now) + : forChannel() + ? tr::lng_background_other_channel(tr::now) + : (_forPeer && !_fromMessageId) ? tr::lng_background_other_info( tr::now, lt_user, diff --git a/Telegram/SourceFiles/boxes/background_preview_box.h b/Telegram/SourceFiles/boxes/background_preview_box.h index dc44f9509f8ea6..96150c6ba05256 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.h +++ b/Telegram/SourceFiles/boxes/background_preview_box.h @@ -7,7 +7,7 @@ For license and copyright information please follow this link: */ #pragma once -#include "boxes/abstract_box.h" +#include "ui/layers/box_content.h" #include "base/binary_guard.h" #include "history/admin_log/history_admin_log_item.h" #include "history/view/history_view_element.h" @@ -29,6 +29,8 @@ class ChatStyle; class MediaSlider; template class SlideWrap; +template +class FadeWrap; } // namespace Ui struct BackgroundPreviewArgs { @@ -66,9 +68,10 @@ class BackgroundPreviewBox void apply(); void applyForPeer(); + void applyForPeer(bool both); void applyForEveryone(); - void uploadForPeer(); - void setExistingForPeer(const Data::WallPaper &paper); + void uploadForPeer(bool both); + void setExistingForPeer(const Data::WallPaper &paper, bool both); void share(); void radialAnimationCallback(crl::time now); QRect radialRect() const; @@ -91,18 +94,25 @@ class BackgroundPreviewBox void applyDarkMode(bool dark); [[nodiscard]] OverridenStyle prepareOverridenStyle(bool dark); + [[nodiscard]] bool forChannel() const; + [[nodiscard]] bool forGroup() const; + void checkLevelForChannel(); + + void recreate(bool dark); void resetTitle(); void rebuildButtons(bool dark); void createDimmingSlider(bool dark); const not_null _controller; PeerData * const _forPeer = nullptr; + bool _forPeerLevelCheck = false; FullMsgId _fromMessageId; std::unique_ptr _chatStyle; const not_null _serviceHistory; AdminLog::OwnedItem _service; AdminLog::OwnedItem _text1; AdminLog::OwnedItem _text2; + QString _paperEmojiId; Data::WallPaper _paper; std::shared_ptr _media; QImage _full; @@ -131,6 +141,8 @@ class BackgroundPreviewBox float64 _uploadProgress = 0.; rpl::lifetime _uploadLifetime; + std::unique_ptr> _forBothOverlay; + rpl::variable _paletteServiceBg; rpl::lifetime _serviceBgLifetime; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index b952e5a6ef32a5..14a859e7610c8f 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -85,8 +85,6 @@ confirmInviteTitle: FlatLabel(defaultFlatLabel) { textFg: windowBoldFg; style: TextStyle(defaultTextStyle) { font: font(18px semibold); - linkFont: font(18px semibold); - linkFontOver: font(18px semibold underline); } } confirmInviteAbout: FlatLabel(boxLabel) { @@ -143,8 +141,6 @@ contactsPadding: margins(16px, 7px, 16px, 7px); contactsNameTop: 2px; contactsNameStyle: TextStyle(defaultTextStyle) { font: semiboldFont; - linkFont: semiboldFont; - linkFontOver: semiboldFont; } contactsStatusTop: 23px; contactsStatusFont: font(fsize); @@ -199,8 +195,6 @@ localStorageRowTitle: FlatLabel(defaultFlatLabel) { maxHeight: 20px; style: TextStyle(defaultTextStyle) { font: font(14px semibold); - linkFont: font(14px semibold); - linkFontOver: font(14px semibold); } } localStorageRowSize: FlatLabel(defaultFlatLabel) { @@ -208,8 +202,6 @@ localStorageRowSize: FlatLabel(defaultFlatLabel) { maxHeight: 20px; style: TextStyle(defaultTextStyle) { font: font(14px); - linkFont: font(14px); - linkFontOver: font(14px); } } localStorageClear: defaultBoxButton; @@ -228,8 +220,6 @@ sharePhotoTop: 6px; shareBoxListItem: PeerListItem(defaultPeerListItem) { nameStyle: TextStyle(defaultTextStyle) { font: font(11px); - linkFont: font(11px); - linkFontOver: font(11px); } nameFg: windowFg; nameFgChecked: windowActiveTextFg; @@ -537,8 +527,6 @@ adminLogFilterLittleSkip: 16px; adminLogFilterCheckbox: Checkbox(defaultBoxCheckbox) { style: TextStyle(boxTextStyle) { font: font(boxFontSize semibold); - linkFont: font(boxFontSize semibold); - linkFontOver: font(boxFontSize semibold underline); } } adminLogFilterSkip: 32px; @@ -580,16 +568,12 @@ rightsPhotoButton: UserpicButton(defaultUserpicButton) { rightsPhotoMargin: margins(20px, 0px, 15px, 18px); rightsNameStyle: TextStyle(semiboldTextStyle) { font: font(15px semibold); - linkFont: font(15px semibold); - linkFontOver: font(15px semibold underline); } rightsNameTop: 8px; rightsStatusTop: 32px; rightsHeaderLabel: FlatLabel(boxLabel) { style: TextStyle(semiboldTextStyle) { font: font(boxFontSize semibold); - linkFont: font(boxFontSize semibold); - linkFontOver: font(boxFontSize semibold underline); } textFg: windowActiveTextFg; } @@ -623,8 +607,6 @@ proxyRowTitlePalette: TextPalette(defaultTextPalette) { } proxyRowTitleStyle: TextStyle(defaultTextStyle) { font: semiboldFont; - linkFont: normalFont; - linkFontOver: normalFont; } proxyRowStatusFg: windowSubTextFg; proxyRowStatusFgOnline: windowActiveTextFg; @@ -771,7 +753,28 @@ backgroundCheck: ServiceCheck { color: msgServiceFg; duration: 200; } +backgroundConfirmPadding: margins(24px, 16px, 24px, 16px); +backgroundConfirm: RoundButton(defaultActiveButton) { + height: 44px; + textTop: 12px; + font: font(13px semibold); +} +backgroundConfirmCancel: RoundButton(backgroundConfirm) { + textFg: mediaviewSaveMsgFg; + textFgOver: mediaviewSaveMsgFg; + numbersTextFg: mediaviewSaveMsgFg; + numbersTextFgOver: mediaviewSaveMsgFg; + textBg: shadowFg; + textBgOver: shadowFg; + + height: 44px; + textTop: 12px; + font: font(13px semibold); + ripple: RippleAnimation(defaultRippleAnimation) { + color: shadowFg; + } +} urlAuthCheckbox: Checkbox(defaultBoxCheckbox) { width: 240px; } @@ -788,6 +791,9 @@ slowmodeLabelsMargin: margins(0px, 5px, 0px, 0px); slowmodeLabel: LabelSimple(defaultLabelSimple) { textFg: windowSubTextFg; } +boostsUnrestrictLabel: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; +} customBadgeField: InputField(defaultInputField) { textMargins: margins(2px, 7px, 2px, 0px); @@ -807,8 +813,6 @@ pollResultsQuestion: FlatLabel(defaultFlatLabel) { textFg: windowBoldFg; style: TextStyle(defaultTextStyle) { font: font(16px semibold); - linkFont: font(16px semibold); - linkFontOver: font(16px semibold underline); } } pollResultsVotesCount: FlatLabel(defaultFlatLabel) { @@ -837,8 +841,6 @@ inviteViaLinkButton: SettingsButton(defaultSettingsButton) { style: TextStyle(defaultTextStyle) { font: font(14px semibold); - linkFont: font(14px semibold); - linkFontOver: font(14px semibold underline); } height: 20px; @@ -928,6 +930,14 @@ sponsoredUrlButton: RoundButton(defaultActiveButton) { } } +requestPeerRestriction: FlatLabel(defaultFlatLabel) { + minWidth: 240px; + textFg: membersAboutLimitFg; + style: TextStyle(boxTextStyle) { + lineHeight: 22px; + } +} + requestsBoxItem: PeerListItem(peerListBoxItem) { height: 99px; button: OutlineButton(defaultPeerListButton) { diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index 144582a533fce9..4403f56faabcf6 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -113,7 +113,8 @@ Base64UrlInput::Base64UrlInput( rpl::producer placeholder, const QString &val) : MaskedInputField(parent, st, std::move(placeholder), val) { - if (!QRegularExpression("^[a-zA-Z0-9_\\-]+$").match(val).hasMatch()) { + static const auto RegExp = QRegularExpression("^[a-zA-Z0-9_\\-]+$"); + if (!RegExp.match(val).hasMatch()) { setText(QString()); } } @@ -747,7 +748,7 @@ void ProxiesBox::applyView(View &&view) { const auto wrap = _wrap ? _wrap.data() : _initialWrap.data(); - const auto [i, ok] = _rows.emplace(id, nullptr); + const auto &[i, ok] = _rows.emplace(id, nullptr); i->second.reset(wrap->insert( 0, object_ptr( @@ -831,8 +832,9 @@ void ProxyBox::prepare() { connect(_host.data(), &HostInput::changed, [=] { Ui::PostponeCall(_host, [=] { const auto host = _host->getLastText().trimmed(); - static const auto mask = u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q; - const auto match = QRegularExpression(mask).match(host); + static const auto mask = QRegularExpression( + u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q); + const auto match = mask.match(host); if (_host->cursorPosition() == host.size() && match.hasMatch()) { const auto port = match.captured(1); @@ -1107,6 +1109,10 @@ void ProxiesBoxController::ShowApplyConfirmation( proxy.password = fields.value(u"secret"_q); } if (proxy) { + static const auto UrlStartRegExp = QRegularExpression( + "^https://", + QRegularExpression::CaseInsensitiveOption); + static const auto UrlEndRegExp = QRegularExpression("/$"); const auto displayed = "https://" + server + "/"; const auto parsed = QUrl::fromUserInput(displayed); const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed) @@ -1117,11 +1123,9 @@ void ProxiesBoxController::ShowApplyConfirmation( const auto displayServer = QString( displayUrl ).replace( - QRegularExpression( - "^https://", - QRegularExpression::CaseInsensitiveOption), + UrlStartRegExp, QString() - ).replace(QRegularExpression("/$"), QString()); + ).replace(UrlEndRegExp, QString()); const auto text = tr::lng_sure_enable_socks( tr::now, lt_server, diff --git a/Telegram/SourceFiles/boxes/connection_box.h b/Telegram/SourceFiles/boxes/connection_box.h index d16d5cd11b971a..8c78ebf34e1099 100644 --- a/Telegram/SourceFiles/boxes/connection_box.h +++ b/Telegram/SourceFiles/boxes/connection_box.h @@ -9,7 +9,7 @@ For license and copyright information please follow this link: #include "base/timer.h" #include "base/object_ptr.h" -#include "core/core_settings.h" +#include "core/core_settings_proxy.h" #include "mtproto/connection_abstract.h" #include "mtproto/mtproto_proxy_data.h" diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp index 3274e4eeba40d5..373899be4a79c4 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.cpp +++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp @@ -19,6 +19,7 @@ For license and copyright information please follow this link: #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" #include "ui/text/text_utilities.h" +#include "ui/vertical_list.h" #include "main/main_session.h" #include "core/application.h" #include "core/core_settings.h" @@ -26,7 +27,6 @@ For license and copyright information please follow this link: #include "chat_helpers/message_field.h" #include "menu/menu_send.h" #include "history/view/history_view_schedule_box.h" -#include "settings/settings_common.h" #include "base/unique_qptr.h" #include "base/event_filter.h" #include "base/call_delayed.h" @@ -34,7 +34,6 @@ For license and copyright information please follow this link: #include "window/window_session_controller.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" -#include "styles/style_settings.h" namespace { @@ -194,7 +193,7 @@ not_null CreateWarningLabel( if (value >= 0) { result->setText(QString::number(value)); } else { - result->setMarkedText(Ui::Text::PlainLink( + result->setMarkedText(Ui::Text::Colorized( QString::number(value))); } result->setVisible(shown); @@ -790,7 +789,7 @@ not_null CreatePollBox::setupQuestion( using namespace Settings; const auto session = &_controller->session(); - AddSubsectionTitle(container, tr::lng_polls_create_question()); + Ui::AddSubsectionTitle(container, tr::lng_polls_create_question()); const auto question = container->add( object_ptr( container, @@ -818,9 +817,9 @@ not_null CreatePollBox::setupQuestion( - st::createPollWarningPosition.x()), (geometry.y() - st::createPollFieldPadding.top() - - st::settingsSubsectionTitlePadding.bottom() - - st::settingsSubsectionTitle.style.font->height - + st::settingsSubsectionTitle.style.font->ascent + - st::defaultSubsectionTitlePadding.bottom() + - st::defaultSubsectionTitle.style.font->height + + st::defaultSubsectionTitle.style.font->ascent - st::createPollWarning.style.font->ascent), geometry.width()); }, warning->lifetime()); @@ -841,8 +840,8 @@ not_null CreatePollBox::setupSolution( const auto inner = outer->entity(); const auto session = &_controller->session(); - AddSkip(inner); - AddSubsectionTitle(inner, tr::lng_polls_solution_title()); + Ui::AddSkip(inner); + Ui::AddSubsectionTitle(inner, tr::lng_polls_solution_title()); const auto solution = inner->add( object_ptr( inner, @@ -875,9 +874,9 @@ not_null CreatePollBox::setupSolution( - st::createPollWarningPosition.x()), (geometry.y() - st::createPollFieldPadding.top() - - st::settingsSubsectionTitlePadding.bottom() - - st::settingsSubsectionTitle.style.font->height - + st::settingsSubsectionTitle.style.font->ascent + - st::defaultSubsectionTitlePadding.bottom() + - st::defaultSubsectionTitle.style.font->height + + st::defaultSubsectionTitle.style.font->ascent - st::createPollWarning.style.font->ascent), geometry.width()); }, warning->lifetime()); @@ -902,13 +901,13 @@ object_ptr CreatePollBox::setupContent() { const auto container = result.data(); const auto question = setupQuestion(container); - AddDivider(container); - AddSkip(container); + Ui::AddDivider(container); + Ui::AddSkip(container); container->add( object_ptr( container, tr::lng_polls_create_options(), - st::settingsSubsectionTitle), + st::defaultSubsectionTitle), st::createPollFieldTitlePadding); const auto options = lifetime().make_state( getDelegate()->outerContainer(), @@ -939,8 +938,8 @@ object_ptr CreatePollBox::setupContent() { options->focusFirst(); }, question->lifetime()); - AddSkip(container); - AddSubsectionTitle(container, tr::lng_polls_create_settings()); + Ui::AddSkip(container); + Ui::AddSubsectionTitle(container, tr::lng_polls_create_settings()); const auto anonymous = (!(_disabled & PollData::Flag::PublicVotes)) ? container->add( diff --git a/Telegram/SourceFiles/boxes/create_poll_box.h b/Telegram/SourceFiles/boxes/create_poll_box.h index 3622139876d364..2147c2e328b34b 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.h +++ b/Telegram/SourceFiles/boxes/create_poll_box.h @@ -7,7 +7,7 @@ For license and copyright information please follow this link: */ #pragma once -#include "boxes/abstract_box.h" +#include "ui/layers/box_content.h" #include "api/api_common.h" #include "data/data_poll.h" #include "base/flags.h" diff --git a/Telegram/SourceFiles/boxes/delete_messages_box.cpp b/Telegram/SourceFiles/boxes/delete_messages_box.cpp index 99fe6b8835a652..0a939ace62843c 100644 --- a/Telegram/SourceFiles/boxes/delete_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/delete_messages_box.cpp @@ -221,7 +221,7 @@ void DeleteMessagesBox::prepare() { ? QString() : QString(" (%1)").arg(total)); }); - search->searchMessages(QString(), _moderateFrom); + search->searchMessages({ .from = _moderateFrom }); } } else { details.text = (_ids.size() == 1) diff --git a/Telegram/SourceFiles/boxes/dictionaries_manager.h b/Telegram/SourceFiles/boxes/dictionaries_manager.h index 0dd39141030b82..f91fb1741413da 100644 --- a/Telegram/SourceFiles/boxes/dictionaries_manager.h +++ b/Telegram/SourceFiles/boxes/dictionaries_manager.h @@ -9,7 +9,7 @@ For license and copyright information please follow this link: #ifndef TDESKTOP_DISABLE_SPELLCHECK -#include "boxes/abstract_box.h" +#include "ui/layers/box_content.h" namespace Main { class Session; diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index defc4bd645d476..8e94beeb9c2d94 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -470,8 +470,8 @@ void EditCaptionBox::rebuildPreview() { void EditCaptionBox::setupField() { const auto peer = _historyItem->history()->peer; - const auto allow = [=](const auto&) { - return Data::AllowEmojiWithoutPremium(peer); + const auto allow = [=](not_null emoji) { + return Data::AllowEmojiWithoutPremium(peer, emoji); }; InitMessageFieldHandlers( _controller, diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.h b/Telegram/SourceFiles/boxes/edit_caption_box.h index 80a7b0a82ef781..110c0e58849d7c 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.h +++ b/Telegram/SourceFiles/boxes/edit_caption_box.h @@ -7,7 +7,7 @@ For license and copyright information please follow this link: */ #pragma once -#include "boxes/abstract_box.h" +#include "ui/layers/box_content.h" #include "ui/chat/attach/attach_prepare.h" namespace ChatHelpers { diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index e5c601881fcbae..f09b1044849e5d 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -7,15 +7,22 @@ For license and copyright information please follow this link: */ #include "boxes/edit_privacy_box.h" +#include "api/api_global_privacy.h" +#include "ui/layers/generic_box.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/shadow.h" #include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" +#include "ui/painter.h" +#include "ui/vertical_list.h" #include "history/history.h" #include "boxes/peer_list_controllers.h" #include "settings/settings_common.h" +#include "settings/settings_premium.h" #include "settings/settings_privacy_security.h" #include "calls/calls_instance.h" #include "base/binary_guard.h" @@ -28,8 +35,39 @@ For license and copyright information please follow this link: #include "window/window_session_controller.h" #include "styles/style_settings.h" #include "styles/style_layers.h" +#include "styles/style_menu_icons.h" namespace { +namespace { + +void CreateRadiobuttonLock( + not_null widget, + const style::Checkbox &st) { + const auto lock = Ui::CreateChild(widget.get()); + lock->setAttribute(Qt::WA_TransparentForMouseEvents); + + lock->resize(st::defaultRadio.diameter, st::defaultRadio.diameter); + + widget->sizeValue( + ) | rpl::start_with_next([=, &st](QSize size) { + lock->move(st.checkPosition); + }, lock->lifetime()); + + lock->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(lock); + auto hq = PainterHighQualityEnabler(p); + const auto &icon = st::messagePrivacyLock; + const auto size = st::defaultRadio.diameter; + const auto image = icon.instance(st::checkboxFg->c); + p.drawImage(QRectF( + (size - icon.width()) / 2., + (size - icon.height()) / 2., + icon.width(), + icon.height()), image); + }, lock->lifetime()); +} + +} // namespace class PrivacyExceptionsBoxController : public ChatsListBoxController { public: @@ -233,7 +271,7 @@ Ui::FlatLabel *EditPrivacyBox::addLabel( object_ptr( container, std::move(label), - st::settingsDividerLabelPadding), + st::defaultBoxDividerLabelPadding), { 0, topSkip, 0, 0 }); return result; } @@ -294,7 +332,7 @@ void EditPrivacyBox::setupContent() { const auto button = content->add( object_ptr>( content, - CreateButton( + object_ptr