diff --git a/.cursor/api_usage.md b/.cursor/rules/api_usage.mdc similarity index 83% rename from .cursor/api_usage.md rename to .cursor/rules/api_usage.mdc index c551f0b7ce..bf6e55bbdd 100644 --- a/.cursor/api_usage.md +++ b/.cursor/rules/api_usage.mdc @@ -1,8 +1,13 @@ +--- +description: For tasks requiring sending Telegram server API requests or working with generated API types. +globs: +alwaysApply: false +--- # Telegram Desktop API Usage ## API Schema -The API definitions are described using [TL Language](https://core.telegram.org/mtproto/TL) in two main schema files: +The API definitions are described using [TL Language]\(https:/core.telegram.org/mtproto/TL) in two main schema files: 1. **`Telegram/SourceFiles/mtproto/scheme/mtproto.tl`** * Defines the core MTProto protocol types and methods used for basic communication, encryption, authorization, service messages, etc. @@ -39,7 +44,7 @@ api().request(MTPnamespace_MethodName( MTP_long(randomId), // ... other arguments matching the TL definition MTP_vector() // Example for a vector argument -)).done([=](const MTPResponseType &result) { +)).done([=]\(const MTPResponseType &result) { // Handle the successful response (result). // 'result' will be of the C++ type corresponding to the TL type // specified after the '=' in the api.tl method definition. @@ -47,9 +52,9 @@ api().request(MTPnamespace_MethodName( // 1. Multiple Constructors (e.g., User = User | UserEmpty): // Use .match() with lambdas for each constructor: - result.match([&](const MTPDuser &data) { + result.match([&]\(const MTPDuser &data) { /* use data.vfirst_name().v, etc. */ - }, [&](const MTPDuserEmpty &data) { + }, [&]\(const MTPDuserEmpty &data) { /* handle empty user */ }); @@ -64,7 +69,7 @@ api().request(MTPnamespace_MethodName( // 2. Single Constructor (e.g., Messages = messages { msgs: vector }): // Use .match() with a single lambda: - result.match([&](const MTPDmessages &data) { /* use data.vmessages().v */ }); + result.match([&]\(const MTPDmessages &data) { /* use data.vmessages().v */ }); // Or check the type explicitly and use the constructor getter: if (result.type() == mtpc_messages) { @@ -76,7 +81,7 @@ api().request(MTPnamespace_MethodName( const auto &data = result.data(); // Only works for single-constructor types! // use data.vmessages().v -}).fail([=](const MTP::Error &error) { +}).fail([=]\(const MTP::Error &error) { // Handle the API error (error). // 'error' is an MTP::Error object containing the error code (error.type()) // and description (error.description()). Check for specific error strings. @@ -93,8 +98,8 @@ api().request(MTPnamespace_MethodName( * Always refer to `Telegram/SourceFiles/mtproto/scheme/api.tl` for the correct method names, parameters (names and types), and response types. * Use the generated `MTP...` types/classes for request parameters (e.g., `MTP_int`, `MTP_string`, `MTP_bool`, `MTP_vector`, `MTPInputUser`, etc.) and response handling. * The `.done()` lambda receives the specific C++ `MTP...` type corresponding to the TL return type. - * For types with **multiple constructors** (e.g., `User = User | UserEmpty`), use `result.match([&](const MTPDuser &d){ ... }, [&](const MTPDuserEmpty &d){ ... })` to handle each case, or check `result.type() == mtpc_user` / `mtpc_userEmpty` and call the specific `result.c_user()` / `result.c_userEmpty()` getter (which asserts on type mismatch). - * For types with a **single constructor** (e.g., `Messages = messages{...}`), you can use `result.match([&](const MTPDmessages &d){ ... })` with one lambda, or check `type()` and call `c_messages()`, or use the shortcut `result.data()` to access the fields directly. + * For types with **multiple constructors** (e.g., `User = User | UserEmpty`), use `result.match([&]\(const MTPDuser &d){ ... }, [&]\(const MTPDuserEmpty &d){ ... })` to handle each case, or check `result.type() == mtpc_user` / `mtpc_userEmpty` and call the specific `result.c_user()` / `result.c_userEmpty()` getter (which asserts on type mismatch). + * For types with a **single constructor** (e.g., `Messages = messages{...}`), you can use `result.match([&]\(const MTPDmessages &d){ ... })` with one lambda, or check `type()` and call `c_messages()`, or use the shortcut `result.data()` to access the fields directly. * The `.fail()` lambda receives an `MTP::Error` object. Check `error.type()` against known error strings (often defined as constants or using `u"..."_q` literals). * Directly construct the `MTPnamespace_MethodName(...)` object inside `request()`. -* Include `.handleFloodErrors()` before `.send()` for standard flood wait handling. \ No newline at end of file +* Include `.handleFloodErrors()` before `.send()` for standard flood wait handling. diff --git a/.cursor/localization.md b/.cursor/rules/localization.mdc similarity index 97% rename from .cursor/localization.md rename to .cursor/rules/localization.mdc index cf39e7d6fd..85e7a32c7a 100644 --- a/.cursor/localization.md +++ b/.cursor/rules/localization.mdc @@ -1,3 +1,8 @@ +--- +description: For tasks requiring changing or adding user facing phrases and text parts. +globs: +alwaysApply: false +--- # Telegram Desktop Localization ## Coding Style Note @@ -156,4 +161,4 @@ auto messageProducer = tr::lng_user_posted_photo( // Type: rpl::producer`, typically by converting an `int` producer using `| tr::to_count()`. * Optional projector function as the last argument modifies the output type and required placeholder types. -* Actual translations are loaded at runtime from the API. \ No newline at end of file +* Actual translations are loaded at runtime from the API. diff --git a/.cursor/rpl_guide.md b/.cursor/rules/rpl_guide.mdc similarity index 91% rename from .cursor/rpl_guide.md rename to .cursor/rules/rpl_guide.mdc index 8ff7be46a5..628a1d7e50 100644 --- a/.cursor/rpl_guide.md +++ b/.cursor/rules/rpl_guide.mdc @@ -1,3 +1,8 @@ +--- +description: +globs: +alwaysApply: true +--- # RPL (Reactive Programming Library) Guide ## Coding Style Note @@ -64,7 +69,7 @@ rpl::lifetime lifetime; // Counter is consumed here, use std::move if it's an l-value. std::move( counter -) | rpl::start_with_next([=](int nextValue) { +) | rpl::start_with_next([=]\(int nextValue) { // Process the next integer value emitted by the producer. qDebug() << "Received: " << nextValue; }, lifetime); // Pass the lifetime to manage the subscription. @@ -77,7 +82,7 @@ std::move( auto counter2 = /* ... */; // Type: rpl::producer rpl::lifetime subscriptionLifetime = std::move( counter2 -) | rpl::start_with_next([=](int nextValue) { /* ... */ }); +) | rpl::start_with_next([=]\(int nextValue) { /* ... */ }); // The returned lifetime MUST be stored. If it's discarded immediately, // the subscription stops instantly. // `counter2` is also moved-from here. @@ -93,7 +98,7 @@ rpl::lifetime lifetime; // If it's the only use, std::move(dataStream) would be preferred. rpl::duplicate( dataStream -) | rpl::start_with_error([=](Error &&error) { +) | rpl::start_with_error([=]\(Error &&error) { // Handle the error signaled by the producer. qDebug() << "Error: " << error.text(); }, lifetime); @@ -101,7 +106,7 @@ rpl::duplicate( // Using dataStream again, perhaps duplicated again or moved if last use. rpl::duplicate( dataStream -) | rpl::start_with_done([=]() { +) | rpl::start_with_done([=] { // Execute when the producer signals it's finished emitting values. qDebug() << "Stream finished."; }, lifetime); @@ -110,9 +115,9 @@ rpl::duplicate( std::move( dataStream ) | rpl::start_with_next_error_done( - [=](QString &&value) { /* handle next value */ }, - [=](Error &&error) { /* handle error */ }, - [=]() { /* handle done */ }, + [=]\(QString &&value) { /* handle next value */ }, + [=]\(Error &&error) { /* handle error */ }, + [=] { /* handle done */ }, lifetime); ``` @@ -164,7 +169,7 @@ You can combine multiple producers into one: // The lambda receives unpacked arguments, not the tuple itself. std::move( combined - ) | rpl::start_with_next([=](int count, const QString &text) { + ) | rpl::start_with_next([=]\(int count, const QString &text) { // No need for std::get<0>(latest), etc. qDebug() << "Combined: Count=" << count << ", Text=" << text; }, lifetime); @@ -172,11 +177,11 @@ You can combine multiple producers into one: // This also works with map, filter, etc. std::move( combined - ) | rpl::filter([=](int count, const QString &text) { + ) | rpl::filter([=]\(int count, const QString &text) { return count > 0 && !text.isEmpty(); - }) | rpl::map([=](int count, const QString &text) { + }) | rpl::map([=]\(int count, const QString &text) { return text.repeated(count); - }) | rpl::start_with_next([=](const QString &result) { + }) | rpl::start_with_next([=]\(const QString &result) { qDebug() << "Mapped & Filtered: " << result; }, lifetime); ``` @@ -192,7 +197,7 @@ You can combine multiple producers into one: // Starting the merged producer consumes it. std::move( merged - ) | rpl::start_with_next([=](QString &&value) { + ) | rpl::start_with_next([=]\(QString &&value) { // Receives values from either sourceA or sourceB as they arrive. qDebug() << "Merged value: " << value; }, lifetime); @@ -208,4 +213,4 @@ You can combine multiple producers into one: * Use `rpl::combine` or `rpl::merge` to combine streams. * When starting a chain (`std::move(producer) | rpl::map(...)`), explicitly move the initial producer. * These functions typically duplicate their input producers internally. -* Starting a pipeline consumes the producer; use ` \ No newline at end of file +* Starting a pipeline consumes the producer; use ` diff --git a/.cursor/styling.md b/.cursor/rules/styling.mdc similarity index 98% rename from .cursor/styling.md rename to .cursor/rules/styling.mdc index 8e3578068f..42729f096c 100644 --- a/.cursor/styling.md +++ b/.cursor/rules/styling.mdc @@ -1,3 +1,8 @@ +--- +description: For tasks requiring working with user facing UI components. +globs: +alwaysApply: false +--- # Telegram Desktop UI Styling ## Style Definition Files @@ -146,4 +151,4 @@ void MyWidget::paintEvent(QPaintEvent *e) { * Icons are defined inline using `name: icon{{ "path_stem", color }};` or `name: icon{ { "path1", c1 }, ... };` syntax, with optional path modifiers. * Code generation creates `struct` definitions in the `style` namespace for custom types and objects/structs in the `st` namespace for defined variables/groups. * Generated headers are in `styles/` with a `style_` prefix and must be included. -* Access style properties via the generated `st::` objects (e.g., `st::primaryButton.height`, `st::chatInput.backgroundColor`). \ No newline at end of file +* Access style properties via the generated `st::` objects (e.g., `st::primaryButton.height`, `st::chatInput.backgroundColor`). diff --git a/.devcontainer.json b/.devcontainer.json index 366f6ed46a..16a1bcc119 100644 --- a/.devcontainer.json +++ b/.devcontainer.json @@ -5,11 +5,9 @@ "vscode": { "settings": { "C_Cpp.intelliSenseEngine": "disabled", - "clangd.arguments": [ - "--compile-commands-dir=${workspaceFolder}/out" - ], "cmake.generator": "Ninja Multi-Config", - "cmake.buildDirectory": "${workspaceFolder}/out" + "cmake.buildDirectory": "${workspaceFolder}/out", + "cmake.copyCompileCommands": "${workspaceFolder}/compile_commands.json" }, "extensions": [ "ms-vscode.cpptools-extension-pack", diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index eae84dc676..228ef623c6 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -12,6 +12,7 @@ on: - '!.github/workflows/linux.yml' - 'snap/**' - 'Telegram/build/**' + - '!Telegram/build/docker/centos_env/**' - 'Telegram/Resources/uwp/**' - 'Telegram/Resources/winrc/**' - 'Telegram/SourceFiles/platform/win/**' @@ -30,6 +31,7 @@ on: - '!.github/workflows/linux.yml' - 'snap/**' - 'Telegram/build/**' + - '!Telegram/build/docker/centos_env/**' - 'Telegram/Resources/uwp/**' - 'Telegram/Resources/winrc/**' - 'Telegram/SourceFiles/platform/win/**' @@ -52,27 +54,44 @@ jobs: env: UPLOAD_ARTIFACT: "true" + ONLY_CACHE: "false" + IMAGE_TAG: tdesktop:centos_env steps: - - name: Get repository name. - run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV - - name: Clone. uses: actions/checkout@v4 with: submodules: recursive - path: ${{ env.REPO_NAME }} - name: First set up. run: | - 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 + sudo apt update + curl -sSL https://install.python-poetry.org | python3 - + cd Telegram/build/docker/centos_env + poetry install + DOCKERFILE=$(DEBUG= LTO= poetry run gen_dockerfile) + echo "$DOCKERFILE" > Dockerfile + + - name: Free up some disk space. + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be + with: + tool-cache: true + + - name: Set up Docker Buildx. + uses: docker/setup-buildx-action@v3 + + - name: Libraries. + uses: docker/build-push-action@v6 + with: + context: Telegram/build/docker/centos_env + load: ${{ env.ONLY_CACHE == 'false' }} + tags: ${{ env.IMAGE_TAG }} + cache-from: type=gha + cache-to: type=gha,mode=max - name: Telegram Desktop build. + if: env.ONLY_CACHE == 'false' run: | - cd $REPO_NAME - DEFINE="" if [ -n "${{ matrix.defines }}" ]; then DEFINE="-D ${{ matrix.defines }}=ON" @@ -86,21 +105,22 @@ jobs: -u $(id -u) \ -v $PWD:/usr/src/tdesktop \ -e CONFIG=Debug \ - tdesktop:centos_env \ + $IMAGE_TAG \ /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_CONFIGURATION_TYPES=Debug \ + -D CMAKE_C_FLAGS_DEBUG="-O0 -U_FORTIFY_SOURCE" \ + -D CMAKE_CXX_FLAGS_DEBUG="-O0 -U_FORTIFY_SOURCE" \ -D CMAKE_EXE_LINKER_FLAGS="-s" \ + -D CMAKE_COMPILE_WARNING_AS_ERROR=ON \ -D TDESKTOP_API_TEST=ON \ -D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \ $DEFINE - name: Check. + if: env.ONLY_CACHE == 'false' run: | - filePath="$REPO_NAME/out/Debug/Telegram" + filePath="out/Debug/Telegram" if test -f "$filePath"; then echo "Build successfully done! :)" @@ -114,7 +134,7 @@ jobs: - name: Move artifact. if: env.UPLOAD_ARTIFACT == 'true' run: | - cd $REPO_NAME/out/Debug + cd out/Debug mkdir artifact mv {Telegram,Updater} artifact/ - uses: actions/upload-artifact@v4 @@ -122,4 +142,4 @@ jobs: name: Upload artifact. with: name: ${{ env.ARTIFACT_NAME }} - path: ${{ env.REPO_NAME }}/out/Debug/artifact/ + path: out/Debug/artifact/ diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index d08d5824d2..8964ebac77 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -177,11 +177,11 @@ jobs: %TDESKTOP_BUILD_GENERATOR% ^ %TDESKTOP_BUILD_ARCH% ^ %TDESKTOP_BUILD_API% ^ - -D CMAKE_C_FLAGS="/WX" ^ - -D CMAKE_CXX_FLAGS="/WX" ^ + -D CMAKE_CONFIGURATION_TYPES=Debug ^ + -D CMAKE_COMPILE_WARNING_AS_ERROR=ON ^ + -D CMAKE_MSVC_DEBUG_INFORMATION_FORMAT= ^ -D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^ - -D DESKTOP_APP_NO_PDB=ON ^ %TDESKTOP_BUILD_DEFINE% cmake --build ..\out --config Debug --parallel diff --git a/.gitignore b/.gitignore index 42b53b2f2b..3db1a60aed 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ ipch/ .vs/ .vscode/ .cache/ +compile_commands.json /Telegram/log.txt /Telegram/data diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f60f982c4..830c44c5f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,15 +4,7 @@ # For license and copyright information please follow this link: # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -if (APPLE) - # target_precompile_headers with COMPILE_LANGUAGE restriction. - cmake_minimum_required(VERSION 3.23) -else() - cmake_minimum_required(VERSION 3.16) -endif() -if (POLICY CMP0149) - cmake_policy(SET CMP0149 NEW) -endif() +cmake_minimum_required(VERSION 3.25...3.31) set_property(GLOBAL PROPERTY USE_FOLDERS ON) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index f91548d4fa..7412592163 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -296,8 +296,8 @@ PRIVATE boxes/peers/edit_contact_box.h boxes/peers/edit_forum_topic_box.cpp boxes/peers/edit_forum_topic_box.h - boxes/peers/edit_linked_chat_box.cpp - boxes/peers/edit_linked_chat_box.h + boxes/peers/edit_discussion_link_box.cpp + boxes/peers/edit_discussion_link_box.h boxes/peers/edit_members_visible.cpp boxes/peers/edit_members_visible.h boxes/peers/edit_participant_box.cpp @@ -583,6 +583,8 @@ PRIVATE data/components/factchecks.h data/components/location_pickers.cpp data/components/location_pickers.h + data/components/promo_suggestions.cpp + data/components/promo_suggestions.h data/components/recent_peers.cpp data/components/recent_peers.h data/components/scheduled_messages.cpp diff --git a/Telegram/Resources/icons/menu/order_date.png b/Telegram/Resources/icons/menu/order_date.png new file mode 100644 index 0000000000..c5c1391e4d Binary files /dev/null and b/Telegram/Resources/icons/menu/order_date.png differ diff --git a/Telegram/Resources/icons/menu/order_date@2x.png b/Telegram/Resources/icons/menu/order_date@2x.png new file mode 100644 index 0000000000..60e5f56da3 Binary files /dev/null and b/Telegram/Resources/icons/menu/order_date@2x.png differ diff --git a/Telegram/Resources/icons/menu/order_date@3x.png b/Telegram/Resources/icons/menu/order_date@3x.png new file mode 100644 index 0000000000..9efac756f4 Binary files /dev/null and b/Telegram/Resources/icons/menu/order_date@3x.png differ diff --git a/Telegram/Resources/icons/menu/order_number.png b/Telegram/Resources/icons/menu/order_number.png new file mode 100644 index 0000000000..1771ee9f71 Binary files /dev/null and b/Telegram/Resources/icons/menu/order_number.png differ diff --git a/Telegram/Resources/icons/menu/order_number@2x.png b/Telegram/Resources/icons/menu/order_number@2x.png new file mode 100644 index 0000000000..3cd11faad9 Binary files /dev/null and b/Telegram/Resources/icons/menu/order_number@2x.png differ diff --git a/Telegram/Resources/icons/menu/order_number@3x.png b/Telegram/Resources/icons/menu/order_number@3x.png new file mode 100644 index 0000000000..a779b67131 Binary files /dev/null and b/Telegram/Resources/icons/menu/order_number@3x.png differ diff --git a/Telegram/Resources/icons/menu/order_price.png b/Telegram/Resources/icons/menu/order_price.png new file mode 100644 index 0000000000..56f780814e Binary files /dev/null and b/Telegram/Resources/icons/menu/order_price.png differ diff --git a/Telegram/Resources/icons/menu/order_price@2x.png b/Telegram/Resources/icons/menu/order_price@2x.png new file mode 100644 index 0000000000..ff726b8866 Binary files /dev/null and b/Telegram/Resources/icons/menu/order_price@2x.png differ diff --git a/Telegram/Resources/icons/menu/order_price@3x.png b/Telegram/Resources/icons/menu/order_price@3x.png new file mode 100644 index 0000000000..f0e4c87122 Binary files /dev/null and b/Telegram/Resources/icons/menu/order_price@3x.png differ diff --git a/Telegram/Resources/icons/menu/tag_sell.png b/Telegram/Resources/icons/menu/tag_sell.png new file mode 100644 index 0000000000..5afc942076 Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_sell.png differ diff --git a/Telegram/Resources/icons/menu/tag_sell@2x.png b/Telegram/Resources/icons/menu/tag_sell@2x.png new file mode 100644 index 0000000000..bd0cc198f0 Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_sell@2x.png differ diff --git a/Telegram/Resources/icons/menu/tag_sell@3x.png b/Telegram/Resources/icons/menu/tag_sell@3x.png new file mode 100644 index 0000000000..da06c7bf1c Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_sell@3x.png differ diff --git a/Telegram/Resources/icons/settings/mini_gift_order_date.svg b/Telegram/Resources/icons/settings/mini_gift_order_date.svg new file mode 100644 index 0000000000..130d30310f --- /dev/null +++ b/Telegram/Resources/icons/settings/mini_gift_order_date.svg @@ -0,0 +1,12 @@ + + + Mini / mini_gift_sorting2 + + + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/mini_gift_order_number.svg b/Telegram/Resources/icons/settings/mini_gift_order_number.svg new file mode 100644 index 0000000000..37cc05f572 --- /dev/null +++ b/Telegram/Resources/icons/settings/mini_gift_order_number.svg @@ -0,0 +1,12 @@ + + + Mini / mini_gift_sorting3 + + + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/mini_gift_order_price.svg b/Telegram/Resources/icons/settings/mini_gift_order_price.svg new file mode 100644 index 0000000000..36409f6b4b --- /dev/null +++ b/Telegram/Resources/icons/settings/mini_gift_order_price.svg @@ -0,0 +1,10 @@ + + + Mini / mini_gift_sorting1 + + + + + + + \ No newline at end of file diff --git a/Telegram/Resources/langs/cloud_lang.strings b/Telegram/Resources/langs/cloud_lang.strings index 16e9ea3572..c7e33531c0 100644 --- a/Telegram/Resources/langs/cloud_lang.strings +++ b/Telegram/Resources/langs/cloud_lang.strings @@ -16,7 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "cloud_lng_passport_in_ar" = "Arabic"; "cloud_lng_passport_in_az" = "Azerbaijani"; "cloud_lng_passport_in_bg" = "Bulgarian"; -"cloud_lng_passport_in_bn" = "Bangla"; +"cloud_lng_passport_in_bn" = "Bengali"; "cloud_lng_passport_in_cs" = "Czech"; "cloud_lng_passport_in_da" = "Danish"; "cloud_lng_passport_in_de" = "German"; @@ -64,7 +64,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "cloud_lng_translate_to_ar" = "Arabic"; "cloud_lng_translate_to_az" = "Azerbaijani"; "cloud_lng_translate_to_bg" = "Bulgarian"; -// "cloud_lng_translate_to_bn" = "Bangla"; +// "cloud_lng_translate_to_bn" = "Bengali"; "cloud_lng_translate_to_cs" = "Czech"; "cloud_lng_translate_to_da" = "Danish"; "cloud_lng_translate_to_de" = "German"; @@ -109,50 +109,116 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "cloud_lng_translate_to_uz" = "Uzbek"; "cloud_lng_translate_to_vi" = "Vietnamese"; +"cloud_lng_language_af" = "Afrikaans"; +"cloud_lng_language_am" = "Amharic"; "cloud_lng_language_ar" = "Arabic"; "cloud_lng_language_az" = "Azerbaijani"; +"cloud_lng_language_be" = "Belarusian"; "cloud_lng_language_bg" = "Bulgarian"; -// "cloud_lng_language_bn" = "Bangla"; +"cloud_lng_language_bn" = "Bengali"; +"cloud_lng_language_bs" = "Bosnian"; +"cloud_lng_language_ca" = "Catalan"; +// "cloud_lng_language_ceb" = "Cebuano"; +"cloud_lng_language_co" = "Corsican"; "cloud_lng_language_cs" = "Czech"; +"cloud_lng_language_cy" = "Welsh"; "cloud_lng_language_da" = "Danish"; "cloud_lng_language_de" = "German"; -// "cloud_lng_language_dv" = "Divehi"; -// "cloud_lng_language_dz" = "Dzongkha"; +"cloud_lng_language_dv" = "Divehi"; +"cloud_lng_language_dz" = "Dzongkha"; "cloud_lng_language_el" = "Greek"; "cloud_lng_language_en" = "English"; +"cloud_lng_language_eo" = "Esperanto"; "cloud_lng_language_es" = "Spanish"; "cloud_lng_language_et" = "Estonian"; +"cloud_lng_language_eu" = "Basque"; "cloud_lng_language_fa" = "Persian"; +"cloud_lng_language_fi" = "Finnish"; "cloud_lng_language_fr" = "French"; +"cloud_lng_language_fy" = "Frisian"; +"cloud_lng_language_ga" = "Irish"; +"cloud_lng_language_gd" = "Scots Gaelic"; +"cloud_lng_language_gl" = "Galician"; +"cloud_lng_language_gu" = "Gujarati"; +"cloud_lng_language_ha" = "Hausa"; +"cloud_lng_language_haw" = "Hawaiian"; "cloud_lng_language_he" = "Hebrew"; +"cloud_lng_language_hi" = "Hindi"; +// "cloud_lng_language_hmn" = "Hmong"; "cloud_lng_language_hr" = "Croatian"; +"cloud_lng_language_ht" = "Haitian Creole"; "cloud_lng_language_hu" = "Hungarian"; "cloud_lng_language_hy" = "Armenian"; "cloud_lng_language_id" = "Indonesian"; +"cloud_lng_language_ig" = "Igbo"; "cloud_lng_language_is" = "Icelandic"; "cloud_lng_language_it" = "Italian"; +"cloud_lng_language_iw" = "Hebrew (Obsolete code)"; "cloud_lng_language_ja" = "Japanese"; +"cloud_lng_language_jv" = "Javanese"; "cloud_lng_language_ka" = "Georgian"; -// "cloud_lng_language_km" = "Khmer"; +"cloud_lng_language_kk" = "Kazakh"; +"cloud_lng_language_km" = "Khmer"; +"cloud_lng_language_kn" = "Kannada"; "cloud_lng_language_ko" = "Korean"; +"cloud_lng_language_ku" = "Kurdish"; +"cloud_lng_language_ky" = "Kyrgyz"; +"cloud_lng_language_la" = "Latin"; +"cloud_lng_language_lb" = "Luxembourgish"; "cloud_lng_language_lo" = "Lao"; "cloud_lng_language_lt" = "Lithuanian"; "cloud_lng_language_lv" = "Latvian"; +"cloud_lng_language_mg" = "Malagasy"; +"cloud_lng_language_mi" = "Maori"; "cloud_lng_language_mk" = "Macedonian"; +"cloud_lng_language_ml" = "Malayalam"; "cloud_lng_language_mn" = "Mongolian"; +"cloud_lng_language_mr" = "Marathi"; "cloud_lng_language_ms" = "Malay"; +"cloud_lng_language_mt" = "Maltese"; "cloud_lng_language_my" = "Burmese"; "cloud_lng_language_ne" = "Nepali"; "cloud_lng_language_nl" = "Dutch"; +"cloud_lng_language_no" = "Norwegian"; +"cloud_lng_language_ny" = "Nyanja"; +"cloud_lng_language_or" = "Odia (Oriya)"; +"cloud_lng_language_pa" = "Punjabi"; "cloud_lng_language_pl" = "Polish"; +"cloud_lng_language_ps" = "Pashto"; "cloud_lng_language_pt" = "Portuguese"; "cloud_lng_language_ro" = "Romanian"; "cloud_lng_language_ru" = "Russian"; +"cloud_lng_language_rw" = "Kinyarwanda"; +"cloud_lng_language_sd" = "Sindhi"; +"cloud_lng_language_si" = "Sinhala"; "cloud_lng_language_sk" = "Slovak"; "cloud_lng_language_sl" = "Slovenian"; +"cloud_lng_language_sm" = "Samoan"; +"cloud_lng_language_sn" = "Shona"; +"cloud_lng_language_so" = "Somali"; +"cloud_lng_language_sq" = "Albanian"; +"cloud_lng_language_sr" = "Serbian"; +"cloud_lng_language_st" = "Sesotho"; +"cloud_lng_language_su" = "Sundanese"; +"cloud_lng_language_sv" = "Swedish"; +"cloud_lng_language_sw" = "Swahili"; +"cloud_lng_language_ta" = "Tamil"; +"cloud_lng_language_te" = "Telugu"; +"cloud_lng_language_tg" = "Tajik"; "cloud_lng_language_th" = "Thai"; "cloud_lng_language_tk" = "Turkmen"; +"cloud_lng_language_tl" = "Tagalog"; "cloud_lng_language_tr" = "Turkish"; +"cloud_lng_language_tt" = "Tatar"; +"cloud_lng_language_ug" = "Uyghur"; "cloud_lng_language_uk" = "Ukrainian"; +"cloud_lng_language_ur" = "Urdu"; "cloud_lng_language_uz" = "Uzbek"; "cloud_lng_language_vi" = "Vietnamese"; +"cloud_lng_language_xh" = "Xhosa"; +"cloud_lng_language_yi" = "Yiddish"; +"cloud_lng_language_yo" = "Yoruba"; +"cloud_lng_language_zh" = "Chinese"; +// "cloud_lng_language_zh-CN" = "Chinese (Simplified)"; +// "cloud_lng_language_zh-TW" = "Chinese (Traditional)"; +"cloud_lng_language_zu" = "Zulu"; diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 37e3631b56..779778df3e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -836,6 +836,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_disconnect" = "Disconnect"; "lng_settings_connected_title" = "Connected websites"; +"lng_settings_suggestion_phone_number_title" = "Is {phone} still your number?"; +"lng_settings_suggestion_phone_number_about" = "Keep your number up to date to ensure you can always log into Telegram. {link}"; +"lng_settings_suggestion_phone_number_about_link" = "https://telegram.org/faq#q-i-have-a-new-phone-number-what-do-i-do"; +"lng_settings_suggestion_phone_number_change" = "Please change your phone number in the official Telegram app on your phone as soon as possible. {emoji}"; + "lng_settings_power_menu" = "Battery and Animations"; "lng_settings_power_title" = "Power Usage"; "lng_settings_power_subtitle" = "Power saving options"; @@ -2251,6 +2256,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_peer_gifts_title" = "Gifts"; "lng_peer_gifts_about" = "These gifts were sent to {user} by other users."; "lng_peer_gifts_about_mine" = "These gifts were sent to you by other users. Click on a gift to convert it to Stars or change its privacy settings."; +"lng_peer_gifts_empty_search" = "No matching gifts"; +"lng_peer_gifts_view_all" = "View All Gifts"; "lng_peer_gifts_notify" = "Notify About New Gifts"; "lng_peer_gifts_notify_enabled" = "You will receive a message from Telegram when your channel receives a gift."; "lng_peer_gifts_filter_by_value" = "Sort by Value"; @@ -2823,6 +2830,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_box_history_entry_reaction_name" = "Star Reaction"; "lng_credits_box_history_entry_subscription" = "Monthly subscription fee"; "lng_credits_box_history_entry_gift_upgrade" = "Collectible Upgrade"; +"lng_credits_box_history_entry_gift_sold" = "Gift Sale"; +"lng_credits_box_history_entry_gift_bought" = "Gift Purchase"; +"lng_credits_box_history_entry_gift_sold_to" = "To"; +"lng_credits_box_history_entry_gift_full_price" = "Full Price"; +"lng_credits_box_history_entry_gift_bought_from" = "From"; "lng_credits_subscription_section" = "My subscriptions"; "lng_credits_box_subscription_title" = "Subscription"; @@ -2965,6 +2977,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_chatbots_include_button" = "Select Chats"; "lng_chatbots_exclude_about" = "Select chats or entire chat categories which the bot will not have access to."; "lng_chatbots_permissions_title" = "Bot permissions"; +"lng_chatbots_warning_title" = "Warning"; +"lng_chatbots_warning_both_text" = "The bot {bot} will be able to **manage your gifts and stars**, including giving them away to other users."; +"lng_chatbots_warning_gifts_text" = "The bot {bot} will be able to **manage your gifts**, including giving them away to other users."; +"lng_chatbots_warning_stars_text" = "The bot {bot} will be able to **transfer your stars**."; +"lng_chatbots_warning_username_text" = "The bot {bot} will be able to **set and remove usernames** for your account, which may result in the loss of your current username."; "lng_chatbots_manage_messages" = "Manage Messages"; "lng_chatbots_read" = "Read Messages"; @@ -3155,6 +3172,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boost_group_ask" = "Ask your **Premium** members to boost your group with this link:"; //"lng_boost_group_gifting" = "Boost your group by gifting your members Telegram Premium. {link}"; +"lng_boost_channel_title_autotranslate" = "Autotranslation of Messages"; +"lng_boost_channel_needs_level_autotranslate#one" = "Your channel needs to reach **Level {count}** to enable autotranslation of messages."; +"lng_boost_channel_needs_level_autotranslate#other" = "Your channel needs to reach **Level {count}** to enable autotranslation of messages."; + "lng_feature_stories#one" = "**{count}** Story Per Day"; "lng_feature_stories#other" = "**{count}** Stories Per Day"; "lng_feature_reactions#one" = "**{count}** Custom Reaction"; @@ -3173,6 +3194,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_feature_custom_background_group" = "Custom Group Background"; "lng_feature_custom_emoji_pack" = "Custom Emoji Pack"; "lng_feature_transcribe" = "Voice-to-Text Conversion"; +"lng_feature_autotranslate" = "Autotranslation of Messages"; "lng_giveaway_new_title" = "Boosts via Gifts"; "lng_giveaway_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers."; @@ -3421,10 +3443,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_stars_link" = "What are Stars >"; "lng_gift_stars_limited" = "limited"; "lng_gift_stars_sold_out" = "sold out"; +"lng_gift_stars_resale" = "resale"; +"lng_gift_stars_on_sale" = "on sale"; "lng_gift_stars_tabs_all" = "All Gifts"; "lng_gift_stars_tabs_my" = "My Gifts"; "lng_gift_stars_tabs_limited" = "Limited"; "lng_gift_stars_tabs_in_stock" = "In Stock"; +"lng_gift_stars_tabs_resale" = "Resale"; "lng_gift_send_title" = "Send a Gift"; "lng_gift_send_message" = "Enter Message"; "lng_gift_send_anonymous" = "Hide My Name"; @@ -3446,7 +3471,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_send_limited_left#other" = "{count} left"; "lng_gift_send_button" = "Send a Gift for {cost}"; "lng_gift_send_button_self" = "Buy a Gift for {cost}"; +"lng_gift_buy_resale_title" = "Buy {name}"; +"lng_gift_buy_resale_button" = "Buy for {cost}"; +"lng_gift_buy_resale_confirm" = "Do you want to buy {name} for {price} and gift it to {user}?"; +"lng_gift_buy_resale_confirm_self" = "Do you want to buy {name} for {price}?"; +"lng_gift_buy_price_change_title" = "Price change!"; +"lng_gift_buy_price_change_text" = "This gift price was changed and now is {price}. Do you still want to buy?"; "lng_gift_sent_title" = "Gift Sent!"; +"lng_gift_sent_resale_done" = "{user} has been notified about your gift."; +"lng_gift_sent_resale_done_self" = "{gift} is now yours."; "lng_gift_sent_about#one" = "You spent **{count}** Star from your balance."; "lng_gift_sent_about#other" = "You spent **{count}** Stars from your balance."; "lng_gift_limited_of_one" = "unique"; @@ -3573,6 +3606,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_transfer_button_for" = "Transfer for {price}"; "lng_gift_transfer_wear" = "Wear"; "lng_gift_transfer_take_off" = "Take Off"; +"lng_gift_transfer_sell" = "Sell"; +"lng_gift_transfer_update" = "Change Price"; +"lng_gift_transfer_unlist" = "Unlist"; +"lng_gift_sell_unlist_title" = "Unlist {name}"; +"lng_gift_sell_unlist_sure" = "Are you sure you want to unlist your gift?"; +"lng_gift_sell_title" = "Price in Stars"; +"lng_gift_sell_placeholder" = "Enter price in Stars"; +"lng_gift_sell_about" = "You will receive {percent} of the selected amount."; +"lng_gift_sell_amount#one" = "You will receive **{count}** Star."; +"lng_gift_sell_amount#other" = "You will receive **{count}** Stars."; +"lng_gift_sell_min_price#one" = "Minimum price is {count} Star."; +"lng_gift_sell_min_price#other" = "Minimum price is {count} Stars."; +"lng_gift_sell_put" = "Put for Sale"; +"lng_gift_sell_update" = "Update the Price"; +"lng_gift_sell_toast" = "{name} is now for sale!"; +"lng_gift_sell_updated" = "Sale price for {name} was updated."; +"lng_gift_sell_removed" = "{name} is removed from sale."; "lng_gift_menu_show" = "Show"; "lng_gift_menu_hide" = "Hide"; "lng_gift_wear_title" = "Wear {name}"; @@ -3589,6 +3639,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_wear_end_toast" = "You took off {name}"; "lng_gift_many_pinned_title" = "Too Many Pinned Gifts"; "lng_gift_many_pinned_choose" = "Select a gift to unpin below"; +"lng_gift_resale_price" = "Price"; +"lng_gift_resale_number" = "Number"; +"lng_gift_resale_date" = "Date"; +"lng_gift_resale_count#one" = "{count} gift in resale"; +"lng_gift_resale_count#other" = "{count} gifts in resale"; +"lng_gift_resale_sort_price" = "Sort by Price"; +"lng_gift_resale_sort_date" = "Sort by Date"; +"lng_gift_resale_sort_number" = "Sort by Number"; +"lng_gift_resale_filter_all" = "Select All"; +"lng_gift_resale_model" = "Model"; +"lng_gift_resale_models#one" = "{count} Model"; +"lng_gift_resale_models#other" = "{count} Models"; +"lng_gift_resale_backdrop" = "Backdrop"; +"lng_gift_resale_backdrops#one" = "{count} Backdrop"; +"lng_gift_resale_backdrops#other" = "{count} Backdrops"; +"lng_gift_resale_symbol" = "Symbol"; +"lng_gift_resale_symbols#one" = "{count} Symbol"; +"lng_gift_resale_symbols#other" = "{count} Symbols"; +"lng_gift_resale_early" = "You will be able to resell this gift in {duration}."; +"lng_gift_transfer_early" = "You will be able to transfer this gift in {duration}."; +"lng_gift_resale_transfer_early_title" = "Try Later"; "lng_accounts_limit_title" = "Limit Reached"; "lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account."; @@ -4242,6 +4313,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_sensitive_toast" = "You can update the visibility of sensitive media in **Settings > Chat Settings > Sensitive content**"; "lng_translate_show_original" = "Show Original"; +"lng_translate_return_original" = "View Original ({language})"; "lng_translate_bar_to" = "Translate to {name}"; "lng_translate_bar_to_other" = "Translate to {name}"; "lng_translate_menu_to" = "Translate To"; @@ -4349,6 +4421,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_edit_contact_title" = "Edit contact"; "lng_edit_channel_title" = "Edit channel"; "lng_edit_bot_title" = "Edit bot"; +"lng_edit_autotranslate" = "Auto-translate messages"; "lng_edit_sign_messages" = "Sign messages"; "lng_edit_sign_messages_about" = "Add names of admins to the messages they post."; "lng_edit_sign_profiles" = "Show authors' profiles"; @@ -5205,10 +5278,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_filter_all_actions" = "All actions"; "lng_admin_log_filter_actions_type_subtitle" = "Filter actions by type"; "lng_admin_log_filter_actions_member_section" = "Members And Admins"; +"lng_admin_log_filter_actions_subscriber_section" = "Subscribers And Admins"; "lng_admin_log_filter_restrictions" = "New restrictions"; "lng_admin_log_filter_admins_new" = "Admin rights"; "lng_admin_log_filter_members_new" = "New members"; +"lng_admin_log_filter_subscribers_new" = "New subscribers"; "lng_admin_log_filter_actions_settings_section" = "Group Settings"; +"lng_admin_log_filter_actions_channel_settings_section" = "Channel Settings"; "lng_admin_log_filter_info_group" = "Group info"; "lng_admin_log_filter_info_channel" = "Channel info"; "lng_admin_log_filter_actions_messages_section" = "Messages"; @@ -5219,6 +5295,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_filter_voice_chats_channel" = "Live stream"; "lng_admin_log_filter_invite_links" = "Invite links"; "lng_admin_log_filter_members_removed" = "Members leaving"; +"lng_admin_log_filter_subscribers_removed" = "Subscribers leaving"; "lng_admin_log_filter_topics" = "Topics"; "lng_admin_log_filter_sub_extend" = "Subscription Renewals"; "lng_admin_log_filter_all_admins" = "All users and admins"; @@ -5329,6 +5406,8 @@ 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_autotranslate_enabled" = "{from} enabled automatic translation"; +"lng_admin_log_autotranslate_disabled" = "{from} disabled automatic translation"; "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}"; diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc index 5feeeacbc6..50cf186e02 100644 --- a/Telegram/Resources/qrc/telegram/animations.qrc +++ b/Telegram/Resources/qrc/telegram/animations.qrc @@ -1,6 +1,7 @@ ../../animations/blocked_peers_empty.tgs + ../../animations/change_number.tgs ../../animations/filters.tgs ../../animations/cloud_filters.tgs ../../animations/local_passcode_enter.tgs diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 202f6b843a..0510faaad4 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.14.3.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index a0bb4a1fb6..9f31e5cc98 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 5,14,1,0 - PRODUCTVERSION 5,14,1,0 + FILEVERSION 5,14,3,0 + PRODUCTVERSION 5,14,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop" - VALUE "FileVersion", "5.14.1.0" + VALUE "FileVersion", "5.14.3.0" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "5.14.1.0" + VALUE "ProductVersion", "5.14.3.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index bf1bdc6e42..0812bee7bf 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 5,14,1,0 - PRODUCTVERSION 5,14,1,0 + FILEVERSION 5,14,3,0 + PRODUCTVERSION 5,14,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop Updater" - VALUE "FileVersion", "5.14.1.0" + VALUE "FileVersion", "5.14.3.0" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "5.14.1.0" + VALUE "ProductVersion", "5.14.3.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 2c06385f6d..ec49947ebf 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -157,6 +157,7 @@ constexpr auto kTransactionsLimit = 100; .converted = stargift && incoming, .stargift = stargift.has_value(), .giftUpgraded = tl.data().is_stargift_upgrade(), + .giftResale = tl.data().is_stargift_resale(), .reaction = tl.data().is_reaction(), .refunded = tl.data().is_refund(), .pending = tl.data().is_pending(), @@ -528,8 +529,12 @@ void EditCreditsSubscription( )).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send(); } -MTPInputSavedStarGift InputSavedStarGiftId(const Data::SavedStarGiftId &id) { - return id.isUser() +MTPInputSavedStarGift InputSavedStarGiftId( + const Data::SavedStarGiftId &id, + const std::shared_ptr &unique) { + return (!id && unique) + ? MTP_inputSavedStarGiftSlug(MTP_string(unique->slug)) + : id.isUser() ? MTP_inputSavedStarGiftUser(MTP_int(id.userMessageId().bare)) : MTP_inputSavedStarGiftChat( id.chat()->input, diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h index c9c87b2e8e..559e84711e 100644 --- a/Telegram/SourceFiles/api/api_credits.h +++ b/Telegram/SourceFiles/api/api_credits.h @@ -122,6 +122,7 @@ void EditCreditsSubscription( Fn fail); [[nodiscard]] MTPInputSavedStarGift InputSavedStarGiftId( - const Data::SavedStarGiftId &id); + const Data::SavedStarGiftId &id, + const std::shared_ptr &unique = nullptr); } // namespace Api diff --git a/Telegram/SourceFiles/api/api_earn.cpp b/Telegram/SourceFiles/api/api_earn.cpp index c44690151c..d0b58b40fc 100644 --- a/Telegram/SourceFiles/api/api_earn.cpp +++ b/Telegram/SourceFiles/api/api_earn.cpp @@ -46,11 +46,15 @@ void HandleWithdrawalButton( bool loading = false; }; - const auto channel = receiver.currencyReceiver; - const auto peer = receiver.creditsReceiver; + const auto currencyReceiver = receiver.currencyReceiver; + const auto creditsReceiver = receiver.creditsReceiver; + const auto isChannel = receiver.currencyReceiver + && receiver.currencyReceiver->isChannel(); const auto state = button->lifetime().make_state(); - const auto session = (channel ? &channel->session() : &peer->session()); + const auto session = (currencyReceiver + ? ¤cyReceiver->session() + : &creditsReceiver->session()); using ChannelOutUrl = MTPstats_BroadcastRevenueWithdrawalUrl; using CreditsOutUrl = MTPpayments_StarsRevenueWithdrawalUrl; @@ -59,7 +63,7 @@ void HandleWithdrawalButton( const auto processOut = [=] { if (state->loading) { return; - } else if (peer && !receiver.creditsAmount()) { + } else if (creditsReceiver && !receiver.creditsAmount()) { return; } state->loading = true; @@ -70,10 +74,10 @@ void HandleWithdrawalButton( state->loading = false; auto fields = PasscodeBox::CloudFields::From(pass); - fields.customTitle = channel + fields.customTitle = isChannel ? tr::lng_channel_earn_balance_password_title() : tr::lng_bot_earn_balance_password_title(); - fields.customDescription = channel + fields.customDescription = isChannel ? tr::lng_channel_earn_balance_password_description(tr::now) : tr::lng_bot_earn_balance_password_description(tr::now); fields.customSubmitButton = tr::lng_passcode_submit(); @@ -94,18 +98,18 @@ void HandleWithdrawalButton( show->showToast(message); } }; - if (channel) { + if (currencyReceiver) { session->api().request( MTPstats_GetBroadcastRevenueWithdrawalUrl( - channel->input, + currencyReceiver->input, result.result )).done([=](const ChannelOutUrl &r) { done(qs(r.data().vurl())); }).fail(fail).send(); - } else if (peer) { + } else if (creditsReceiver) { session->api().request( MTPpayments_GetStarsRevenueWithdrawalUrl( - peer->input, + creditsReceiver->input, MTP_long(receiver.creditsAmount()), result.result )).done([=](const CreditsOutUrl &r) { @@ -134,17 +138,17 @@ void HandleWithdrawalButton( processOut(); } }; - if (channel) { + if (currencyReceiver) { session->api().request( MTPstats_GetBroadcastRevenueWithdrawalUrl( - channel->input, + currencyReceiver->input, MTP_inputCheckPasswordEmpty() )).fail(fail).send(); - } else if (peer) { + } else if (creditsReceiver) { session->api().request( MTPpayments_GetStarsRevenueWithdrawalUrl( - peer->input, - MTP_long(std::numeric_limits::max()), + creditsReceiver->input, + MTP_long(receiver.creditsAmount()), MTP_inputCheckPasswordEmpty() )).fail(fail).send(); } diff --git a/Telegram/SourceFiles/api/api_earn.h b/Telegram/SourceFiles/api/api_earn.h index cbee5d25ab..4142c9453c 100644 --- a/Telegram/SourceFiles/api/api_earn.h +++ b/Telegram/SourceFiles/api/api_earn.h @@ -22,7 +22,7 @@ void RestrictSponsored( Fn failed); struct RewardReceiver final { - ChannelData *currencyReceiver = nullptr; + PeerData *currencyReceiver = nullptr; PeerData *creditsReceiver = nullptr; Fn creditsAmount; }; diff --git a/Telegram/SourceFiles/api/api_global_privacy.cpp b/Telegram/SourceFiles/api/api_global_privacy.cpp index 2a2def3c5a..0beb421833 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.cpp +++ b/Telegram/SourceFiles/api/api_global_privacy.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_global_privacy.h" #include "apiwrap.h" +#include "data/components/promo_suggestions.h" #include "data/data_user.h" #include "main/main_session.h" #include "main/main_app_config.h" @@ -101,13 +102,11 @@ rpl::producer GlobalPrivacy::showArchiveAndMute() const { } rpl::producer<> GlobalPrivacy::suggestArchiveAndMute() const { - return _session->appConfig().suggestionRequested( - u"AUTOARCHIVE_POPULAR"_q); + return _session->promoSuggestions().requested(u"AUTOARCHIVE_POPULAR"_q); } void GlobalPrivacy::dismissArchiveAndMuteSuggestion() { - _session->appConfig().dismissSuggestion( - u"AUTOARCHIVE_POPULAR"_q); + _session->promoSuggestions().dismiss(u"AUTOARCHIVE_POPULAR"_q); } void GlobalPrivacy::updateHideReadTime(bool hide) { diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index be3ead5afc..fc48c21c50 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -799,6 +799,7 @@ std::optional FromTL( return gift.match([&](const MTPDstarGift &data) { const auto document = session->data().processDocument( data.vsticker()); + const auto resellPrice = data.vresell_min_stars().value_or_empty(); const auto remaining = data.vavailability_remains(); const auto total = data.vavailability_total(); if (!document->sticker()) { @@ -809,7 +810,10 @@ std::optional FromTL( .stars = int64(data.vstars().v), .starsConverted = int64(data.vconvert_stars().v), .starsToUpgrade = int64(data.vupgrade_stars().value_or_empty()), + .starsResellMin = int64(resellPrice), .document = document, + .resellTitle = qs(data.vtitle().value_or_empty()), + .resellCount = int(data.vavailability_resale().value_or_empty()), .limitedLeft = remaining.value_or_empty(), .limitedCount = total.value_or_empty(), .firstSaleDate = data.vfirst_sale_date().value_or_empty(), @@ -849,6 +853,7 @@ std::optional FromTL( ? peerFromMTP(*data.vowner_id()) : PeerId()), .number = data.vnum().v, + .starsForResale = int(data.vresell_stars().value_or_empty()), .model = *model, .pattern = *pattern, }), @@ -881,6 +886,8 @@ std::optional FromTL( } else if (const auto unique = parsed->unique.get()) { unique->starsForTransfer = data.vtransfer_stars().value_or(-1); unique->exportAt = data.vcan_export_at().value_or_empty(); + unique->canTransferAt = data.vcan_transfer_at().value_or_empty(); + unique->canResellAt = data.vcan_resell_at().value_or_empty(); } using Id = Data::SavedStarGiftId; const auto hasUnique = parsed->unique != nullptr; @@ -936,7 +943,7 @@ Data::UniqueGiftPattern FromTL( } Data::UniqueGiftBackdrop FromTL(const MTPDstarGiftAttributeBackdrop &data) { - auto result = Data::UniqueGiftBackdrop(); + auto result = Data::UniqueGiftBackdrop{ .id = data.vbackdrop_id().v }; result.name = qs(data.vname()); result.rarityPermille = data.vrarity_permille().v; result.centerColor = Ui::ColorFromSerialized( diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 9dd15bdfba..2cdb347706 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/mtproto_dc_options.h" #include "data/business/data_shortcut_messages.h" #include "data/components/credits.h" +#include "data/components/promo_suggestions.h" #include "data/components/scheduled_messages.h" #include "data/components/top_peers.h" #include "data/notify/data_notify_settings.h" @@ -2076,6 +2077,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { case mtpc_updateConfig: { session().mtp().requestConfig(); + session().promoSuggestions().invalidate(); } break; case mtpc_updateUserPhone: { diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 6fc701b606..cf03883baa 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -97,8 +97,6 @@ namespace { // Save draft to the cloud with 1 sec extra delay. constexpr auto kSaveCloudDraftTimeout = 1000; -constexpr auto kTopPromotionInterval = TimeId(60 * 60); -constexpr auto kTopPromotionMinDelay = TimeId(10); constexpr auto kSmallDelayMs = 5; constexpr auto kReadFeaturedSetsTimeout = crl::time(1000); constexpr auto kFileLoaderQueueStopTimeout = crl::time(5000); @@ -169,7 +167,6 @@ ApiWrap::ApiWrap(not_null session) , _featuredSetsReadTimer([=] { readFeaturedSets(); }) , _dialogsLoadState(std::make_unique()) , _fileLoader(std::make_unique(kFileLoaderQueueStopTimeout)) -, _topPromotionTimer([=] { refreshTopPromotion(); }) , _updateNotifyTimer([=] { sendNotifySettingsUpdates(); }) , _statsSessionKillTimer([=] { checkStatsSessions(); }) , _authorizations(std::make_unique(this)) @@ -205,11 +202,6 @@ ApiWrap::ApiWrap(not_null session) }, _session->lifetime()); setupSupportMode(); - - Core::App().settings().proxy().connectionTypeValue( - ) | rpl::start_with_next([=] { - refreshTopPromotion(); - }, _session->lifetime()); }); } @@ -249,79 +241,6 @@ void ApiWrap::requestChangelog( //).send(); } -void ApiWrap::refreshTopPromotion() { - const auto now = base::unixtime::now(); - const auto next = (_topPromotionNextRequestTime != 0) - ? _topPromotionNextRequestTime - : now; - if (_topPromotionRequestId) { - getTopPromotionDelayed(now, next); - return; - } - const auto key = [&]() -> std::pair { - if (!Core::App().settings().proxy().isEnabled()) { - return {}; - } - const auto &proxy = Core::App().settings().proxy().selected(); - if (proxy.type != MTP::ProxyData::Type::Mtproto) { - return {}; - } - return { proxy.host, proxy.port }; - }(); - if (_topPromotionKey == key && now < next) { - getTopPromotionDelayed(now, next); - return; - } - _topPromotionKey = key; - _topPromotionRequestId = request(MTPhelp_GetPromoData( - )).done([=](const MTPhelp_PromoData &result) { - _topPromotionRequestId = 0; - topPromotionDone(result); - }).fail([=] { - _topPromotionRequestId = 0; - const auto now = base::unixtime::now(); - const auto next = _topPromotionNextRequestTime = now - + kTopPromotionInterval; - if (!_topPromotionTimer.isActive()) { - getTopPromotionDelayed(now, next); - } - }).send(); -} - -void ApiWrap::getTopPromotionDelayed(TimeId now, TimeId next) { - _topPromotionTimer.callOnce(std::min( - std::max(next - now, kTopPromotionMinDelay), - kTopPromotionInterval) * crl::time(1000)); -}; - -void ApiWrap::topPromotionDone(const MTPhelp_PromoData &proxy) { - _topPromotionNextRequestTime = proxy.match([&](const auto &data) { - return data.vexpires().v; - }); - getTopPromotionDelayed( - base::unixtime::now(), - _topPromotionNextRequestTime); - - const auto& settings = AyuSettings::getInstance(); - if (settings.disableAds) { - _session->data().setTopPromoted(nullptr, QString(), QString()); - return; - } - - proxy.match([&](const MTPDhelp_promoDataEmpty &data) { - _session->data().setTopPromoted(nullptr, QString(), QString()); - }, [&](const MTPDhelp_promoData &data) { - _session->data().processChats(data.vchats()); - _session->data().processUsers(data.vusers()); - const auto peerId = peerFromMTP(data.vpeer()); - const auto history = _session->data().history(peerId); - _session->data().setTopPromoted( - history, - data.vpsa_type().value_or_empty(), - data.vpsa_message().value_or_empty()); - }); -} - void ApiWrap::requestDeepLinkInfo( const QString &path, Fn callback) { diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 0817560b7f..529114b0a1 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -200,7 +200,6 @@ public: void requestChangelog( const QString &sinceVersion, Fn callback); - void refreshTopPromotion(); void requestDeepLinkInfo( const QString &path, Fn callback); @@ -569,9 +568,6 @@ private: not_null album, Fn done = nullptr); - void getTopPromotionDelayed(TimeId now, TimeId next); - void topPromotionDone(const MTPhelp_PromoData &proxy); - void sendNotifySettingsUpdates(); template @@ -709,11 +705,6 @@ private: std::unique_ptr _fileLoader; base::flat_map> _sendingAlbums; - mtpRequestId _topPromotionRequestId = 0; - std::pair _topPromotionKey; - TimeId _topPromotionNextRequestTime = TimeId(0); - base::Timer _topPromotionTimer; - base::flat_set> _updateNotifyTopics; base::flat_set> _updateNotifyPeers; base::flat_set _updateNotifyDefaults; diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp index 6630c0428b..4cd55628b0 100644 --- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp @@ -316,16 +316,23 @@ void FillChooseFilterMenu( return; } const auto session = &strong->session(); - const auto count = session->data().chatsFilters().list().size(); - if ((count - 1) >= limit()) { + const auto &list = session->data().chatsFilters().list(); + if ((list.size() - 1) >= limit()) { return; } + const auto chooseNextId = [&] { + auto id = 2; + while (ranges::contains(list, id, &Data::ChatFilter::id)) { + ++id; + } + return id; + }; auto filter = Data::ChatFilter({}, {}, {}, {}, {}, { history }, {}, {}); const auto send = [=](const Data::ChatFilter &filter) { session->api().request(MTPmessages_UpdateDialogFilter( MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter), - MTP_int(count), + MTP_int(chooseNextId()), filter.tl() )).done([=] { session->data().chatsFilters().reload(); diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 691821c16c..cb5dee6e26 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/replace_boost_box.h" // BoostsForGift. #include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox. #include "boxes/star_gift_box.h" // ShowStarGiftBox. -#include "boxes/transfer_gift_box.h" // ShowTransferGiftBox. #include "core/ui_integration.h" #include "data/data_boosts.h" #include "data/data_changes.h" @@ -1336,7 +1335,13 @@ void AddStarGiftTable( }, tooltip->lifetime()); }; - if (unique && entry.bareGiftOwnerId) { + if (unique && entry.bareGiftResaleRecipientId) { + AddTableRow( + table, + tr::lng_credits_box_history_entry_peer(), + MakePeerTableValue(table, show, PeerId(entry.bareGiftResaleRecipientId)), + st::giveawayGiftCodePeerMargin); + } else if (unique && entry.bareGiftOwnerId) { const auto ownerId = PeerId(entry.bareGiftOwnerId); const auto was = std::make_shared>(); const auto handleChange = [=]( @@ -1621,7 +1626,17 @@ void AddCreditsHistoryEntryTable( const auto starrefRecipientId = PeerId(entry.starrefRecipientId); const auto session = &show->session(); if (entry.starrefCommission) { - if (entry.starrefAmount) { + if (entry.giftResale && entry.starrefCommission < 1000) { + const auto full = int(base::SafeRound(entry.credits.value() + / (1. - (entry.starrefCommission / 1000.)))); + auto value = Ui::Text::IconEmoji(&st::starIconEmojiColored); + const auto starsText = Lang::FormatStarsAmountDecimal( + StarsAmount{ full }); + AddTableRow( + table, + tr::lng_credits_box_history_entry_gift_full_price(), + rpl::single(value.append(' ' + starsText))); + } else if (entry.starrefAmount) { AddTableRow( table, tr::lng_star_ref_commission_title(), @@ -1635,7 +1650,7 @@ void AddCreditsHistoryEntryTable( Ui::Text::WithEntities)); } } - if (starrefRecipientId && entry.starrefAmount) { + if (starrefRecipientId && entry.starrefAmount && !entry.giftResale) { AddTableRow( table, tr::lng_credits_box_history_entry_affiliate(), @@ -1645,7 +1660,9 @@ void AddCreditsHistoryEntryTable( if (peerId && entry.starrefCommission) { AddTableRow( table, - (entry.starrefAmount + (entry.giftResale + ? tr::lng_credits_box_history_entry_gift_sold_to + : entry.starrefAmount ? tr::lng_credits_box_history_entry_referred : tr::lng_credits_box_history_entry_miniapp)(), show, @@ -1656,6 +1673,8 @@ void AddCreditsHistoryEntryTable( ? tr::lng_credits_box_history_entry_referred() : entry.in ? tr::lng_credits_box_history_entry_peer_in() + : entry.giftResale + ? tr::lng_credits_box_history_entry_gift_bought_from() : entry.giftUpgraded ? tr::lng_credits_box_history_entry_gift_from() : tr::lng_credits_box_history_entry_peer(); diff --git a/Telegram/SourceFiles/boxes/moderate_messages_box.cpp b/Telegram/SourceFiles/boxes/moderate_messages_box.cpp index 0fa39c2ee4..fbe6ee72b4 100644 --- a/Telegram/SourceFiles/boxes/moderate_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/moderate_messages_box.cpp @@ -77,7 +77,7 @@ ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) { if (author == peer) { return {}; } else if (const auto channel = author->asChannel()) { - if (channel->linkedChat() == peer) { + if (channel->discussionLink() == peer) { return {}; } } diff --git a/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_discussion_link_box.cpp similarity index 96% rename from Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp rename to Telegram/SourceFiles/boxes/peers/edit_discussion_link_box.cpp index bae3d8a769..ab2089b6a1 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_discussion_link_box.cpp @@ -5,7 +5,7 @@ the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ -#include "boxes/peers/edit_linked_chat_box.h" +#include "boxes/peers/edit_discussion_link_box.h" #include "lang/lang_keys.h" #include "data/data_channel.h" @@ -224,7 +224,7 @@ void Controller::choose(not_null chat) { return tr::lng_manage_discussion_group_about(Ui::Text::WithEntities); } -[[nodiscard]] object_ptr EditLinkedChatBox( +[[nodiscard]] object_ptr EditDiscussionLinkBox( not_null navigation, not_null channel, ChannelData *chat, @@ -272,7 +272,7 @@ void Controller::choose(not_null chat) { Settings::AddButtonWithIcon( above, tr::lng_manage_discussion_group_create(), - st::infoCreateLinkedChatButton, + st::infoCreateDiscussionLinkButton, { &st::menuIconGroupCreate } )->addClickHandler([=, parent = above.data()] { const auto guarded = crl::guard(parent, callback); @@ -292,7 +292,7 @@ void Controller::choose(not_null chat) { (channel->isBroadcast() ? tr::lng_manage_discussion_group_unlink : tr::lng_manage_linked_channel_unlink)(), - st::infoUnlinkChatButton, + st::infoUnlinkDiscussionLinkButton, { &st::menuIconRemove } )->addClickHandler([=] { callback(nullptr); }); Ui::AddSkip(below); @@ -327,12 +327,12 @@ void Controller::choose(not_null chat) { } // namespace -object_ptr EditLinkedChatBox( +object_ptr EditDiscussionLinkBox( not_null navigation, not_null channel, std::vector> &&chats, Fn callback) { - return EditLinkedChatBox( + return EditDiscussionLinkBox( navigation, channel, nullptr, @@ -341,13 +341,13 @@ object_ptr EditLinkedChatBox( callback); } -object_ptr EditLinkedChatBox( +object_ptr EditDiscussionLinkBox( not_null navigation, not_null channel, not_null chat, bool canEdit, Fn callback) { - return EditLinkedChatBox( + return EditDiscussionLinkBox( navigation, channel, chat, diff --git a/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.h b/Telegram/SourceFiles/boxes/peers/edit_discussion_link_box.h similarity index 86% rename from Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.h rename to Telegram/SourceFiles/boxes/peers/edit_discussion_link_box.h index 788f57757e..113a9ca1f5 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_discussion_link_box.h @@ -17,14 +17,14 @@ namespace Window { class SessionNavigation; } // namespace Window -[[nodiscard]] object_ptr EditLinkedChatBox( +[[nodiscard]] object_ptr EditDiscussionLinkBox( not_null navigation, not_null channel, not_null chat, bool canEdit, Fn callback); -[[nodiscard]] object_ptr EditLinkedChatBox( +[[nodiscard]] object_ptr EditDiscussionLinkBox( not_null navigation, not_null channel, std::vector> &&chats, diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index c457ce579a..c1c160ac08 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -1482,7 +1482,11 @@ void CheckBoostLevel( show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{ .link = qs(data.vboost_url()), .boost = counters, + .features = (peer->isChannel() + ? LookupBoostFeatures(peer->asChannel()) + : Ui::BoostFeatures()), .reason = *reason, + .group = !peer->isBroadcast(), }, openStatistics, nullptr)); cancel(); }).fail([=](const MTP::Error &error) { diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 75ca484b89..333196057c 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -22,7 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_peer_history_visibility_box.h" #include "boxes/peers/edit_peer_permissions_box.h" #include "boxes/peers/edit_peer_invite_links.h" -#include "boxes/peers/edit_linked_chat_box.h" +#include "boxes/peers/edit_discussion_link_box.h" #include "boxes/peers/edit_peer_requests_box.h" #include "boxes/peers/edit_peer_reactions.h" #include "boxes/peers/replace_boost_box.h" @@ -358,12 +358,13 @@ private: std::optional description; std::optional hiddenPreHistory; std::optional forum; + std::optional autotranslate; std::optional signatures; std::optional signatureProfiles; std::optional noForwards; std::optional joinToWrite; std::optional requestToJoin; - std::optional linkedChat; + std::optional discussionLink; }; [[nodiscard]] object_ptr createPhotoAndTitleEdit(); @@ -379,12 +380,13 @@ private: void refreshForumToggleLocked(); void showEditPeerTypeBox( std::optional> error = {}); - void showEditLinkedChatBox(); + void showEditDiscussionLinkBox(); void fillPrivacyTypeButton(); - void fillLinkedChatButton(); + void fillDiscussionLinkButton(); //void fillInviteLinkButton(); void fillForumButton(); void fillColorIndexButton(); + void fillAutoTranslateButton(); void fillSignaturesButton(); void fillHistoryVisibilityButton(); void fillManageSection(); @@ -408,11 +410,12 @@ private: [[nodiscard]] std::optional validate() const; [[nodiscard]] bool validateUsernamesOrder(Saving &to) const; [[nodiscard]] bool validateUsername(Saving &to) const; - [[nodiscard]] bool validateLinkedChat(Saving &to) const; + [[nodiscard]] bool validateDiscussionLink(Saving &to) const; [[nodiscard]] bool validateTitle(Saving &to) const; [[nodiscard]] bool validateDescription(Saving &to) const; [[nodiscard]] bool validateHistoryVisibility(Saving &to) const; [[nodiscard]] bool validateForum(Saving &to) const; + [[nodiscard]] bool validateAutotranslate(Saving &to) const; [[nodiscard]] bool validateSignatures(Saving &to) const; [[nodiscard]] bool validateForwards(Saving &to) const; [[nodiscard]] bool validateJoinToWrite(Saving &to) const; @@ -421,11 +424,12 @@ private: void save(); void saveUsernamesOrder(); void saveUsername(); - void saveLinkedChat(); + void saveDiscussionLink(); void saveTitle(); void saveDescription(); void saveHistoryVisibility(); void saveForum(); + void saveAutotranslate(); void saveSignatures(); void saveForwards(); void saveJoinToWrite(); @@ -446,12 +450,13 @@ private: void subscribeToMigration(); void migrate(not_null channel); - std::optional _linkedChatSavedValue; - ChannelData *_linkedChatOriginalValue = nullptr; + std::optional _discussionLinkSavedValue; + ChannelData *_discussionLinkOriginalValue = nullptr; bool _channelHasLocationOriginalValue = false; std::optional _historyVisibilitySavedValue; std::optional _typeDataSavedValue; std::optional _forumSavedValue; + std::optional _autotranslateSavedValue; std::optional _signaturesSavedValue; std::optional _signatureProfilesSavedValue; @@ -474,8 +479,8 @@ private: }; const rpl::event_stream _privacyTypeUpdates; - const rpl::event_stream _linkedChatUpdates; - mtpRequestId _linkedChatsRequestId = 0; + const rpl::event_stream _discussionLinkUpdates; + mtpRequestId _discussionLinksRequestId = 0; rpl::lifetime _lifetime; @@ -813,7 +818,7 @@ void Controller::refreshHistoryVisibility() { _controls.historyVisibilityWrap->toggle( (!withUsername && !_channelHasLocationOriginalValue - && (!_linkedChatSavedValue || !*_linkedChatSavedValue) + && (!_discussionLinkSavedValue || !*_discussionLinkSavedValue) && (!_forumSavedValue || !*_forumSavedValue)), anim::type::instant); } @@ -825,8 +830,8 @@ void Controller::showEditPeerTypeBox( _typeDataSavedValue = data; refreshHistoryVisibility(); }); - _typeDataSavedValue->hasLinkedChat - = (_linkedChatSavedValue.value_or(nullptr) != nullptr); + _typeDataSavedValue->hasDiscussionLink + = (_discussionLinkSavedValue.value_or(nullptr) != nullptr); const auto box = _navigation->parentController()->show( Box( _navigation, @@ -841,7 +846,7 @@ void Controller::showEditPeerTypeBox( }, box->lifetime()); } -void Controller::showEditLinkedChatBox() { +void Controller::showEditDiscussionLinkBox() { Expects(_peer->isChannel()); if (_forumSavedValue && *_forumSavedValue) { @@ -855,8 +860,8 @@ void Controller::showEditLinkedChatBox() { if (*box) { (*box)->closeBox(); } - *_linkedChatSavedValue = result; - _linkedChatUpdates.fire_copy(result); + *_discussionLinkSavedValue = result; + _discussionLinkUpdates.fire_copy(result); refreshHistoryVisibility(); refreshForumToggleLocked(); }; @@ -867,31 +872,31 @@ void Controller::showEditLinkedChatBox() { && (!channel->hiddenPreHistory() || channel->canEditPreHistoryHidden())); - if (const auto chat = *_linkedChatSavedValue) { - *box = _navigation->parentController()->show(EditLinkedChatBox( + if (const auto chat = *_discussionLinkSavedValue) { + *box = _navigation->parentController()->show(EditDiscussionLinkBox( _navigation, channel, chat, canEdit, callback)); return; - } else if (!canEdit || _linkedChatsRequestId) { + } else if (!canEdit || _discussionLinksRequestId) { return; } else if (channel->isMegagroup()) { if (_forumSavedValue && *_forumSavedValue - && _linkedChatOriginalValue) { + && _discussionLinkOriginalValue) { ShowForumForDiscussionError(_navigation); } else { - // Restore original linked channel. - callback(_linkedChatOriginalValue); + // Restore original discussion link. + callback(_discussionLinkOriginalValue); } return; } - _linkedChatsRequestId = _api.request( + _discussionLinksRequestId = _api.request( MTPchannels_GetGroupsForDiscussion() ).done([=](const MTPmessages_Chats &result) { - _linkedChatsRequestId = 0; + _discussionLinksRequestId = 0; const auto list = result.match([&](const auto &data) { return data.vchats().v; }); @@ -900,13 +905,13 @@ void Controller::showEditLinkedChatBox() { for (const auto &item : list) { chats.emplace_back(_peer->owner().processChat(item)); } - *box = _navigation->parentController()->show(EditLinkedChatBox( + *box = _navigation->parentController()->show(EditDiscussionLinkBox( _navigation, channel, std::move(chats), callback)); }).fail([=] { - _linkedChatsRequestId = 0; + _discussionLinksRequestId = 0; }).send(); } @@ -972,11 +977,11 @@ void Controller::fillPrivacyTypeButton() { }); } -void Controller::fillLinkedChatButton() { +void Controller::fillDiscussionLinkButton() { Expects(_controls.buttonsLayout != nullptr); - _linkedChatSavedValue = _linkedChatOriginalValue = _peer->isChannel() - ? _peer->asChannel()->linkedChat() + _discussionLinkSavedValue = _discussionLinkOriginalValue = _peer->isChannel() + ? _peer->asChannel()->discussionLink() : nullptr; const auto isGroup = (_peer->isChat() || _peer->isMegagroup()); @@ -985,7 +990,7 @@ void Controller::fillLinkedChatButton() { : rpl::combine( tr::lng_manage_linked_channel(), tr::lng_manage_linked_channel_restore(), - _linkedChatUpdates.events() + _discussionLinkUpdates.events() ) | rpl::map([=]( const QString &edit, const QString &restore, @@ -993,13 +998,13 @@ void Controller::fillLinkedChatButton() { return chat ? edit : restore; }); auto label = isGroup - ? _linkedChatUpdates.events( + ? _discussionLinkUpdates.events( ) | rpl::map([](ChannelData *chat) { return chat ? chat->name() : QString(); }) | rpl::type_erased() : rpl::combine( tr::lng_manage_discussion_group_add(), - _linkedChatUpdates.events() + _discussionLinkUpdates.events() ) | rpl::map([=](const QString &add, ChannelData *chat) { return chat ? chat->name() : add; }) | rpl::type_erased(); @@ -1007,9 +1012,9 @@ void Controller::fillLinkedChatButton() { _controls.buttonsLayout, std::move(text), std::move(label), - [=] { showEditLinkedChatBox(); }, + [=] { showEditDiscussionLinkBox(); }, { isGroup ? &st::menuIconChannel : &st::menuIconGroups }); - _linkedChatUpdates.fire_copy(*_linkedChatSavedValue); + _discussionLinkUpdates.fire_copy(*_discussionLinkSavedValue); } // //void Controller::fillInviteLinkButton() { @@ -1044,7 +1049,7 @@ void Controller::fillForumButton() { ) | rpl::start_with_next([=](bool toggled) { if (_controls.forumToggleLocked && toggled) { unlocks->fire(false); - if (_linkedChatSavedValue && *_linkedChatSavedValue) { + if (_discussionLinkSavedValue && *_discussionLinkSavedValue) { ShowForumForDiscussionError(_navigation); } else { _navigation->showToast( @@ -1074,8 +1079,8 @@ void Controller::refreshForumToggleLocked() { const auto channel = _peer->asChannel(); const auto notenough = !_peer->isForum() && ((chat ? chat->count : channel->membersCount()) < limit); - const auto linked = _linkedChatSavedValue - && *_linkedChatSavedValue; + const auto linked = _discussionLinkSavedValue + && *_discussionLinkSavedValue; const auto locked = _controls.forumToggleLocked = notenough || linked; _controls.forumToggle->setToggleLocked(locked); } @@ -1091,6 +1096,69 @@ void Controller::fillColorIndexButton() { st::managePeerColorsButton); } +void Controller::fillAutoTranslateButton() { + Expects(_controls.buttonsLayout != nullptr); + + const auto channel = _peer->asBroadcast(); + if (!channel) { + return; + } + + const auto requiredLevel = Data::LevelLimits(&channel->session()) + .channelAutoTranslateLevelMin(); + const auto autotranslate = _controls.buttonsLayout->add( + EditPeerInfoBox::CreateButton( + _controls.buttonsLayout, + tr::lng_edit_autotranslate(), + rpl::single(QString()), + [] {}, + st::manageGroupTopicsButton, + { &st::menuIconTranslate })); + struct State { + rpl::event_stream toggled; + rpl::variable isLocked = false; + }; + const auto state = autotranslate->lifetime().make_state(); + autotranslate->toggleOn(rpl::single( + channel->autoTranslation() + ) | rpl::then(state->toggled.events())); + state->isLocked = (channel->levelHint() < requiredLevel); + const auto reason = Ui::AskBoostReason{ + .data = Ui::AskBoostAutotranslate{ .requiredLevel = requiredLevel }, + }; + + state->isLocked.value() | rpl::start_with_next([=](bool locked) { + autotranslate->setToggleLocked(locked); + }, autotranslate->lifetime()); + + autotranslate->toggledChanges( + ) | rpl::start_with_next([=](bool value) { + if (!state->isLocked.current()) { + _autotranslateSavedValue = value; + } else if (value) { + state->toggled.fire(false); + auto weak = Ui::MakeWeak(autotranslate); + CheckBoostLevel( + _navigation->uiShow(), + _peer, + [=](int level) { + if (const auto strong = weak.data()) { + state->isLocked = (level < requiredLevel); + } + return (level < requiredLevel) + ? std::make_optional(reason) + : std::nullopt; + }, + [] {}); + } + }, autotranslate->lifetime()); + + autotranslate->toggledValue( + ) | rpl::start_with_next([=](bool toggled) { + _autotranslateSavedValue = toggled; + }, _controls.buttonsLayout->lifetime()); +} + void Controller::fillSignaturesButton() { Expects(_controls.buttonsLayout != nullptr); @@ -1253,6 +1321,8 @@ void Controller::fillManageSection() { const auto canEditSignatures = isChannel && channel->canEditSignatures() && !channel->isMegagroup(); + const auto canEditAutoTranslate = isChannel + && channel->canEditAutoTranslate(); const auto canEditPreHistoryHidden = isChannel ? channel->canEditPreHistoryHidden() : chat->canEditPreHistoryHidden(); @@ -1283,8 +1353,8 @@ void Controller::fillManageSection() { const auto canEditStickers = isChannel && channel->canEditStickers(); const auto canDeleteChannel = isChannel && channel->canDelete(); const auto canEditColorIndex = isChannel && channel->canEditEmoji(); - const auto canViewOrEditLinkedChat = isChannel - && (channel->linkedChat() + const auto canViewOrEditDiscussionLink = isChannel + && (channel->discussionLink() || (channel->isBroadcast() && channel->canEditInformation())); ::AddSkip(_controls.buttonsLayout, 0); @@ -1294,8 +1364,8 @@ void Controller::fillManageSection() { //} else if (canEditInviteLinks) { // fillInviteLinkButton(); } - if (canViewOrEditLinkedChat) { - fillLinkedChatButton(); + if (canViewOrEditDiscussionLink) { + fillDiscussionLinkButton(); } if (canEditPreHistoryHidden) { fillHistoryVisibilityButton(); @@ -1306,13 +1376,16 @@ void Controller::fillManageSection() { if (canEditColorIndex) { fillColorIndexButton(); } + if (canEditAutoTranslate) { + fillAutoTranslateButton(); + } if (canEditSignatures) { fillSignaturesButton(); } else if (canEditPreHistoryHidden || canEditForum || canEditColorIndex //|| canEditInviteLinks - || canViewOrEditLinkedChat + || canViewOrEditDiscussionLink || canEditType) { ::AddSkip(_controls.buttonsLayout); } @@ -1336,6 +1409,8 @@ void Controller::fillManageSection() { ? tr::lng_manage_peer_reactions_on(tr::now) : some ? QString::number(some) + : allowed.paidEnabled + ? QString::number(1) : tr::lng_manage_peer_reactions_off(tr::now); }); AddButtonWithCount( @@ -1538,7 +1613,11 @@ void Controller::editReactions() { strong->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{ .link = link, .boost = counters, + .features = (_peer->isChannel() + ? LookupBoostFeatures(_peer->asChannel()) + : Ui::BoostFeatures()), .reason = { Ui::AskBoostCustomReactions{ required } }, + .group = !_peer->isBroadcast(), }, openStatistics, nullptr)); } }; @@ -1887,11 +1966,12 @@ std::optional Controller::validate() const { auto result = Saving(); if (validateUsernamesOrder(result) && validateUsername(result) - && validateLinkedChat(result) + && validateDiscussionLink(result) && validateTitle(result) && validateDescription(result) && validateHistoryVisibility(result) && validateForum(result) + && validateAutotranslate(result) && validateSignatures(result) && validateForwards(result) && validateJoinToWrite(result) @@ -1928,11 +2008,11 @@ bool Controller::validateUsername(Saving &to) const { return true; } -bool Controller::validateLinkedChat(Saving &to) const { - if (!_linkedChatSavedValue) { +bool Controller::validateDiscussionLink(Saving &to) const { + if (!_discussionLinkSavedValue) { return true; } - to.linkedChat = *_linkedChatSavedValue; + to.discussionLink = *_discussionLinkSavedValue; return true; } @@ -1979,6 +2059,14 @@ bool Controller::validateForum(Saving &to) const { return true; } +bool Controller::validateAutotranslate(Saving &to) const { + if (!_autotranslateSavedValue.has_value()) { + return true; + } + to.autotranslate = _autotranslateSavedValue; + return true; +} + bool Controller::validateSignatures(Saving &to) const { Expects(_signaturesSavedValue.has_value() == _signatureProfilesSavedValue.has_value()); @@ -2025,11 +2113,12 @@ void Controller::save() { _savingData = *saving; pushSaveStage([=] { saveUsernamesOrder(); }); pushSaveStage([=] { saveUsername(); }); - pushSaveStage([=] { saveLinkedChat(); }); + pushSaveStage([=] { saveDiscussionLink(); }); pushSaveStage([=] { saveTitle(); }); pushSaveStage([=] { saveDescription(); }); pushSaveStage([=] { saveHistoryVisibility(); }); pushSaveStage([=] { saveForum(); }); + pushSaveStage([=] { saveAutotranslate(); }); pushSaveStage([=] { saveSignatures(); }); pushSaveStage([=] { saveForwards(); }); pushSaveStage([=] { saveJoinToWrite(); }); @@ -2147,36 +2236,37 @@ void Controller::saveUsername() { }).send(); } -void Controller::saveLinkedChat() { +void Controller::saveDiscussionLink() { const auto channel = _peer->asChannel(); if (!channel) { return continueSave(); } - if (!_savingData.linkedChat - || *_savingData.linkedChat == channel->linkedChat()) { + if (!_savingData.discussionLink + || *_savingData.discussionLink == channel->discussionLink()) { return continueSave(); } - const auto chat = *_savingData.linkedChat; + const auto chat = *_savingData.discussionLink; if (channel->isBroadcast() && chat && chat->hiddenPreHistory()) { togglePreHistoryHidden( chat, false, - [=] { saveLinkedChat(); }, + [=] { saveDiscussionLink(); }, [=] { cancelSave(); }); return; } - const auto input = *_savingData.linkedChat - ? (*_savingData.linkedChat)->inputChannel + const auto input = *_savingData.discussionLink + ? (*_savingData.discussionLink)->inputChannel : MTP_inputChannelEmpty(); _api.request(MTPchannels_SetDiscussionGroup( (channel->isBroadcast() ? channel->inputChannel : input), (channel->isBroadcast() ? input : channel->inputChannel) )).done([=] { - channel->setLinkedChat(*_savingData.linkedChat); + channel->setDiscussionLink(*_savingData.discussionLink); continueSave(); - }).fail([=] { + }).fail([=](const MTP::Error &error) { + _navigation->showToast(error.type()); cancelSave(); }).send(); } @@ -2416,6 +2506,30 @@ void Controller::saveForum() { if (error.type() == u"CHAT_NOT_MODIFIED"_q) { continueSave(); } else { + _navigation->showToast(error.type()); + cancelSave(); + } + }).send(); +} + +void Controller::saveAutotranslate() { + const auto channel = _peer->asBroadcast(); + if (!_savingData.autotranslate + || !channel + || (*_savingData.autotranslate == channel->autoTranslation())) { + return continueSave(); + } + _api.request(MTPchannels_ToggleAutotranslation( + channel->inputChannel, + MTP_bool(*_savingData.autotranslate) + )).done([=](const MTPUpdates &result) { + channel->session().api().applyUpdates(result); + continueSave(); + }).fail([=](const MTP::Error &error) { + if (error.type() == u"CHAT_NOT_MODIFIED"_q) { + continueSave(); + } else { + _navigation->showToast(error.type()); cancelSave(); } }).send(); @@ -2450,6 +2564,7 @@ void Controller::saveSignatures() { if (error.type() == u"CHAT_NOT_MODIFIED"_q) { continueSave(); } else { + _navigation->showToast(error.type()); cancelSave(); } }).send(); @@ -2470,6 +2585,7 @@ void Controller::saveForwards() { if (error.type() == u"CHAT_NOT_MODIFIED"_q) { continueSave(); } else { + _navigation->showToast(error.type()); cancelSave(); } }).send(); @@ -2492,6 +2608,7 @@ void Controller::saveJoinToWrite() { if (error.type() == u"CHAT_NOT_MODIFIED"_q) { continueSave(); } else { + _navigation->showToast(error.type()); cancelSave(); } }).send(); @@ -2514,6 +2631,7 @@ void Controller::saveRequestToJoin() { if (error.type() == u"CHAT_NOT_MODIFIED"_q) { continueSave(); } else { + _navigation->showToast(error.type()); cancelSave(); } }).send(); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index 31f22a4f24..876df975d9 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -1140,7 +1140,7 @@ void ShowEditPeerPermissionsBox( result.emplace( Flag::ChangeInfo | Flag::PinMessages, tr::lng_rights_permission_unavailable(tr::now)); - } else if (channel->isMegagroup() && channel->linkedChat()) { + } else if (channel->isMegagroup() && channel->discussionLink()) { result.emplace( Flag::ChangeInfo | Flag::PinMessages, tr::lng_rights_permission_in_discuss(tr::now)); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp index b77a9e1d29..2b3665aac6 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp @@ -220,7 +220,7 @@ void Controller::createContent() { const auto wrap = _controls.whoSendWrap->entity(); Ui::AddSkip(wrap); - if (_dataSavedValue->hasLinkedChat) { + if (_dataSavedValue->hasDiscussionLink) { Ui::AddSubsectionTitle(wrap, tr::lng_manage_peer_send_title()); _controls.joinToWrite = wrap->add(EditPeerInfoBox::CreateButton( @@ -498,7 +498,7 @@ void Controller::privacyChanged(Privacy value) { } _controls.whoSendWrap->toggle( (value == Privacy::HasUsername - || (_dataSavedValue && _dataSavedValue->hasLinkedChat)), + || (_dataSavedValue && _dataSavedValue->hasDiscussionLink)), anim::type::instant); }; const auto refreshVisibilities = [&] { diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h index ae77872074..7b9aba6f9a 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h @@ -37,7 +37,7 @@ struct EditPeerTypeData { Privacy privacy = Privacy::NoUsername; QString username; std::vector usernamesOrder; - bool hasLinkedChat = false; + bool hasDiscussionLink = false; bool noForwards = false; bool joinToWrite = false; bool requestToJoin = false; diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp index 354b1bb5a5..85be6dd1ce 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp @@ -449,6 +449,7 @@ Ui::BoostFeatures LookupBoostFeatures(not_null channel) { .nameColorsByLevel = std::move(nameColorsByLevel), .linkStylesByLevel = std::move(linkStylesByLevel), .linkLogoLevel = group ? 0 : levelLimits.channelBgIconLevelMin(), + .autotranslateLevel = group ? 0 : levelLimits.channelAutoTranslateLevelMin(), .transcribeLevel = group ? levelLimits.groupTranscribeLevelMin() : 0, .emojiPackLevel = group ? levelLimits.groupEmojiStickersLevelMin() : 0, .emojiStatusLevel = group diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index d275a4fee5..3d96299190 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -153,6 +153,10 @@ void EditPriceBox( field->resize(width, field->height()); wrap->resize(width, field->height()); }, wrap->lifetime()); + field->paintRequest() | rpl::start_with_next([=](QRect clip) { + auto p = QPainter(field); + st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width()); + }, field->lifetime()); field->selectAll(); box->setFocusCallback([=] { field->setFocusFast(); @@ -173,11 +177,6 @@ void EditPriceBox( return false; }); - field->paintRequest() | rpl::start_with_next([=](QRect clip) { - auto p = QPainter(field); - st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width()); - }, field->lifetime()); - const auto save = [=] { const auto now = field->getLastText().toULongLong(); if (now > limit) { diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index 4247e936c3..d021cba69f 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_global_privacy.h" #include "api/api_premium.h" #include "base/event_filter.h" +#include "base/qt_signal_producer.h" #include "base/random.h" #include "base/timer_rpl.h" #include "base/unixtime.h" @@ -30,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_selector.h" #include "core/ui_integration.h" +#include "data/components/promo_suggestions.h" #include "data/data_birthday.h" #include "data/data_changes.h" #include "data/data_channel.h" @@ -57,6 +59,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lottie/lottie_single_player.h" #include "main/main_app_config.h" #include "main/main_session.h" +#include "menu/gift_resale_filter.h" #include "payments/payments_form.h" #include "payments/payments_checkout_process.h" #include "payments/payments_non_panel_process.h" @@ -82,10 +85,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/ui_utility.h" #include "ui/vertical_list.h" #include "ui/widgets/fields/input_field.h" +#include "ui/widgets/fields/number_input.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/shadow.h" +#include "ui/wrap/fade_wrap.h" #include "ui/wrap/slide_wrap.h" #include "window/themes/window_theme.h" #include "window/section_widget.h" @@ -108,8 +113,9 @@ namespace { constexpr auto kPriceTabAll = 0; constexpr auto kPriceTabInStock = -1; -constexpr auto kPriceTabLimited = -2; -constexpr auto kPriceTabMy = -3; +constexpr auto kPriceTabResale = -2; +constexpr auto kPriceTabLimited = -3; +constexpr auto kPriceTabMy = -4; constexpr auto kMyGiftsPerPage = 50; constexpr auto kGiftMessageLimit = 255; constexpr auto kSentToastDuration = 3 * crl::time(1000); @@ -117,6 +123,8 @@ constexpr auto kSwitchUpgradeCoverInterval = 3 * crl::time(1000); constexpr auto kCrossfadeDuration = crl::time(400); constexpr auto kUpgradeDoneToastDuration = 4 * crl::time(1000); constexpr auto kGiftsPreloadTimeout = 3 * crl::time(1000); +constexpr auto kResaleGiftsPerPage = 50; +constexpr auto kFiltersCount = 4; using namespace HistoryView; using namespace Info::PeerGifts; @@ -128,6 +136,20 @@ enum class PickType { }; using PickCallback = Fn, PickType)>; +enum class AttributeIdType { + Model, + Pattern, + Backdrop, +}; + +struct AttributeId { + uint64 value = 0; + AttributeIdType type = AttributeIdType::Model; + + friend inline auto operator<=>(AttributeId, AttributeId) = default; + friend inline bool operator==(AttributeId, AttributeId) = default; +}; + struct PremiumGiftsDescriptor { std::vector list; std::shared_ptr api; @@ -138,6 +160,50 @@ struct MyGiftsDescriptor { QString offset; }; +struct ModelCount { + Data::UniqueGiftModel model; + int count = 0; +}; + +struct BackdropCount { + Data::UniqueGiftBackdrop backdrop; + int count = 0; +}; + +struct PatternCount { + Data::UniqueGiftPattern pattern; + int count = 0; +}; + +enum class ResaleSort { + Date, + Price, + Number, +}; + +struct ResaleGiftsDescriptor { + uint64 giftId = 0; + QString title; + QString offset; + std::vector list; + std::vector models; + std::vector backdrops; + std::vector patterns; + uint64 attributesHash = 0; + int count = 0; + ResaleSort sort = ResaleSort::Date; +}; + +struct ResaleFilter { + uint64 attributesHash = 0; + base::flat_set attributes; + ResaleSort sort = ResaleSort::Price; + + friend inline bool operator==( + const ResaleFilter &, + const ResaleFilter &) = default; +}; + struct GiftsDescriptor { std::vector list; std::shared_ptr api; @@ -267,6 +333,53 @@ private: }; +[[nodiscard]] AttributeId FromTL(const MTPStarGiftAttributeId &id) { + return id.match([&](const MTPDstarGiftAttributeIdBackdrop &data) { + return AttributeId{ + .value = uint64(uint32(data.vbackdrop_id().v)), + .type = AttributeIdType::Backdrop, + }; + }, [&](const MTPDstarGiftAttributeIdModel &data) { + return AttributeId{ + .value = data.vdocument_id().v, + .type = AttributeIdType::Model, + }; + }, [&](const MTPDstarGiftAttributeIdPattern &data) { + return AttributeId{ + .value = data.vdocument_id().v, + .type = AttributeIdType::Pattern, + }; + }); +} + +[[nodiscard]] MTPStarGiftAttributeId AttributeToTL(AttributeId id) { + switch (id.type) { + case AttributeIdType::Backdrop: + return MTP_starGiftAttributeIdBackdrop( + MTP_int(int32(uint32(id.value)))); + case AttributeIdType::Model: + return MTP_starGiftAttributeIdModel(MTP_long(id.value)); + case AttributeIdType::Pattern: + return MTP_starGiftAttributeIdPattern(MTP_long(id.value)); + } + Unexpected("Invalid attribute id type"); +} + +[[nodiscard]] AttributeId IdFor(const Data::UniqueGiftBackdrop &value) { + return { + .value = uint64(uint32(value.id)), + .type = AttributeIdType::Backdrop, + }; +} + +[[nodiscard]] AttributeId IdFor(const Data::UniqueGiftModel &value) { + return { .value = value.document->id, .type = AttributeIdType::Model }; +} + +[[nodiscard]] AttributeId IdFor(const Data::UniqueGiftPattern &value) { + return { .value = value.document->id, .type = AttributeIdType::Pattern }; +} + [[nodiscard]] bool SortForBirthday(not_null peer) { const auto user = peer->asUser(); if (!user) { @@ -816,7 +929,9 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { const auto allowLimited = !(disallowedTypes & Type::Limited); const auto allowUnlimited = !(disallowedTypes & Type::Unlimited); const auto allowUnique = !(disallowedTypes & Type::Unique); - if (!gift.info.limitedCount) { + if (gift.resale) { + return allowUnique; + } else if (!gift.info.limitedCount) { return allowUnlimited; } return allowLimited || (gift.info.starsToUpgrade && allowUnique); @@ -859,9 +974,15 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { list.reserve(gifts.size()); for (auto &gift : gifts) { list.push_back({ .info = gift }); + if (gift.resellCount > 0) { + list.push_back({ .info = gift, .resale = true }); + } } ranges::stable_sort(list, [](const auto &a, const auto &b) { - return a.info.soldOut < b.info.soldOut; + const auto soldOut = [](const auto &gift) { + return gift.info.soldOut && !gift.resale; + }; + return soldOut(a) < soldOut(b); }); auto &map = Map[session]; @@ -931,6 +1052,8 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { return simple(tr::lng_gift_stars_tabs_limited(tr::now)); } else if (price == kPriceTabInStock) { return simple(tr::lng_gift_stars_tabs_in_stock(tr::now)); + } else if (price == kPriceTabResale) { + return simple(tr::lng_gift_stars_tabs_resale(tr::now)); } auto &manager = session->data().customEmojiManager(); auto result = Text::String(); @@ -942,6 +1065,417 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { return result; } +[[nodiscard]] Text::String ResaleTabText(QString text) { + auto result = Text::String(); + result.setMarkedText( + st::semiboldTextStyle, + TextWithEntities{ text }.append(st::giftBoxResaleTabsDropdown), + kMarkupTextOptions); + return result; +} + +[[nodiscard]] Text::String SortModeText(ResaleSort mode) { + auto text = [&] { + if (mode == ResaleSort::Number) { + return Ui::Text::IconEmoji(&st::giftBoxResaleMiniNumber).append( + tr::lng_gift_resale_number(tr::now)); + } else if (mode == ResaleSort::Price) { + return Ui::Text::IconEmoji(&st::giftBoxResaleMiniPrice).append( + tr::lng_gift_resale_price(tr::now)); + } + return Ui::Text::IconEmoji(&st::giftBoxResaleMiniDate).append( + tr::lng_gift_resale_date(tr::now)); + }(); + auto result = Text::String(); + result.setMarkedText( + st::semiboldTextStyle, + text, + kMarkupTextOptions); + return result; +} + +struct ResaleTabs { + rpl::producer filter; + object_ptr widget; +}; +[[nodiscard]] ResaleTabs MakeResaleTabs( + not_null window, + not_null peer, + const ResaleGiftsDescriptor &info, + rpl::producer filter) { + auto widget = object_ptr((QWidget*)nullptr); + const auto raw = widget.data(); + + struct Button { + QRect geometry; + Text::String text; + }; + struct State { + rpl::variable filter; + rpl::variable fullWidth; + std::vector