Merge tag 'v5.14.3' into dev
|
@ -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
|
# Telegram Desktop API Usage
|
||||||
|
|
||||||
## API Schema
|
## 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`**
|
1. **`Telegram/SourceFiles/mtproto/scheme/mtproto.tl`**
|
||||||
* Defines the core MTProto protocol types and methods used for basic communication, encryption, authorization, service messages, etc.
|
* 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),
|
MTP_long(randomId),
|
||||||
// ... other arguments matching the TL definition
|
// ... other arguments matching the TL definition
|
||||||
MTP_vector<MTPMessageEntity>() // Example for a vector argument
|
MTP_vector<MTPMessageEntity>() // Example for a vector argument
|
||||||
)).done([=](const MTPResponseType &result) {
|
)).done([=]\(const MTPResponseType &result) {
|
||||||
// Handle the successful response (result).
|
// Handle the successful response (result).
|
||||||
// 'result' will be of the C++ type corresponding to the TL type
|
// 'result' will be of the C++ type corresponding to the TL type
|
||||||
// specified after the '=' in the api.tl method definition.
|
// 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):
|
// 1. Multiple Constructors (e.g., User = User | UserEmpty):
|
||||||
// Use .match() with lambdas for each constructor:
|
// Use .match() with lambdas for each constructor:
|
||||||
result.match([&](const MTPDuser &data) {
|
result.match([&]\(const MTPDuser &data) {
|
||||||
/* use data.vfirst_name().v, etc. */
|
/* use data.vfirst_name().v, etc. */
|
||||||
}, [&](const MTPDuserEmpty &data) {
|
}, [&]\(const MTPDuserEmpty &data) {
|
||||||
/* handle empty user */
|
/* handle empty user */
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -64,7 +69,7 @@ api().request(MTPnamespace_MethodName(
|
||||||
|
|
||||||
// 2. Single Constructor (e.g., Messages = messages { msgs: vector<Message> }):
|
// 2. Single Constructor (e.g., Messages = messages { msgs: vector<Message> }):
|
||||||
// Use .match() with a single lambda:
|
// 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:
|
// Or check the type explicitly and use the constructor getter:
|
||||||
if (result.type() == mtpc_messages) {
|
if (result.type() == mtpc_messages) {
|
||||||
|
@ -76,7 +81,7 @@ api().request(MTPnamespace_MethodName(
|
||||||
const auto &data = result.data(); // Only works for single-constructor types!
|
const auto &data = result.data(); // Only works for single-constructor types!
|
||||||
// use data.vmessages().v
|
// use data.vmessages().v
|
||||||
|
|
||||||
}).fail([=](const MTP::Error &error) {
|
}).fail([=]\(const MTP::Error &error) {
|
||||||
// Handle the API error (error).
|
// Handle the API error (error).
|
||||||
// 'error' is an MTP::Error object containing the error code (error.type())
|
// 'error' is an MTP::Error object containing the error code (error.type())
|
||||||
// and description (error.description()). Check for specific error strings.
|
// 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.
|
* 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.
|
* 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.
|
* 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 **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 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).
|
* 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()`.
|
* Directly construct the `MTPnamespace_MethodName(...)` object inside `request()`.
|
||||||
* Include `.handleFloodErrors()` before `.send()` for standard flood wait handling.
|
* Include `.handleFloodErrors()` before `.send()` for standard flood wait handling.
|
|
@ -1,3 +1,8 @@
|
||||||
|
---
|
||||||
|
description: For tasks requiring changing or adding user facing phrases and text parts.
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
# Telegram Desktop Localization
|
# Telegram Desktop Localization
|
||||||
|
|
||||||
## Coding Style Note
|
## Coding Style Note
|
||||||
|
@ -156,4 +161,4 @@ auto messageProducer = tr::lng_user_posted_photo( // Type: rpl::producer<TextWit
|
||||||
* Immediate: Pass `int` or `float64`.
|
* Immediate: Pass `int` or `float64`.
|
||||||
* Reactive: Pass `rpl::producer<float64>`, typically by converting an `int` producer using `| tr::to_count()`.
|
* Reactive: Pass `rpl::producer<float64>`, 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.
|
* Optional projector function as the last argument modifies the output type and required placeholder types.
|
||||||
* Actual translations are loaded at runtime from the API.
|
* Actual translations are loaded at runtime from the API.
|
|
@ -1,3 +1,8 @@
|
||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
# RPL (Reactive Programming Library) Guide
|
# RPL (Reactive Programming Library) Guide
|
||||||
|
|
||||||
## Coding Style Note
|
## Coding Style Note
|
||||||
|
@ -64,7 +69,7 @@ rpl::lifetime lifetime;
|
||||||
// Counter is consumed here, use std::move if it's an l-value.
|
// Counter is consumed here, use std::move if it's an l-value.
|
||||||
std::move(
|
std::move(
|
||||||
counter
|
counter
|
||||||
) | rpl::start_with_next([=](int nextValue) {
|
) | rpl::start_with_next([=]\(int nextValue) {
|
||||||
// Process the next integer value emitted by the producer.
|
// Process the next integer value emitted by the producer.
|
||||||
qDebug() << "Received: " << nextValue;
|
qDebug() << "Received: " << nextValue;
|
||||||
}, lifetime); // Pass the lifetime to manage the subscription.
|
}, lifetime); // Pass the lifetime to manage the subscription.
|
||||||
|
@ -77,7 +82,7 @@ std::move(
|
||||||
auto counter2 = /* ... */; // Type: rpl::producer<int>
|
auto counter2 = /* ... */; // Type: rpl::producer<int>
|
||||||
rpl::lifetime subscriptionLifetime = std::move(
|
rpl::lifetime subscriptionLifetime = std::move(
|
||||||
counter2
|
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 returned lifetime MUST be stored. If it's discarded immediately,
|
||||||
// the subscription stops instantly.
|
// the subscription stops instantly.
|
||||||
// `counter2` is also moved-from here.
|
// `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.
|
// If it's the only use, std::move(dataStream) would be preferred.
|
||||||
rpl::duplicate(
|
rpl::duplicate(
|
||||||
dataStream
|
dataStream
|
||||||
) | rpl::start_with_error([=](Error &&error) {
|
) | rpl::start_with_error([=]\(Error &&error) {
|
||||||
// Handle the error signaled by the producer.
|
// Handle the error signaled by the producer.
|
||||||
qDebug() << "Error: " << error.text();
|
qDebug() << "Error: " << error.text();
|
||||||
}, lifetime);
|
}, lifetime);
|
||||||
|
@ -101,7 +106,7 @@ rpl::duplicate(
|
||||||
// Using dataStream again, perhaps duplicated again or moved if last use.
|
// Using dataStream again, perhaps duplicated again or moved if last use.
|
||||||
rpl::duplicate(
|
rpl::duplicate(
|
||||||
dataStream
|
dataStream
|
||||||
) | rpl::start_with_done([=]() {
|
) | rpl::start_with_done([=] {
|
||||||
// Execute when the producer signals it's finished emitting values.
|
// Execute when the producer signals it's finished emitting values.
|
||||||
qDebug() << "Stream finished.";
|
qDebug() << "Stream finished.";
|
||||||
}, lifetime);
|
}, lifetime);
|
||||||
|
@ -110,9 +115,9 @@ rpl::duplicate(
|
||||||
std::move(
|
std::move(
|
||||||
dataStream
|
dataStream
|
||||||
) | rpl::start_with_next_error_done(
|
) | rpl::start_with_next_error_done(
|
||||||
[=](QString &&value) { /* handle next value */ },
|
[=]\(QString &&value) { /* handle next value */ },
|
||||||
[=](Error &&error) { /* handle error */ },
|
[=]\(Error &&error) { /* handle error */ },
|
||||||
[=]() { /* handle done */ },
|
[=] { /* handle done */ },
|
||||||
lifetime);
|
lifetime);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -164,7 +169,7 @@ You can combine multiple producers into one:
|
||||||
// The lambda receives unpacked arguments, not the tuple itself.
|
// The lambda receives unpacked arguments, not the tuple itself.
|
||||||
std::move(
|
std::move(
|
||||||
combined
|
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.
|
// No need for std::get<0>(latest), etc.
|
||||||
qDebug() << "Combined: Count=" << count << ", Text=" << text;
|
qDebug() << "Combined: Count=" << count << ", Text=" << text;
|
||||||
}, lifetime);
|
}, lifetime);
|
||||||
|
@ -172,11 +177,11 @@ You can combine multiple producers into one:
|
||||||
// This also works with map, filter, etc.
|
// This also works with map, filter, etc.
|
||||||
std::move(
|
std::move(
|
||||||
combined
|
combined
|
||||||
) | rpl::filter([=](int count, const QString &text) {
|
) | rpl::filter([=]\(int count, const QString &text) {
|
||||||
return count > 0 && !text.isEmpty();
|
return count > 0 && !text.isEmpty();
|
||||||
}) | rpl::map([=](int count, const QString &text) {
|
}) | rpl::map([=]\(int count, const QString &text) {
|
||||||
return text.repeated(count);
|
return text.repeated(count);
|
||||||
}) | rpl::start_with_next([=](const QString &result) {
|
}) | rpl::start_with_next([=]\(const QString &result) {
|
||||||
qDebug() << "Mapped & Filtered: " << result;
|
qDebug() << "Mapped & Filtered: " << result;
|
||||||
}, lifetime);
|
}, lifetime);
|
||||||
```
|
```
|
||||||
|
@ -192,7 +197,7 @@ You can combine multiple producers into one:
|
||||||
// Starting the merged producer consumes it.
|
// Starting the merged producer consumes it.
|
||||||
std::move(
|
std::move(
|
||||||
merged
|
merged
|
||||||
) | rpl::start_with_next([=](QString &&value) {
|
) | rpl::start_with_next([=]\(QString &&value) {
|
||||||
// Receives values from either sourceA or sourceB as they arrive.
|
// Receives values from either sourceA or sourceB as they arrive.
|
||||||
qDebug() << "Merged value: " << value;
|
qDebug() << "Merged value: " << value;
|
||||||
}, lifetime);
|
}, lifetime);
|
||||||
|
@ -208,4 +213,4 @@ You can combine multiple producers into one:
|
||||||
* Use `rpl::combine` or `rpl::merge` to combine streams.
|
* Use `rpl::combine` or `rpl::merge` to combine streams.
|
||||||
* When starting a chain (`std::move(producer) | rpl::map(...)`), explicitly move the initial producer.
|
* When starting a chain (`std::move(producer) | rpl::map(...)`), explicitly move the initial producer.
|
||||||
* These functions typically duplicate their input producers internally.
|
* These functions typically duplicate their input producers internally.
|
||||||
* Starting a pipeline consumes the producer; use `
|
* Starting a pipeline consumes the producer; use `
|
|
@ -1,3 +1,8 @@
|
||||||
|
---
|
||||||
|
description: For tasks requiring working with user facing UI components.
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
# Telegram Desktop UI Styling
|
# Telegram Desktop UI Styling
|
||||||
|
|
||||||
## Style Definition Files
|
## 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.
|
* 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.
|
* 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.
|
* 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`).
|
* Access style properties via the generated `st::` objects (e.g., `st::primaryButton.height`, `st::chatInput.backgroundColor`).
|
|
@ -5,11 +5,9 @@
|
||||||
"vscode": {
|
"vscode": {
|
||||||
"settings": {
|
"settings": {
|
||||||
"C_Cpp.intelliSenseEngine": "disabled",
|
"C_Cpp.intelliSenseEngine": "disabled",
|
||||||
"clangd.arguments": [
|
|
||||||
"--compile-commands-dir=${workspaceFolder}/out"
|
|
||||||
],
|
|
||||||
"cmake.generator": "Ninja Multi-Config",
|
"cmake.generator": "Ninja Multi-Config",
|
||||||
"cmake.buildDirectory": "${workspaceFolder}/out"
|
"cmake.buildDirectory": "${workspaceFolder}/out",
|
||||||
|
"cmake.copyCompileCommands": "${workspaceFolder}/compile_commands.json"
|
||||||
},
|
},
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"ms-vscode.cpptools-extension-pack",
|
"ms-vscode.cpptools-extension-pack",
|
||||||
|
|
54
.github/workflows/linux.yml
vendored
|
@ -12,6 +12,7 @@ on:
|
||||||
- '!.github/workflows/linux.yml'
|
- '!.github/workflows/linux.yml'
|
||||||
- 'snap/**'
|
- 'snap/**'
|
||||||
- 'Telegram/build/**'
|
- 'Telegram/build/**'
|
||||||
|
- '!Telegram/build/docker/centos_env/**'
|
||||||
- 'Telegram/Resources/uwp/**'
|
- 'Telegram/Resources/uwp/**'
|
||||||
- 'Telegram/Resources/winrc/**'
|
- 'Telegram/Resources/winrc/**'
|
||||||
- 'Telegram/SourceFiles/platform/win/**'
|
- 'Telegram/SourceFiles/platform/win/**'
|
||||||
|
@ -30,6 +31,7 @@ on:
|
||||||
- '!.github/workflows/linux.yml'
|
- '!.github/workflows/linux.yml'
|
||||||
- 'snap/**'
|
- 'snap/**'
|
||||||
- 'Telegram/build/**'
|
- 'Telegram/build/**'
|
||||||
|
- '!Telegram/build/docker/centos_env/**'
|
||||||
- 'Telegram/Resources/uwp/**'
|
- 'Telegram/Resources/uwp/**'
|
||||||
- 'Telegram/Resources/winrc/**'
|
- 'Telegram/Resources/winrc/**'
|
||||||
- 'Telegram/SourceFiles/platform/win/**'
|
- 'Telegram/SourceFiles/platform/win/**'
|
||||||
|
@ -52,27 +54,44 @@ jobs:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
UPLOAD_ARTIFACT: "true"
|
UPLOAD_ARTIFACT: "true"
|
||||||
|
ONLY_CACHE: "false"
|
||||||
|
IMAGE_TAG: tdesktop:centos_env
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Get repository name.
|
|
||||||
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Clone.
|
- name: Clone.
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
path: ${{ env.REPO_NAME }}
|
|
||||||
|
|
||||||
- name: First set up.
|
- name: First set up.
|
||||||
run: |
|
run: |
|
||||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
|
sudo apt update
|
||||||
docker pull ghcr.io/$GITHUB_REPOSITORY/centos_env
|
curl -sSL https://install.python-poetry.org | python3 -
|
||||||
docker tag ghcr.io/$GITHUB_REPOSITORY/centos_env tdesktop:centos_env
|
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.
|
- name: Telegram Desktop build.
|
||||||
|
if: env.ONLY_CACHE == 'false'
|
||||||
run: |
|
run: |
|
||||||
cd $REPO_NAME
|
|
||||||
|
|
||||||
DEFINE=""
|
DEFINE=""
|
||||||
if [ -n "${{ matrix.defines }}" ]; then
|
if [ -n "${{ matrix.defines }}" ]; then
|
||||||
DEFINE="-D ${{ matrix.defines }}=ON"
|
DEFINE="-D ${{ matrix.defines }}=ON"
|
||||||
|
@ -86,21 +105,22 @@ jobs:
|
||||||
-u $(id -u) \
|
-u $(id -u) \
|
||||||
-v $PWD:/usr/src/tdesktop \
|
-v $PWD:/usr/src/tdesktop \
|
||||||
-e CONFIG=Debug \
|
-e CONFIG=Debug \
|
||||||
tdesktop:centos_env \
|
$IMAGE_TAG \
|
||||||
/usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \
|
/usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \
|
||||||
-D CMAKE_C_FLAGS_DEBUG="" \
|
-D CMAKE_CONFIGURATION_TYPES=Debug \
|
||||||
-D CMAKE_CXX_FLAGS_DEBUG="" \
|
-D CMAKE_C_FLAGS_DEBUG="-O0 -U_FORTIFY_SOURCE" \
|
||||||
-D CMAKE_C_FLAGS="-Werror" \
|
-D CMAKE_CXX_FLAGS_DEBUG="-O0 -U_FORTIFY_SOURCE" \
|
||||||
-D CMAKE_CXX_FLAGS="-Werror" \
|
|
||||||
-D CMAKE_EXE_LINKER_FLAGS="-s" \
|
-D CMAKE_EXE_LINKER_FLAGS="-s" \
|
||||||
|
-D CMAKE_COMPILE_WARNING_AS_ERROR=ON \
|
||||||
-D TDESKTOP_API_TEST=ON \
|
-D TDESKTOP_API_TEST=ON \
|
||||||
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \
|
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \
|
||||||
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
|
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
|
||||||
$DEFINE
|
$DEFINE
|
||||||
|
|
||||||
- name: Check.
|
- name: Check.
|
||||||
|
if: env.ONLY_CACHE == 'false'
|
||||||
run: |
|
run: |
|
||||||
filePath="$REPO_NAME/out/Debug/Telegram"
|
filePath="out/Debug/Telegram"
|
||||||
if test -f "$filePath"; then
|
if test -f "$filePath"; then
|
||||||
echo "Build successfully done! :)"
|
echo "Build successfully done! :)"
|
||||||
|
|
||||||
|
@ -114,7 +134,7 @@ jobs:
|
||||||
- name: Move artifact.
|
- name: Move artifact.
|
||||||
if: env.UPLOAD_ARTIFACT == 'true'
|
if: env.UPLOAD_ARTIFACT == 'true'
|
||||||
run: |
|
run: |
|
||||||
cd $REPO_NAME/out/Debug
|
cd out/Debug
|
||||||
mkdir artifact
|
mkdir artifact
|
||||||
mv {Telegram,Updater} artifact/
|
mv {Telegram,Updater} artifact/
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
|
@ -122,4 +142,4 @@ jobs:
|
||||||
name: Upload artifact.
|
name: Upload artifact.
|
||||||
with:
|
with:
|
||||||
name: ${{ env.ARTIFACT_NAME }}
|
name: ${{ env.ARTIFACT_NAME }}
|
||||||
path: ${{ env.REPO_NAME }}/out/Debug/artifact/
|
path: out/Debug/artifact/
|
||||||
|
|
6
.github/workflows/win.yml
vendored
|
@ -177,11 +177,11 @@ jobs:
|
||||||
%TDESKTOP_BUILD_GENERATOR% ^
|
%TDESKTOP_BUILD_GENERATOR% ^
|
||||||
%TDESKTOP_BUILD_ARCH% ^
|
%TDESKTOP_BUILD_ARCH% ^
|
||||||
%TDESKTOP_BUILD_API% ^
|
%TDESKTOP_BUILD_API% ^
|
||||||
-D CMAKE_C_FLAGS="/WX" ^
|
-D CMAKE_CONFIGURATION_TYPES=Debug ^
|
||||||
-D CMAKE_CXX_FLAGS="/WX" ^
|
-D CMAKE_COMPILE_WARNING_AS_ERROR=ON ^
|
||||||
|
-D CMAKE_MSVC_DEBUG_INFORMATION_FORMAT= ^
|
||||||
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^
|
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^
|
||||||
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
|
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
|
||||||
-D DESKTOP_APP_NO_PDB=ON ^
|
|
||||||
%TDESKTOP_BUILD_DEFINE%
|
%TDESKTOP_BUILD_DEFINE%
|
||||||
|
|
||||||
cmake --build ..\out --config Debug --parallel
|
cmake --build ..\out --config Debug --parallel
|
||||||
|
|
1
.gitignore
vendored
|
@ -20,6 +20,7 @@ ipch/
|
||||||
.vs/
|
.vs/
|
||||||
.vscode/
|
.vscode/
|
||||||
.cache/
|
.cache/
|
||||||
|
compile_commands.json
|
||||||
|
|
||||||
/Telegram/log.txt
|
/Telegram/log.txt
|
||||||
/Telegram/data
|
/Telegram/data
|
||||||
|
|
|
@ -4,15 +4,7 @@
|
||||||
# For license and copyright information please follow this link:
|
# For license and copyright information please follow this link:
|
||||||
# https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
# https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
if (APPLE)
|
cmake_minimum_required(VERSION 3.25...3.31)
|
||||||
# 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()
|
|
||||||
|
|
||||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||||
|
|
||||||
|
|
|
@ -296,8 +296,8 @@ PRIVATE
|
||||||
boxes/peers/edit_contact_box.h
|
boxes/peers/edit_contact_box.h
|
||||||
boxes/peers/edit_forum_topic_box.cpp
|
boxes/peers/edit_forum_topic_box.cpp
|
||||||
boxes/peers/edit_forum_topic_box.h
|
boxes/peers/edit_forum_topic_box.h
|
||||||
boxes/peers/edit_linked_chat_box.cpp
|
boxes/peers/edit_discussion_link_box.cpp
|
||||||
boxes/peers/edit_linked_chat_box.h
|
boxes/peers/edit_discussion_link_box.h
|
||||||
boxes/peers/edit_members_visible.cpp
|
boxes/peers/edit_members_visible.cpp
|
||||||
boxes/peers/edit_members_visible.h
|
boxes/peers/edit_members_visible.h
|
||||||
boxes/peers/edit_participant_box.cpp
|
boxes/peers/edit_participant_box.cpp
|
||||||
|
@ -583,6 +583,8 @@ PRIVATE
|
||||||
data/components/factchecks.h
|
data/components/factchecks.h
|
||||||
data/components/location_pickers.cpp
|
data/components/location_pickers.cpp
|
||||||
data/components/location_pickers.h
|
data/components/location_pickers.h
|
||||||
|
data/components/promo_suggestions.cpp
|
||||||
|
data/components/promo_suggestions.h
|
||||||
data/components/recent_peers.cpp
|
data/components/recent_peers.cpp
|
||||||
data/components/recent_peers.h
|
data/components/recent_peers.h
|
||||||
data/components/scheduled_messages.cpp
|
data/components/scheduled_messages.cpp
|
||||||
|
|
BIN
Telegram/Resources/icons/menu/order_date.png
Normal file
After Width: | Height: | Size: 585 B |
BIN
Telegram/Resources/icons/menu/order_date@2x.png
Normal file
After Width: | Height: | Size: 1,015 B |
BIN
Telegram/Resources/icons/menu/order_date@3x.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/menu/order_number.png
Normal file
After Width: | Height: | Size: 680 B |
BIN
Telegram/Resources/icons/menu/order_number@2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/menu/order_number@3x.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/menu/order_price.png
Normal file
After Width: | Height: | Size: 813 B |
BIN
Telegram/Resources/icons/menu/order_price@2x.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/menu/order_price@3x.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram/Resources/icons/menu/tag_sell.png
Normal file
After Width: | Height: | Size: 377 B |
BIN
Telegram/Resources/icons/menu/tag_sell@2x.png
Normal file
After Width: | Height: | Size: 675 B |
BIN
Telegram/Resources/icons/menu/tag_sell@3x.png
Normal file
After Width: | Height: | Size: 930 B |
12
Telegram/Resources/icons/settings/mini_gift_order_date.svg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>Mini / mini_gift_sorting2</title>
|
||||||
|
<g id="Mini-/-mini_gift_sorting2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<path d="M39.7913043,28.3413043 C40.9234881,28.3413043 41.8413043,29.2591206 41.8413043,30.3913043 L41.8413043,45 C41.8413043,46.1321837 40.9234881,47.05 39.7913043,47.05 C38.6591206,47.05 37.7413043,46.1321837 37.7413043,45 L37.7413043,30.3913043 C37.7413043,29.2591206 38.6591206,28.3413043 39.7913043,28.3413043 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
<path d="M32.1330398,38.0495689 C32.9336146,38.8501437 34.2316028,38.8501437 35.0321776,38.0495689 L39.7906087,33.291 L44.5504311,38.0495689 C45.3088704,38.8080082 46.5137416,38.847926 47.3191738,38.1693225 L47.4495689,38.0495689 C48.2501437,37.2489941 48.2501437,35.9510059 47.4495689,35.1504311 L41.7573686,29.4582308 C40.6715413,28.3724035 38.9110674,28.3724035 37.8252401,29.4582308 L32.1330398,35.1504311 C31.332465,35.9510059 31.332465,37.2489941 32.1330398,38.0495689 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
<path d="M36.5217391,5.3326087 C40.4153466,5.3326087 43.5717391,8.48900121 43.5717391,12.3826087 L43.5717391,21.7217391 C43.5717391,22.8539229 42.6539229,23.7717391 41.5217391,23.7717391 C40.3895554,23.7717391 39.4717391,22.8539229 39.4717391,21.7217391 L39.4717391,12.3826087 C39.4717391,10.7533687 38.1509791,9.4326087 36.5217391,9.4326087 L10,9.4326087 C8.37075999,9.4326087 7.05,10.7533687 7.05,12.3826087 L7.05,35.9826087 C7.05,37.6118487 8.37075999,38.9326087 10,38.9326087 L27.373913,38.9326087 C28.5060968,38.9326087 29.423913,39.850425 29.423913,40.9826087 C29.423913,42.1147924 28.5060968,43.0326087 27.373913,43.0326087 L10,43.0326087 C6.10639251,43.0326087 2.95,39.8762162 2.95,35.9826087 L2.95,12.3826087 C2.95,8.48900121 6.10639251,5.3326087 10,5.3326087 L36.5217391,5.3326087 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
<polygon id="Path" fill="#FFFFFF" fill-rule="nonzero" points="6.09565217 20.3891304 40.426087 20.3891304 40.426087 16.2891304 6.09565217 16.2891304"></polygon>
|
||||||
|
<path d="M11.8434783,0.95 C12.975662,0.95 13.8934783,1.86781626 13.8934783,3 L13.8934783,6.65217391 C13.8934783,7.78435765 12.975662,8.70217391 11.8434783,8.70217391 C10.7112945,8.70217391 9.79347826,7.78435765 9.79347826,6.65217391 L9.79347826,3 C9.79347826,1.86781626 10.7112945,0.95 11.8434783,0.95 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
<path d="M34.6782609,0.95 C35.8104446,0.95 36.7282609,1.86781626 36.7282609,3 L36.7282609,6.65217391 C36.7282609,7.78435765 35.8104446,8.70217391 34.6782609,8.70217391 C33.5460771,8.70217391 32.6282609,7.78435765 32.6282609,6.65217391 L32.6282609,3 C32.6282609,1.86781626 33.5460771,0.95 34.6782609,0.95 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3 KiB |
12
Telegram/Resources/icons/settings/mini_gift_order_number.svg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>Mini / mini_gift_sorting3</title>
|
||||||
|
<g id="Mini-/-mini_gift_sorting3" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<path d="M34.4778761,13.749192 C35.6376741,13.749192 36.5778761,14.689394 36.5778761,15.849192 C36.5778761,17.00899 35.6376741,17.949192 34.4778761,17.949192 L7.57258674,17.949192 C6.41278876,17.949192 5.47258674,17.00899 5.47258674,15.849192 C5.47258674,14.689394 6.41278876,13.749192 7.57258674,13.749192 L34.4778761,13.749192 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
<path d="M30.923367,27.509546 C32.083165,27.509546 33.023367,28.4497481 33.023367,29.609546 C33.023367,30.769344 32.083165,31.709546 30.923367,31.709546 L4,31.709546 C2.84020203,31.709546 1.9,30.769344 1.9,29.609546 C1.9,28.4497481 2.84020203,27.509546 4,27.509546 L30.923367,27.509546 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
<path d="M14.7584617,4.53380965 C15.0159315,3.40295129 16.1413939,2.69492999 17.2722522,2.9523998 C18.4031106,3.20986962 19.1111319,4.33533198 18.8536621,5.46619035 L10.9056841,40.3752813 C10.6482143,41.5061396 9.52275197,42.2141609 8.39189361,41.9566911 C7.26103524,41.6992213 6.55301394,40.5737589 6.81048375,39.4429006 L14.7584617,4.53380965 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
<path d="M28.1811642,4.55038432 C28.4294801,3.41748072 29.5491797,2.70038069 30.6820833,2.94869657 C31.8149869,3.19701246 32.532087,4.31671208 32.2837711,5.44961568 L24.6322095,40.3587066 C24.3838936,41.4916102 23.264194,42.2087102 22.1312904,41.9603943 C20.9983868,41.7120785 20.2812868,40.5923788 20.5296027,39.4594752 L28.1811642,4.55038432 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
<path d="M39.7913043,28.3413043 C40.9234881,28.3413043 41.8413043,29.2591206 41.8413043,30.3913043 L41.8413043,45 C41.8413043,46.1321837 40.9234881,47.05 39.7913043,47.05 C38.6591206,47.05 37.7413043,46.1321837 37.7413043,45 L37.7413043,30.3913043 C37.7413043,29.2591206 38.6591206,28.3413043 39.7913043,28.3413043 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
<path d="M32.1330398,38.0495689 C32.9336146,38.8501437 34.2316028,38.8501437 35.0321776,38.0495689 L39.7906087,33.291 L44.5504311,38.0495689 C45.3088704,38.8080082 46.5137416,38.847926 47.3191738,38.1693225 L47.4495689,38.0495689 C48.2501437,37.2489941 48.2501437,35.9510059 47.4495689,35.1504311 L41.7573686,29.4582308 C40.6715413,28.3724035 38.9110674,28.3724035 37.8252401,29.4582308 L32.1330398,35.1504311 C31.332465,35.9510059 31.332465,37.2489941 32.1330398,38.0495689 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
10
Telegram/Resources/icons/settings/mini_gift_order_price.svg
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>Mini / mini_gift_sorting1</title>
|
||||||
|
<g id="Mini-/-mini_gift_sorting1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<path d="M23.4380165,1.95 C35.3025073,1.95 44.9260331,11.510317 44.9260331,23.3103448 C44.9260331,23.5919731 44.9205389,23.8730129 44.9095753,24.1533571 C44.8653322,25.2846761 43.9123508,26.1659251 42.7810319,26.121682 C41.6497129,26.0774389 40.7684638,25.1244576 40.812707,23.9931386 C40.8215831,23.7661706 40.8260331,23.5385471 40.8260331,23.3103448 C40.8260331,13.7807547 33.0441659,6.05 23.4380165,6.05 C13.8318671,6.05 6.05,13.7807547 6.05,23.3103448 C6.05,32.839935 13.8318671,40.5706897 23.4380165,40.5706897 C25.2657432,40.5706897 27.0529516,40.2914087 28.7555206,39.7492213 C29.8343232,39.4056738 30.9873658,40.0017158 31.3309133,41.0805185 C31.6744608,42.1593211 31.0784187,43.3123637 29.9996161,43.6559112 C27.8959061,44.3258431 25.6891219,44.6706897 23.4380165,44.6706897 C11.5735257,44.6706897 1.95,35.1103726 1.95,23.3103448 C1.95,11.510317 11.5735257,1.95 23.4380165,1.95 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
<path d="M39.7913043,28.3413043 C40.9234881,28.3413043 41.8413043,29.2591206 41.8413043,30.3913043 L41.8413043,45 C41.8413043,46.1321837 40.9234881,47.05 39.7913043,47.05 C38.6591206,47.05 37.7413043,46.1321837 37.7413043,45 L37.7413043,30.3913043 C37.7413043,29.2591206 38.6591206,28.3413043 39.7913043,28.3413043 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
<path d="M32.1330398,38.0495689 C32.9336146,38.8501437 34.2316028,38.8501437 35.0321776,38.0495689 L39.7906087,33.291 L44.5504311,38.0495689 C45.3088704,38.8080082 46.5137416,38.847926 47.3191738,38.1693225 L47.4495689,38.0495689 C48.2501437,37.2489941 48.2501437,35.9510059 47.4495689,35.1504311 L41.7573686,29.4582308 C40.6715413,28.3724035 38.9110674,28.3724035 37.8252401,29.4582308 L32.1330398,35.1504311 C31.332465,35.9510059 31.332465,37.2489941 32.1330398,38.0495689 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
<path d="M23.8366211,35.9195313 C24.4958008,35.9195313 25.281738,35.5533203 25.281738,34.6744141 L25.281738,33.5025391 C29.2221677,33.0777344 31.4831055,30.7632813 31.4831055,27.3941406 C31.4831055,24.49375 29.7399414,22.7359375 26.1217773,21.9449219 L23.1481445,21.2710938 C21.287793,20.8609375 20.3795898,20.0113281 20.3795898,18.7662109 C20.3795898,17.2720703 21.6686523,16.1880859 23.6901367,16.1880859 C25.3307617,16.1880859 26.4733398,16.7447266 27.7477539,18.165625 C28.3922852,18.8394531 28.890332,19.0884766 29.5641602,19.0884766 C30.3844727,19.0884766 31.0143555,18.5171875 31.0143555,17.6675781 C31.0143555,16.8472656 30.530957,15.9537109 29.7106445,15.1480469 C28.6266602,14.1226563 27.2006837,13.4488281 25.369629,13.2144531 L25.369629,11.9986328 C25.369629,11.134375 24.5836914,10.7681641 23.9098633,10.7681641 C23.2506836,10.7681641 22.464746,11.1197266 22.464746,11.9986328 L22.464746,13.1705078 C18.6708007,13.5220703 16.4538086,15.7925781 16.4538086,19.0445313 C16.4538086,21.8863281 18.1969727,23.7759766 21.5368164,24.5230469 L24.5104492,25.2115234 C26.678418,25.7242188 27.5719727,26.4859375 27.5719727,27.775 C27.5719727,29.4449219 26.2682617,30.4996094 23.9391602,30.4996094 C22.1959961,30.4996094 20.7458008,29.8404297 19.4274414,28.4195313 C18.6803711,27.6724609 18.2702148,27.5113281 17.7135742,27.5113281 C16.8200195,27.5113281 16.1461914,28.0826172 16.1461914,29.0640625 C16.1461914,29.9283203 16.6442383,30.821875 17.537793,31.5982422 C18.709668,32.6675781 20.3407222,33.3267578 22.376855,33.5171875 L22.376855,34.6744141 C22.376855,35.5533203 23.162793,35.9195313 23.8366211,35.9195313 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -16,7 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"cloud_lng_passport_in_ar" = "Arabic";
|
"cloud_lng_passport_in_ar" = "Arabic";
|
||||||
"cloud_lng_passport_in_az" = "Azerbaijani";
|
"cloud_lng_passport_in_az" = "Azerbaijani";
|
||||||
"cloud_lng_passport_in_bg" = "Bulgarian";
|
"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_cs" = "Czech";
|
||||||
"cloud_lng_passport_in_da" = "Danish";
|
"cloud_lng_passport_in_da" = "Danish";
|
||||||
"cloud_lng_passport_in_de" = "German";
|
"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_ar" = "Arabic";
|
||||||
"cloud_lng_translate_to_az" = "Azerbaijani";
|
"cloud_lng_translate_to_az" = "Azerbaijani";
|
||||||
"cloud_lng_translate_to_bg" = "Bulgarian";
|
"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_cs" = "Czech";
|
||||||
"cloud_lng_translate_to_da" = "Danish";
|
"cloud_lng_translate_to_da" = "Danish";
|
||||||
"cloud_lng_translate_to_de" = "German";
|
"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_uz" = "Uzbek";
|
||||||
"cloud_lng_translate_to_vi" = "Vietnamese";
|
"cloud_lng_translate_to_vi" = "Vietnamese";
|
||||||
|
|
||||||
|
"cloud_lng_language_af" = "Afrikaans";
|
||||||
|
"cloud_lng_language_am" = "Amharic";
|
||||||
"cloud_lng_language_ar" = "Arabic";
|
"cloud_lng_language_ar" = "Arabic";
|
||||||
"cloud_lng_language_az" = "Azerbaijani";
|
"cloud_lng_language_az" = "Azerbaijani";
|
||||||
|
"cloud_lng_language_be" = "Belarusian";
|
||||||
"cloud_lng_language_bg" = "Bulgarian";
|
"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_cs" = "Czech";
|
||||||
|
"cloud_lng_language_cy" = "Welsh";
|
||||||
"cloud_lng_language_da" = "Danish";
|
"cloud_lng_language_da" = "Danish";
|
||||||
"cloud_lng_language_de" = "German";
|
"cloud_lng_language_de" = "German";
|
||||||
// "cloud_lng_language_dv" = "Divehi";
|
"cloud_lng_language_dv" = "Divehi";
|
||||||
// "cloud_lng_language_dz" = "Dzongkha";
|
"cloud_lng_language_dz" = "Dzongkha";
|
||||||
"cloud_lng_language_el" = "Greek";
|
"cloud_lng_language_el" = "Greek";
|
||||||
"cloud_lng_language_en" = "English";
|
"cloud_lng_language_en" = "English";
|
||||||
|
"cloud_lng_language_eo" = "Esperanto";
|
||||||
"cloud_lng_language_es" = "Spanish";
|
"cloud_lng_language_es" = "Spanish";
|
||||||
"cloud_lng_language_et" = "Estonian";
|
"cloud_lng_language_et" = "Estonian";
|
||||||
|
"cloud_lng_language_eu" = "Basque";
|
||||||
"cloud_lng_language_fa" = "Persian";
|
"cloud_lng_language_fa" = "Persian";
|
||||||
|
"cloud_lng_language_fi" = "Finnish";
|
||||||
"cloud_lng_language_fr" = "French";
|
"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_he" = "Hebrew";
|
||||||
|
"cloud_lng_language_hi" = "Hindi";
|
||||||
|
// "cloud_lng_language_hmn" = "Hmong";
|
||||||
"cloud_lng_language_hr" = "Croatian";
|
"cloud_lng_language_hr" = "Croatian";
|
||||||
|
"cloud_lng_language_ht" = "Haitian Creole";
|
||||||
"cloud_lng_language_hu" = "Hungarian";
|
"cloud_lng_language_hu" = "Hungarian";
|
||||||
"cloud_lng_language_hy" = "Armenian";
|
"cloud_lng_language_hy" = "Armenian";
|
||||||
"cloud_lng_language_id" = "Indonesian";
|
"cloud_lng_language_id" = "Indonesian";
|
||||||
|
"cloud_lng_language_ig" = "Igbo";
|
||||||
"cloud_lng_language_is" = "Icelandic";
|
"cloud_lng_language_is" = "Icelandic";
|
||||||
"cloud_lng_language_it" = "Italian";
|
"cloud_lng_language_it" = "Italian";
|
||||||
|
"cloud_lng_language_iw" = "Hebrew (Obsolete code)";
|
||||||
"cloud_lng_language_ja" = "Japanese";
|
"cloud_lng_language_ja" = "Japanese";
|
||||||
|
"cloud_lng_language_jv" = "Javanese";
|
||||||
"cloud_lng_language_ka" = "Georgian";
|
"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_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_lo" = "Lao";
|
||||||
"cloud_lng_language_lt" = "Lithuanian";
|
"cloud_lng_language_lt" = "Lithuanian";
|
||||||
"cloud_lng_language_lv" = "Latvian";
|
"cloud_lng_language_lv" = "Latvian";
|
||||||
|
"cloud_lng_language_mg" = "Malagasy";
|
||||||
|
"cloud_lng_language_mi" = "Maori";
|
||||||
"cloud_lng_language_mk" = "Macedonian";
|
"cloud_lng_language_mk" = "Macedonian";
|
||||||
|
"cloud_lng_language_ml" = "Malayalam";
|
||||||
"cloud_lng_language_mn" = "Mongolian";
|
"cloud_lng_language_mn" = "Mongolian";
|
||||||
|
"cloud_lng_language_mr" = "Marathi";
|
||||||
"cloud_lng_language_ms" = "Malay";
|
"cloud_lng_language_ms" = "Malay";
|
||||||
|
"cloud_lng_language_mt" = "Maltese";
|
||||||
"cloud_lng_language_my" = "Burmese";
|
"cloud_lng_language_my" = "Burmese";
|
||||||
"cloud_lng_language_ne" = "Nepali";
|
"cloud_lng_language_ne" = "Nepali";
|
||||||
"cloud_lng_language_nl" = "Dutch";
|
"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_pl" = "Polish";
|
||||||
|
"cloud_lng_language_ps" = "Pashto";
|
||||||
"cloud_lng_language_pt" = "Portuguese";
|
"cloud_lng_language_pt" = "Portuguese";
|
||||||
"cloud_lng_language_ro" = "Romanian";
|
"cloud_lng_language_ro" = "Romanian";
|
||||||
"cloud_lng_language_ru" = "Russian";
|
"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_sk" = "Slovak";
|
||||||
"cloud_lng_language_sl" = "Slovenian";
|
"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_th" = "Thai";
|
||||||
"cloud_lng_language_tk" = "Turkmen";
|
"cloud_lng_language_tk" = "Turkmen";
|
||||||
|
"cloud_lng_language_tl" = "Tagalog";
|
||||||
"cloud_lng_language_tr" = "Turkish";
|
"cloud_lng_language_tr" = "Turkish";
|
||||||
|
"cloud_lng_language_tt" = "Tatar";
|
||||||
|
"cloud_lng_language_ug" = "Uyghur";
|
||||||
"cloud_lng_language_uk" = "Ukrainian";
|
"cloud_lng_language_uk" = "Ukrainian";
|
||||||
|
"cloud_lng_language_ur" = "Urdu";
|
||||||
"cloud_lng_language_uz" = "Uzbek";
|
"cloud_lng_language_uz" = "Uzbek";
|
||||||
"cloud_lng_language_vi" = "Vietnamese";
|
"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";
|
||||||
|
|
|
@ -836,6 +836,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_settings_disconnect" = "Disconnect";
|
"lng_settings_disconnect" = "Disconnect";
|
||||||
"lng_settings_connected_title" = "Connected websites";
|
"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_menu" = "Battery and Animations";
|
||||||
"lng_settings_power_title" = "Power Usage";
|
"lng_settings_power_title" = "Power Usage";
|
||||||
"lng_settings_power_subtitle" = "Power saving options";
|
"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_title" = "Gifts";
|
||||||
"lng_peer_gifts_about" = "These gifts were sent to {user} by other users.";
|
"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_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" = "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_notify_enabled" = "You will receive a message from Telegram when your channel receives a gift.";
|
||||||
"lng_peer_gifts_filter_by_value" = "Sort by Value";
|
"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_reaction_name" = "Star Reaction";
|
||||||
"lng_credits_box_history_entry_subscription" = "Monthly subscription fee";
|
"lng_credits_box_history_entry_subscription" = "Monthly subscription fee";
|
||||||
"lng_credits_box_history_entry_gift_upgrade" = "Collectible Upgrade";
|
"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_subscription_section" = "My subscriptions";
|
||||||
"lng_credits_box_subscription_title" = "Subscription";
|
"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_include_button" = "Select Chats";
|
||||||
"lng_chatbots_exclude_about" = "Select chats or entire chat categories which the bot will not have access to.";
|
"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_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_manage_messages" = "Manage Messages";
|
||||||
"lng_chatbots_read" = "Read 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_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_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#one" = "**{count}** Story Per Day";
|
||||||
"lng_feature_stories#other" = "**{count}** Stories Per Day";
|
"lng_feature_stories#other" = "**{count}** Stories Per Day";
|
||||||
"lng_feature_reactions#one" = "**{count}** Custom Reaction";
|
"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_background_group" = "Custom Group Background";
|
||||||
"lng_feature_custom_emoji_pack" = "Custom Emoji Pack";
|
"lng_feature_custom_emoji_pack" = "Custom Emoji Pack";
|
||||||
"lng_feature_transcribe" = "Voice-to-Text Conversion";
|
"lng_feature_transcribe" = "Voice-to-Text Conversion";
|
||||||
|
"lng_feature_autotranslate" = "Autotranslation of Messages";
|
||||||
|
|
||||||
"lng_giveaway_new_title" = "Boosts via Gifts";
|
"lng_giveaway_new_title" = "Boosts via Gifts";
|
||||||
"lng_giveaway_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers.";
|
"lng_giveaway_new_about" = "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_link" = "What are Stars >";
|
||||||
"lng_gift_stars_limited" = "limited";
|
"lng_gift_stars_limited" = "limited";
|
||||||
"lng_gift_stars_sold_out" = "sold out";
|
"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_all" = "All Gifts";
|
||||||
"lng_gift_stars_tabs_my" = "My Gifts";
|
"lng_gift_stars_tabs_my" = "My Gifts";
|
||||||
"lng_gift_stars_tabs_limited" = "Limited";
|
"lng_gift_stars_tabs_limited" = "Limited";
|
||||||
"lng_gift_stars_tabs_in_stock" = "In Stock";
|
"lng_gift_stars_tabs_in_stock" = "In Stock";
|
||||||
|
"lng_gift_stars_tabs_resale" = "Resale";
|
||||||
"lng_gift_send_title" = "Send a Gift";
|
"lng_gift_send_title" = "Send a Gift";
|
||||||
"lng_gift_send_message" = "Enter Message";
|
"lng_gift_send_message" = "Enter Message";
|
||||||
"lng_gift_send_anonymous" = "Hide My Name";
|
"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_limited_left#other" = "{count} left";
|
||||||
"lng_gift_send_button" = "Send a Gift for {cost}";
|
"lng_gift_send_button" = "Send a Gift for {cost}";
|
||||||
"lng_gift_send_button_self" = "Buy 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_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#one" = "You spent **{count}** Star from your balance.";
|
||||||
"lng_gift_sent_about#other" = "You spent **{count}** Stars from your balance.";
|
"lng_gift_sent_about#other" = "You spent **{count}** Stars from your balance.";
|
||||||
"lng_gift_limited_of_one" = "unique";
|
"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_button_for" = "Transfer for {price}";
|
||||||
"lng_gift_transfer_wear" = "Wear";
|
"lng_gift_transfer_wear" = "Wear";
|
||||||
"lng_gift_transfer_take_off" = "Take Off";
|
"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_show" = "Show";
|
||||||
"lng_gift_menu_hide" = "Hide";
|
"lng_gift_menu_hide" = "Hide";
|
||||||
"lng_gift_wear_title" = "Wear {name}";
|
"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_wear_end_toast" = "You took off {name}";
|
||||||
"lng_gift_many_pinned_title" = "Too Many Pinned Gifts";
|
"lng_gift_many_pinned_title" = "Too Many Pinned Gifts";
|
||||||
"lng_gift_many_pinned_choose" = "Select a gift to unpin below";
|
"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_limit_title" = "Limit Reached";
|
||||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
|
"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_sensitive_toast" = "You can update the visibility of sensitive media in **Settings > Chat Settings > Sensitive content**";
|
||||||
|
|
||||||
"lng_translate_show_original" = "Show Original";
|
"lng_translate_show_original" = "Show Original";
|
||||||
|
"lng_translate_return_original" = "View Original ({language})";
|
||||||
"lng_translate_bar_to" = "Translate to {name}";
|
"lng_translate_bar_to" = "Translate to {name}";
|
||||||
"lng_translate_bar_to_other" = "Translate to {name}";
|
"lng_translate_bar_to_other" = "Translate to {name}";
|
||||||
"lng_translate_menu_to" = "Translate To";
|
"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_contact_title" = "Edit contact";
|
||||||
"lng_edit_channel_title" = "Edit channel";
|
"lng_edit_channel_title" = "Edit channel";
|
||||||
"lng_edit_bot_title" = "Edit bot";
|
"lng_edit_bot_title" = "Edit bot";
|
||||||
|
"lng_edit_autotranslate" = "Auto-translate messages";
|
||||||
"lng_edit_sign_messages" = "Sign messages";
|
"lng_edit_sign_messages" = "Sign messages";
|
||||||
"lng_edit_sign_messages_about" = "Add names of admins to the messages they post.";
|
"lng_edit_sign_messages_about" = "Add names of admins to the messages they post.";
|
||||||
"lng_edit_sign_profiles" = "Show authors' profiles";
|
"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_all_actions" = "All actions";
|
||||||
"lng_admin_log_filter_actions_type_subtitle" = "Filter actions by type";
|
"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_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_restrictions" = "New restrictions";
|
||||||
"lng_admin_log_filter_admins_new" = "Admin rights";
|
"lng_admin_log_filter_admins_new" = "Admin rights";
|
||||||
"lng_admin_log_filter_members_new" = "New members";
|
"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_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_group" = "Group info";
|
||||||
"lng_admin_log_filter_info_channel" = "Channel info";
|
"lng_admin_log_filter_info_channel" = "Channel info";
|
||||||
"lng_admin_log_filter_actions_messages_section" = "Messages";
|
"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_voice_chats_channel" = "Live stream";
|
||||||
"lng_admin_log_filter_invite_links" = "Invite links";
|
"lng_admin_log_filter_invite_links" = "Invite links";
|
||||||
"lng_admin_log_filter_members_removed" = "Members leaving";
|
"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_topics" = "Topics";
|
||||||
"lng_admin_log_filter_sub_extend" = "Subscription Renewals";
|
"lng_admin_log_filter_sub_extend" = "Subscription Renewals";
|
||||||
"lng_admin_log_filter_all_admins" = "All users and admins";
|
"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_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_enabled" = "{from} enabled aggressive anti-spam";
|
||||||
"lng_admin_log_antispam_disabled" = "{from} disabled 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_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_set_background_emoji" = "{from} set channel background emoji to {emoji}";
|
||||||
"lng_admin_log_change_background_emoji" = "{from} changed channel background emoji from {previous} to {emoji}";
|
"lng_admin_log_change_background_emoji" = "{from} changed channel background emoji from {previous} to {emoji}";
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="/animations">
|
<qresource prefix="/animations">
|
||||||
<file alias="blocked_peers_empty.tgs">../../animations/blocked_peers_empty.tgs</file>
|
<file alias="blocked_peers_empty.tgs">../../animations/blocked_peers_empty.tgs</file>
|
||||||
|
<file alias="change_number.tgs">../../animations/change_number.tgs</file>
|
||||||
<file alias="filters.tgs">../../animations/filters.tgs</file>
|
<file alias="filters.tgs">../../animations/filters.tgs</file>
|
||||||
<file alias="cloud_filters.tgs">../../animations/cloud_filters.tgs</file>
|
<file alias="cloud_filters.tgs">../../animations/cloud_filters.tgs</file>
|
||||||
<file alias="local_passcode_enter.tgs">../../animations/local_passcode_enter.tgs</file>
|
<file alias="local_passcode_enter.tgs">../../animations/local_passcode_enter.tgs</file>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||||
ProcessorArchitecture="ARCHITECTURE"
|
ProcessorArchitecture="ARCHITECTURE"
|
||||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||||
Version="5.14.1.0" />
|
Version="5.14.3.0" />
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Telegram Desktop</DisplayName>
|
<DisplayName>Telegram Desktop</DisplayName>
|
||||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||||
|
|
|
@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 5,14,1,0
|
FILEVERSION 5,14,3,0
|
||||||
PRODUCTVERSION 5,14,1,0
|
PRODUCTVERSION 5,14,3,0
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -62,10 +62,10 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Radolyn Labs"
|
VALUE "CompanyName", "Radolyn Labs"
|
||||||
VALUE "FileDescription", "AyuGram Desktop"
|
VALUE "FileDescription", "AyuGram Desktop"
|
||||||
VALUE "FileVersion", "5.14.1.0"
|
VALUE "FileVersion", "5.14.3.0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||||
VALUE "ProductName", "AyuGram Desktop"
|
VALUE "ProductName", "AyuGram Desktop"
|
||||||
VALUE "ProductVersion", "5.14.1.0"
|
VALUE "ProductVersion", "5.14.3.0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
|
@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 5,14,1,0
|
FILEVERSION 5,14,3,0
|
||||||
PRODUCTVERSION 5,14,1,0
|
PRODUCTVERSION 5,14,3,0
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -53,10 +53,10 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Radolyn Labs"
|
VALUE "CompanyName", "Radolyn Labs"
|
||||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||||
VALUE "FileVersion", "5.14.1.0"
|
VALUE "FileVersion", "5.14.3.0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||||
VALUE "ProductName", "AyuGram Desktop"
|
VALUE "ProductName", "AyuGram Desktop"
|
||||||
VALUE "ProductVersion", "5.14.1.0"
|
VALUE "ProductVersion", "5.14.3.0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
|
@ -157,6 +157,7 @@ constexpr auto kTransactionsLimit = 100;
|
||||||
.converted = stargift && incoming,
|
.converted = stargift && incoming,
|
||||||
.stargift = stargift.has_value(),
|
.stargift = stargift.has_value(),
|
||||||
.giftUpgraded = tl.data().is_stargift_upgrade(),
|
.giftUpgraded = tl.data().is_stargift_upgrade(),
|
||||||
|
.giftResale = tl.data().is_stargift_resale(),
|
||||||
.reaction = tl.data().is_reaction(),
|
.reaction = tl.data().is_reaction(),
|
||||||
.refunded = tl.data().is_refund(),
|
.refunded = tl.data().is_refund(),
|
||||||
.pending = tl.data().is_pending(),
|
.pending = tl.data().is_pending(),
|
||||||
|
@ -528,8 +529,12 @@ void EditCreditsSubscription(
|
||||||
)).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send();
|
)).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
MTPInputSavedStarGift InputSavedStarGiftId(const Data::SavedStarGiftId &id) {
|
MTPInputSavedStarGift InputSavedStarGiftId(
|
||||||
return id.isUser()
|
const Data::SavedStarGiftId &id,
|
||||||
|
const std::shared_ptr<Data::UniqueGift> &unique) {
|
||||||
|
return (!id && unique)
|
||||||
|
? MTP_inputSavedStarGiftSlug(MTP_string(unique->slug))
|
||||||
|
: id.isUser()
|
||||||
? MTP_inputSavedStarGiftUser(MTP_int(id.userMessageId().bare))
|
? MTP_inputSavedStarGiftUser(MTP_int(id.userMessageId().bare))
|
||||||
: MTP_inputSavedStarGiftChat(
|
: MTP_inputSavedStarGiftChat(
|
||||||
id.chat()->input,
|
id.chat()->input,
|
||||||
|
|
|
@ -122,6 +122,7 @@ void EditCreditsSubscription(
|
||||||
Fn<void(QString)> fail);
|
Fn<void(QString)> fail);
|
||||||
|
|
||||||
[[nodiscard]] MTPInputSavedStarGift InputSavedStarGiftId(
|
[[nodiscard]] MTPInputSavedStarGift InputSavedStarGiftId(
|
||||||
const Data::SavedStarGiftId &id);
|
const Data::SavedStarGiftId &id,
|
||||||
|
const std::shared_ptr<Data::UniqueGift> &unique = nullptr);
|
||||||
|
|
||||||
} // namespace Api
|
} // namespace Api
|
||||||
|
|
|
@ -46,11 +46,15 @@ void HandleWithdrawalButton(
|
||||||
bool loading = false;
|
bool loading = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto channel = receiver.currencyReceiver;
|
const auto currencyReceiver = receiver.currencyReceiver;
|
||||||
const auto peer = receiver.creditsReceiver;
|
const auto creditsReceiver = receiver.creditsReceiver;
|
||||||
|
const auto isChannel = receiver.currencyReceiver
|
||||||
|
&& receiver.currencyReceiver->isChannel();
|
||||||
|
|
||||||
const auto state = button->lifetime().make_state<State>();
|
const auto state = button->lifetime().make_state<State>();
|
||||||
const auto session = (channel ? &channel->session() : &peer->session());
|
const auto session = (currencyReceiver
|
||||||
|
? ¤cyReceiver->session()
|
||||||
|
: &creditsReceiver->session());
|
||||||
|
|
||||||
using ChannelOutUrl = MTPstats_BroadcastRevenueWithdrawalUrl;
|
using ChannelOutUrl = MTPstats_BroadcastRevenueWithdrawalUrl;
|
||||||
using CreditsOutUrl = MTPpayments_StarsRevenueWithdrawalUrl;
|
using CreditsOutUrl = MTPpayments_StarsRevenueWithdrawalUrl;
|
||||||
|
@ -59,7 +63,7 @@ void HandleWithdrawalButton(
|
||||||
const auto processOut = [=] {
|
const auto processOut = [=] {
|
||||||
if (state->loading) {
|
if (state->loading) {
|
||||||
return;
|
return;
|
||||||
} else if (peer && !receiver.creditsAmount()) {
|
} else if (creditsReceiver && !receiver.creditsAmount()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state->loading = true;
|
state->loading = true;
|
||||||
|
@ -70,10 +74,10 @@ void HandleWithdrawalButton(
|
||||||
state->loading = false;
|
state->loading = false;
|
||||||
|
|
||||||
auto fields = PasscodeBox::CloudFields::From(pass);
|
auto fields = PasscodeBox::CloudFields::From(pass);
|
||||||
fields.customTitle = channel
|
fields.customTitle = isChannel
|
||||||
? tr::lng_channel_earn_balance_password_title()
|
? tr::lng_channel_earn_balance_password_title()
|
||||||
: tr::lng_bot_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_channel_earn_balance_password_description(tr::now)
|
||||||
: tr::lng_bot_earn_balance_password_description(tr::now);
|
: tr::lng_bot_earn_balance_password_description(tr::now);
|
||||||
fields.customSubmitButton = tr::lng_passcode_submit();
|
fields.customSubmitButton = tr::lng_passcode_submit();
|
||||||
|
@ -94,18 +98,18 @@ void HandleWithdrawalButton(
|
||||||
show->showToast(message);
|
show->showToast(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (channel) {
|
if (currencyReceiver) {
|
||||||
session->api().request(
|
session->api().request(
|
||||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||||
channel->input,
|
currencyReceiver->input,
|
||||||
result.result
|
result.result
|
||||||
)).done([=](const ChannelOutUrl &r) {
|
)).done([=](const ChannelOutUrl &r) {
|
||||||
done(qs(r.data().vurl()));
|
done(qs(r.data().vurl()));
|
||||||
}).fail(fail).send();
|
}).fail(fail).send();
|
||||||
} else if (peer) {
|
} else if (creditsReceiver) {
|
||||||
session->api().request(
|
session->api().request(
|
||||||
MTPpayments_GetStarsRevenueWithdrawalUrl(
|
MTPpayments_GetStarsRevenueWithdrawalUrl(
|
||||||
peer->input,
|
creditsReceiver->input,
|
||||||
MTP_long(receiver.creditsAmount()),
|
MTP_long(receiver.creditsAmount()),
|
||||||
result.result
|
result.result
|
||||||
)).done([=](const CreditsOutUrl &r) {
|
)).done([=](const CreditsOutUrl &r) {
|
||||||
|
@ -134,17 +138,17 @@ void HandleWithdrawalButton(
|
||||||
processOut();
|
processOut();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (channel) {
|
if (currencyReceiver) {
|
||||||
session->api().request(
|
session->api().request(
|
||||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||||
channel->input,
|
currencyReceiver->input,
|
||||||
MTP_inputCheckPasswordEmpty()
|
MTP_inputCheckPasswordEmpty()
|
||||||
)).fail(fail).send();
|
)).fail(fail).send();
|
||||||
} else if (peer) {
|
} else if (creditsReceiver) {
|
||||||
session->api().request(
|
session->api().request(
|
||||||
MTPpayments_GetStarsRevenueWithdrawalUrl(
|
MTPpayments_GetStarsRevenueWithdrawalUrl(
|
||||||
peer->input,
|
creditsReceiver->input,
|
||||||
MTP_long(std::numeric_limits<int64_t>::max()),
|
MTP_long(receiver.creditsAmount()),
|
||||||
MTP_inputCheckPasswordEmpty()
|
MTP_inputCheckPasswordEmpty()
|
||||||
)).fail(fail).send();
|
)).fail(fail).send();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ void RestrictSponsored(
|
||||||
Fn<void(QString)> failed);
|
Fn<void(QString)> failed);
|
||||||
|
|
||||||
struct RewardReceiver final {
|
struct RewardReceiver final {
|
||||||
ChannelData *currencyReceiver = nullptr;
|
PeerData *currencyReceiver = nullptr;
|
||||||
PeerData *creditsReceiver = nullptr;
|
PeerData *creditsReceiver = nullptr;
|
||||||
Fn<uint64()> creditsAmount;
|
Fn<uint64()> creditsAmount;
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_global_privacy.h"
|
#include "api/api_global_privacy.h"
|
||||||
|
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "data/components/promo_suggestions.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "main/main_app_config.h"
|
#include "main/main_app_config.h"
|
||||||
|
@ -101,13 +102,11 @@ rpl::producer<bool> GlobalPrivacy::showArchiveAndMute() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<> GlobalPrivacy::suggestArchiveAndMute() const {
|
rpl::producer<> GlobalPrivacy::suggestArchiveAndMute() const {
|
||||||
return _session->appConfig().suggestionRequested(
|
return _session->promoSuggestions().requested(u"AUTOARCHIVE_POPULAR"_q);
|
||||||
u"AUTOARCHIVE_POPULAR"_q);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalPrivacy::dismissArchiveAndMuteSuggestion() {
|
void GlobalPrivacy::dismissArchiveAndMuteSuggestion() {
|
||||||
_session->appConfig().dismissSuggestion(
|
_session->promoSuggestions().dismiss(u"AUTOARCHIVE_POPULAR"_q);
|
||||||
u"AUTOARCHIVE_POPULAR"_q);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalPrivacy::updateHideReadTime(bool hide) {
|
void GlobalPrivacy::updateHideReadTime(bool hide) {
|
||||||
|
|
|
@ -799,6 +799,7 @@ std::optional<Data::StarGift> FromTL(
|
||||||
return gift.match([&](const MTPDstarGift &data) {
|
return gift.match([&](const MTPDstarGift &data) {
|
||||||
const auto document = session->data().processDocument(
|
const auto document = session->data().processDocument(
|
||||||
data.vsticker());
|
data.vsticker());
|
||||||
|
const auto resellPrice = data.vresell_min_stars().value_or_empty();
|
||||||
const auto remaining = data.vavailability_remains();
|
const auto remaining = data.vavailability_remains();
|
||||||
const auto total = data.vavailability_total();
|
const auto total = data.vavailability_total();
|
||||||
if (!document->sticker()) {
|
if (!document->sticker()) {
|
||||||
|
@ -809,7 +810,10 @@ std::optional<Data::StarGift> FromTL(
|
||||||
.stars = int64(data.vstars().v),
|
.stars = int64(data.vstars().v),
|
||||||
.starsConverted = int64(data.vconvert_stars().v),
|
.starsConverted = int64(data.vconvert_stars().v),
|
||||||
.starsToUpgrade = int64(data.vupgrade_stars().value_or_empty()),
|
.starsToUpgrade = int64(data.vupgrade_stars().value_or_empty()),
|
||||||
|
.starsResellMin = int64(resellPrice),
|
||||||
.document = document,
|
.document = document,
|
||||||
|
.resellTitle = qs(data.vtitle().value_or_empty()),
|
||||||
|
.resellCount = int(data.vavailability_resale().value_or_empty()),
|
||||||
.limitedLeft = remaining.value_or_empty(),
|
.limitedLeft = remaining.value_or_empty(),
|
||||||
.limitedCount = total.value_or_empty(),
|
.limitedCount = total.value_or_empty(),
|
||||||
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
|
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
|
||||||
|
@ -849,6 +853,7 @@ std::optional<Data::StarGift> FromTL(
|
||||||
? peerFromMTP(*data.vowner_id())
|
? peerFromMTP(*data.vowner_id())
|
||||||
: PeerId()),
|
: PeerId()),
|
||||||
.number = data.vnum().v,
|
.number = data.vnum().v,
|
||||||
|
.starsForResale = int(data.vresell_stars().value_or_empty()),
|
||||||
.model = *model,
|
.model = *model,
|
||||||
.pattern = *pattern,
|
.pattern = *pattern,
|
||||||
}),
|
}),
|
||||||
|
@ -881,6 +886,8 @@ std::optional<Data::SavedStarGift> FromTL(
|
||||||
} else if (const auto unique = parsed->unique.get()) {
|
} else if (const auto unique = parsed->unique.get()) {
|
||||||
unique->starsForTransfer = data.vtransfer_stars().value_or(-1);
|
unique->starsForTransfer = data.vtransfer_stars().value_or(-1);
|
||||||
unique->exportAt = data.vcan_export_at().value_or_empty();
|
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;
|
using Id = Data::SavedStarGiftId;
|
||||||
const auto hasUnique = parsed->unique != nullptr;
|
const auto hasUnique = parsed->unique != nullptr;
|
||||||
|
@ -936,7 +943,7 @@ Data::UniqueGiftPattern FromTL(
|
||||||
}
|
}
|
||||||
|
|
||||||
Data::UniqueGiftBackdrop FromTL(const MTPDstarGiftAttributeBackdrop &data) {
|
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.name = qs(data.vname());
|
||||||
result.rarityPermille = data.vrarity_permille().v;
|
result.rarityPermille = data.vrarity_permille().v;
|
||||||
result.centerColor = Ui::ColorFromSerialized(
|
result.centerColor = Ui::ColorFromSerialized(
|
||||||
|
|
|
@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "mtproto/mtproto_dc_options.h"
|
#include "mtproto/mtproto_dc_options.h"
|
||||||
#include "data/business/data_shortcut_messages.h"
|
#include "data/business/data_shortcut_messages.h"
|
||||||
#include "data/components/credits.h"
|
#include "data/components/credits.h"
|
||||||
|
#include "data/components/promo_suggestions.h"
|
||||||
#include "data/components/scheduled_messages.h"
|
#include "data/components/scheduled_messages.h"
|
||||||
#include "data/components/top_peers.h"
|
#include "data/components/top_peers.h"
|
||||||
#include "data/notify/data_notify_settings.h"
|
#include "data/notify/data_notify_settings.h"
|
||||||
|
@ -2076,6 +2077,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||||
|
|
||||||
case mtpc_updateConfig: {
|
case mtpc_updateConfig: {
|
||||||
session().mtp().requestConfig();
|
session().mtp().requestConfig();
|
||||||
|
session().promoSuggestions().invalidate();
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case mtpc_updateUserPhone: {
|
case mtpc_updateUserPhone: {
|
||||||
|
|
|
@ -97,8 +97,6 @@ namespace {
|
||||||
// Save draft to the cloud with 1 sec extra delay.
|
// Save draft to the cloud with 1 sec extra delay.
|
||||||
constexpr auto kSaveCloudDraftTimeout = 1000;
|
constexpr auto kSaveCloudDraftTimeout = 1000;
|
||||||
|
|
||||||
constexpr auto kTopPromotionInterval = TimeId(60 * 60);
|
|
||||||
constexpr auto kTopPromotionMinDelay = TimeId(10);
|
|
||||||
constexpr auto kSmallDelayMs = 5;
|
constexpr auto kSmallDelayMs = 5;
|
||||||
constexpr auto kReadFeaturedSetsTimeout = crl::time(1000);
|
constexpr auto kReadFeaturedSetsTimeout = crl::time(1000);
|
||||||
constexpr auto kFileLoaderQueueStopTimeout = crl::time(5000);
|
constexpr auto kFileLoaderQueueStopTimeout = crl::time(5000);
|
||||||
|
@ -169,7 +167,6 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
||||||
, _featuredSetsReadTimer([=] { readFeaturedSets(); })
|
, _featuredSetsReadTimer([=] { readFeaturedSets(); })
|
||||||
, _dialogsLoadState(std::make_unique<DialogsLoadState>())
|
, _dialogsLoadState(std::make_unique<DialogsLoadState>())
|
||||||
, _fileLoader(std::make_unique<TaskQueue>(kFileLoaderQueueStopTimeout))
|
, _fileLoader(std::make_unique<TaskQueue>(kFileLoaderQueueStopTimeout))
|
||||||
, _topPromotionTimer([=] { refreshTopPromotion(); })
|
|
||||||
, _updateNotifyTimer([=] { sendNotifySettingsUpdates(); })
|
, _updateNotifyTimer([=] { sendNotifySettingsUpdates(); })
|
||||||
, _statsSessionKillTimer([=] { checkStatsSessions(); })
|
, _statsSessionKillTimer([=] { checkStatsSessions(); })
|
||||||
, _authorizations(std::make_unique<Api::Authorizations>(this))
|
, _authorizations(std::make_unique<Api::Authorizations>(this))
|
||||||
|
@ -205,11 +202,6 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
||||||
}, _session->lifetime());
|
}, _session->lifetime());
|
||||||
|
|
||||||
setupSupportMode();
|
setupSupportMode();
|
||||||
|
|
||||||
Core::App().settings().proxy().connectionTypeValue(
|
|
||||||
) | rpl::start_with_next([=] {
|
|
||||||
refreshTopPromotion();
|
|
||||||
}, _session->lifetime());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,79 +241,6 @@ void ApiWrap::requestChangelog(
|
||||||
//).send();
|
//).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<QString, uint32> {
|
|
||||||
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(
|
void ApiWrap::requestDeepLinkInfo(
|
||||||
const QString &path,
|
const QString &path,
|
||||||
Fn<void(TextWithEntities message, bool updateRequired)> callback) {
|
Fn<void(TextWithEntities message, bool updateRequired)> callback) {
|
||||||
|
|
|
@ -200,7 +200,6 @@ public:
|
||||||
void requestChangelog(
|
void requestChangelog(
|
||||||
const QString &sinceVersion,
|
const QString &sinceVersion,
|
||||||
Fn<void(const MTPUpdates &result)> callback);
|
Fn<void(const MTPUpdates &result)> callback);
|
||||||
void refreshTopPromotion();
|
|
||||||
void requestDeepLinkInfo(
|
void requestDeepLinkInfo(
|
||||||
const QString &path,
|
const QString &path,
|
||||||
Fn<void(TextWithEntities message, bool updateRequired)> callback);
|
Fn<void(TextWithEntities message, bool updateRequired)> callback);
|
||||||
|
@ -569,9 +568,6 @@ private:
|
||||||
not_null<SendingAlbum*> album,
|
not_null<SendingAlbum*> album,
|
||||||
Fn<void(bool)> done = nullptr);
|
Fn<void(bool)> done = nullptr);
|
||||||
|
|
||||||
void getTopPromotionDelayed(TimeId now, TimeId next);
|
|
||||||
void topPromotionDone(const MTPhelp_PromoData &proxy);
|
|
||||||
|
|
||||||
void sendNotifySettingsUpdates();
|
void sendNotifySettingsUpdates();
|
||||||
|
|
||||||
template <typename Request>
|
template <typename Request>
|
||||||
|
@ -709,11 +705,6 @@ private:
|
||||||
std::unique_ptr<TaskQueue> _fileLoader;
|
std::unique_ptr<TaskQueue> _fileLoader;
|
||||||
base::flat_map<uint64, std::shared_ptr<SendingAlbum>> _sendingAlbums;
|
base::flat_map<uint64, std::shared_ptr<SendingAlbum>> _sendingAlbums;
|
||||||
|
|
||||||
mtpRequestId _topPromotionRequestId = 0;
|
|
||||||
std::pair<QString, uint32> _topPromotionKey;
|
|
||||||
TimeId _topPromotionNextRequestTime = TimeId(0);
|
|
||||||
base::Timer _topPromotionTimer;
|
|
||||||
|
|
||||||
base::flat_set<not_null<const Data::ForumTopic*>> _updateNotifyTopics;
|
base::flat_set<not_null<const Data::ForumTopic*>> _updateNotifyTopics;
|
||||||
base::flat_set<not_null<const PeerData*>> _updateNotifyPeers;
|
base::flat_set<not_null<const PeerData*>> _updateNotifyPeers;
|
||||||
base::flat_set<Data::DefaultNotify> _updateNotifyDefaults;
|
base::flat_set<Data::DefaultNotify> _updateNotifyDefaults;
|
||||||
|
|
|
@ -316,16 +316,23 @@ void FillChooseFilterMenu(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto session = &strong->session();
|
const auto session = &strong->session();
|
||||||
const auto count = session->data().chatsFilters().list().size();
|
const auto &list = session->data().chatsFilters().list();
|
||||||
if ((count - 1) >= limit()) {
|
if ((list.size() - 1) >= limit()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const auto chooseNextId = [&] {
|
||||||
|
auto id = 2;
|
||||||
|
while (ranges::contains(list, id, &Data::ChatFilter::id)) {
|
||||||
|
++id;
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
};
|
||||||
auto filter =
|
auto filter =
|
||||||
Data::ChatFilter({}, {}, {}, {}, {}, { history }, {}, {});
|
Data::ChatFilter({}, {}, {}, {}, {}, { history }, {}, {});
|
||||||
const auto send = [=](const Data::ChatFilter &filter) {
|
const auto send = [=](const Data::ChatFilter &filter) {
|
||||||
session->api().request(MTPmessages_UpdateDialogFilter(
|
session->api().request(MTPmessages_UpdateDialogFilter(
|
||||||
MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter),
|
MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter),
|
||||||
MTP_int(count),
|
MTP_int(chooseNextId()),
|
||||||
filter.tl()
|
filter.tl()
|
||||||
)).done([=] {
|
)).done([=] {
|
||||||
session->data().chatsFilters().reload();
|
session->data().chatsFilters().reload();
|
||||||
|
|
|
@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "boxes/peers/replace_boost_box.h" // BoostsForGift.
|
#include "boxes/peers/replace_boost_box.h" // BoostsForGift.
|
||||||
#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
|
#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
|
||||||
#include "boxes/star_gift_box.h" // ShowStarGiftBox.
|
#include "boxes/star_gift_box.h" // ShowStarGiftBox.
|
||||||
#include "boxes/transfer_gift_box.h" // ShowTransferGiftBox.
|
|
||||||
#include "core/ui_integration.h"
|
#include "core/ui_integration.h"
|
||||||
#include "data/data_boosts.h"
|
#include "data/data_boosts.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
|
@ -1336,7 +1335,13 @@ void AddStarGiftTable(
|
||||||
}, tooltip->lifetime());
|
}, 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 ownerId = PeerId(entry.bareGiftOwnerId);
|
||||||
const auto was = std::make_shared<std::optional<CollectibleId>>();
|
const auto was = std::make_shared<std::optional<CollectibleId>>();
|
||||||
const auto handleChange = [=](
|
const auto handleChange = [=](
|
||||||
|
@ -1621,7 +1626,17 @@ void AddCreditsHistoryEntryTable(
|
||||||
const auto starrefRecipientId = PeerId(entry.starrefRecipientId);
|
const auto starrefRecipientId = PeerId(entry.starrefRecipientId);
|
||||||
const auto session = &show->session();
|
const auto session = &show->session();
|
||||||
if (entry.starrefCommission) {
|
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(
|
AddTableRow(
|
||||||
table,
|
table,
|
||||||
tr::lng_star_ref_commission_title(),
|
tr::lng_star_ref_commission_title(),
|
||||||
|
@ -1635,7 +1650,7 @@ void AddCreditsHistoryEntryTable(
|
||||||
Ui::Text::WithEntities));
|
Ui::Text::WithEntities));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (starrefRecipientId && entry.starrefAmount) {
|
if (starrefRecipientId && entry.starrefAmount && !entry.giftResale) {
|
||||||
AddTableRow(
|
AddTableRow(
|
||||||
table,
|
table,
|
||||||
tr::lng_credits_box_history_entry_affiliate(),
|
tr::lng_credits_box_history_entry_affiliate(),
|
||||||
|
@ -1645,7 +1660,9 @@ void AddCreditsHistoryEntryTable(
|
||||||
if (peerId && entry.starrefCommission) {
|
if (peerId && entry.starrefCommission) {
|
||||||
AddTableRow(
|
AddTableRow(
|
||||||
table,
|
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_referred
|
||||||
: tr::lng_credits_box_history_entry_miniapp)(),
|
: tr::lng_credits_box_history_entry_miniapp)(),
|
||||||
show,
|
show,
|
||||||
|
@ -1656,6 +1673,8 @@ void AddCreditsHistoryEntryTable(
|
||||||
? tr::lng_credits_box_history_entry_referred()
|
? tr::lng_credits_box_history_entry_referred()
|
||||||
: entry.in
|
: entry.in
|
||||||
? tr::lng_credits_box_history_entry_peer_in()
|
? tr::lng_credits_box_history_entry_peer_in()
|
||||||
|
: entry.giftResale
|
||||||
|
? tr::lng_credits_box_history_entry_gift_bought_from()
|
||||||
: entry.giftUpgraded
|
: entry.giftUpgraded
|
||||||
? tr::lng_credits_box_history_entry_gift_from()
|
? tr::lng_credits_box_history_entry_gift_from()
|
||||||
: tr::lng_credits_box_history_entry_peer();
|
: tr::lng_credits_box_history_entry_peer();
|
||||||
|
|
|
@ -77,7 +77,7 @@ ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) {
|
||||||
if (author == peer) {
|
if (author == peer) {
|
||||||
return {};
|
return {};
|
||||||
} else if (const auto channel = author->asChannel()) {
|
} else if (const auto channel = author->asChannel()) {
|
||||||
if (channel->linkedChat() == peer) {
|
if (channel->discussionLink() == peer) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ the official desktop application for the Telegram messaging service.
|
||||||
For license and copyright information please follow this link:
|
For license and copyright information please follow this link:
|
||||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
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 "lang/lang_keys.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
|
@ -224,7 +224,7 @@ void Controller::choose(not_null<ChatData*> chat) {
|
||||||
return tr::lng_manage_discussion_group_about(Ui::Text::WithEntities);
|
return tr::lng_manage_discussion_group_about(Ui::Text::WithEntities);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
[[nodiscard]] object_ptr<Ui::BoxContent> EditDiscussionLinkBox(
|
||||||
not_null<Window::SessionNavigation*> navigation,
|
not_null<Window::SessionNavigation*> navigation,
|
||||||
not_null<ChannelData*> channel,
|
not_null<ChannelData*> channel,
|
||||||
ChannelData *chat,
|
ChannelData *chat,
|
||||||
|
@ -272,7 +272,7 @@ void Controller::choose(not_null<ChatData*> chat) {
|
||||||
Settings::AddButtonWithIcon(
|
Settings::AddButtonWithIcon(
|
||||||
above,
|
above,
|
||||||
tr::lng_manage_discussion_group_create(),
|
tr::lng_manage_discussion_group_create(),
|
||||||
st::infoCreateLinkedChatButton,
|
st::infoCreateDiscussionLinkButton,
|
||||||
{ &st::menuIconGroupCreate }
|
{ &st::menuIconGroupCreate }
|
||||||
)->addClickHandler([=, parent = above.data()] {
|
)->addClickHandler([=, parent = above.data()] {
|
||||||
const auto guarded = crl::guard(parent, callback);
|
const auto guarded = crl::guard(parent, callback);
|
||||||
|
@ -292,7 +292,7 @@ void Controller::choose(not_null<ChatData*> chat) {
|
||||||
(channel->isBroadcast()
|
(channel->isBroadcast()
|
||||||
? tr::lng_manage_discussion_group_unlink
|
? tr::lng_manage_discussion_group_unlink
|
||||||
: tr::lng_manage_linked_channel_unlink)(),
|
: tr::lng_manage_linked_channel_unlink)(),
|
||||||
st::infoUnlinkChatButton,
|
st::infoUnlinkDiscussionLinkButton,
|
||||||
{ &st::menuIconRemove }
|
{ &st::menuIconRemove }
|
||||||
)->addClickHandler([=] { callback(nullptr); });
|
)->addClickHandler([=] { callback(nullptr); });
|
||||||
Ui::AddSkip(below);
|
Ui::AddSkip(below);
|
||||||
|
@ -327,12 +327,12 @@ void Controller::choose(not_null<ChatData*> chat) {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
object_ptr<Ui::BoxContent> EditDiscussionLinkBox(
|
||||||
not_null<Window::SessionNavigation*> navigation,
|
not_null<Window::SessionNavigation*> navigation,
|
||||||
not_null<ChannelData*> channel,
|
not_null<ChannelData*> channel,
|
||||||
std::vector<not_null<PeerData*>> &&chats,
|
std::vector<not_null<PeerData*>> &&chats,
|
||||||
Fn<void(ChannelData*)> callback) {
|
Fn<void(ChannelData*)> callback) {
|
||||||
return EditLinkedChatBox(
|
return EditDiscussionLinkBox(
|
||||||
navigation,
|
navigation,
|
||||||
channel,
|
channel,
|
||||||
nullptr,
|
nullptr,
|
||||||
|
@ -341,13 +341,13 @@ object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||||
callback);
|
callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
object_ptr<Ui::BoxContent> EditDiscussionLinkBox(
|
||||||
not_null<Window::SessionNavigation*> navigation,
|
not_null<Window::SessionNavigation*> navigation,
|
||||||
not_null<ChannelData*> channel,
|
not_null<ChannelData*> channel,
|
||||||
not_null<ChannelData*> chat,
|
not_null<ChannelData*> chat,
|
||||||
bool canEdit,
|
bool canEdit,
|
||||||
Fn<void(ChannelData*)> callback) {
|
Fn<void(ChannelData*)> callback) {
|
||||||
return EditLinkedChatBox(
|
return EditDiscussionLinkBox(
|
||||||
navigation,
|
navigation,
|
||||||
channel,
|
channel,
|
||||||
chat,
|
chat,
|
|
@ -17,14 +17,14 @@ namespace Window {
|
||||||
class SessionNavigation;
|
class SessionNavigation;
|
||||||
} // namespace Window
|
} // namespace Window
|
||||||
|
|
||||||
[[nodiscard]] object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
[[nodiscard]] object_ptr<Ui::BoxContent> EditDiscussionLinkBox(
|
||||||
not_null<Window::SessionNavigation*> navigation,
|
not_null<Window::SessionNavigation*> navigation,
|
||||||
not_null<ChannelData*> channel,
|
not_null<ChannelData*> channel,
|
||||||
not_null<ChannelData*> chat,
|
not_null<ChannelData*> chat,
|
||||||
bool canEdit,
|
bool canEdit,
|
||||||
Fn<void(ChannelData*)> callback);
|
Fn<void(ChannelData*)> callback);
|
||||||
|
|
||||||
[[nodiscard]] object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
[[nodiscard]] object_ptr<Ui::BoxContent> EditDiscussionLinkBox(
|
||||||
not_null<Window::SessionNavigation*> navigation,
|
not_null<Window::SessionNavigation*> navigation,
|
||||||
not_null<ChannelData*> channel,
|
not_null<ChannelData*> channel,
|
||||||
std::vector<not_null<PeerData*>> &&chats,
|
std::vector<not_null<PeerData*>> &&chats,
|
|
@ -1482,7 +1482,11 @@ void CheckBoostLevel(
|
||||||
show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
|
show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
|
||||||
.link = qs(data.vboost_url()),
|
.link = qs(data.vboost_url()),
|
||||||
.boost = counters,
|
.boost = counters,
|
||||||
|
.features = (peer->isChannel()
|
||||||
|
? LookupBoostFeatures(peer->asChannel())
|
||||||
|
: Ui::BoostFeatures()),
|
||||||
.reason = *reason,
|
.reason = *reason,
|
||||||
|
.group = !peer->isBroadcast(),
|
||||||
}, openStatistics, nullptr));
|
}, openStatistics, nullptr));
|
||||||
cancel();
|
cancel();
|
||||||
}).fail([=](const MTP::Error &error) {
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
|
|
@ -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_history_visibility_box.h"
|
||||||
#include "boxes/peers/edit_peer_permissions_box.h"
|
#include "boxes/peers/edit_peer_permissions_box.h"
|
||||||
#include "boxes/peers/edit_peer_invite_links.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_requests_box.h"
|
||||||
#include "boxes/peers/edit_peer_reactions.h"
|
#include "boxes/peers/edit_peer_reactions.h"
|
||||||
#include "boxes/peers/replace_boost_box.h"
|
#include "boxes/peers/replace_boost_box.h"
|
||||||
|
@ -358,12 +358,13 @@ private:
|
||||||
std::optional<QString> description;
|
std::optional<QString> description;
|
||||||
std::optional<bool> hiddenPreHistory;
|
std::optional<bool> hiddenPreHistory;
|
||||||
std::optional<bool> forum;
|
std::optional<bool> forum;
|
||||||
|
std::optional<bool> autotranslate;
|
||||||
std::optional<bool> signatures;
|
std::optional<bool> signatures;
|
||||||
std::optional<bool> signatureProfiles;
|
std::optional<bool> signatureProfiles;
|
||||||
std::optional<bool> noForwards;
|
std::optional<bool> noForwards;
|
||||||
std::optional<bool> joinToWrite;
|
std::optional<bool> joinToWrite;
|
||||||
std::optional<bool> requestToJoin;
|
std::optional<bool> requestToJoin;
|
||||||
std::optional<ChannelData*> linkedChat;
|
std::optional<ChannelData*> discussionLink;
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] object_ptr<Ui::RpWidget> createPhotoAndTitleEdit();
|
[[nodiscard]] object_ptr<Ui::RpWidget> createPhotoAndTitleEdit();
|
||||||
|
@ -379,12 +380,13 @@ private:
|
||||||
void refreshForumToggleLocked();
|
void refreshForumToggleLocked();
|
||||||
void showEditPeerTypeBox(
|
void showEditPeerTypeBox(
|
||||||
std::optional<rpl::producer<QString>> error = {});
|
std::optional<rpl::producer<QString>> error = {});
|
||||||
void showEditLinkedChatBox();
|
void showEditDiscussionLinkBox();
|
||||||
void fillPrivacyTypeButton();
|
void fillPrivacyTypeButton();
|
||||||
void fillLinkedChatButton();
|
void fillDiscussionLinkButton();
|
||||||
//void fillInviteLinkButton();
|
//void fillInviteLinkButton();
|
||||||
void fillForumButton();
|
void fillForumButton();
|
||||||
void fillColorIndexButton();
|
void fillColorIndexButton();
|
||||||
|
void fillAutoTranslateButton();
|
||||||
void fillSignaturesButton();
|
void fillSignaturesButton();
|
||||||
void fillHistoryVisibilityButton();
|
void fillHistoryVisibilityButton();
|
||||||
void fillManageSection();
|
void fillManageSection();
|
||||||
|
@ -408,11 +410,12 @@ private:
|
||||||
[[nodiscard]] std::optional<Saving> validate() const;
|
[[nodiscard]] std::optional<Saving> validate() const;
|
||||||
[[nodiscard]] bool validateUsernamesOrder(Saving &to) const;
|
[[nodiscard]] bool validateUsernamesOrder(Saving &to) const;
|
||||||
[[nodiscard]] bool validateUsername(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 validateTitle(Saving &to) const;
|
||||||
[[nodiscard]] bool validateDescription(Saving &to) const;
|
[[nodiscard]] bool validateDescription(Saving &to) const;
|
||||||
[[nodiscard]] bool validateHistoryVisibility(Saving &to) const;
|
[[nodiscard]] bool validateHistoryVisibility(Saving &to) const;
|
||||||
[[nodiscard]] bool validateForum(Saving &to) const;
|
[[nodiscard]] bool validateForum(Saving &to) const;
|
||||||
|
[[nodiscard]] bool validateAutotranslate(Saving &to) const;
|
||||||
[[nodiscard]] bool validateSignatures(Saving &to) const;
|
[[nodiscard]] bool validateSignatures(Saving &to) const;
|
||||||
[[nodiscard]] bool validateForwards(Saving &to) const;
|
[[nodiscard]] bool validateForwards(Saving &to) const;
|
||||||
[[nodiscard]] bool validateJoinToWrite(Saving &to) const;
|
[[nodiscard]] bool validateJoinToWrite(Saving &to) const;
|
||||||
|
@ -421,11 +424,12 @@ private:
|
||||||
void save();
|
void save();
|
||||||
void saveUsernamesOrder();
|
void saveUsernamesOrder();
|
||||||
void saveUsername();
|
void saveUsername();
|
||||||
void saveLinkedChat();
|
void saveDiscussionLink();
|
||||||
void saveTitle();
|
void saveTitle();
|
||||||
void saveDescription();
|
void saveDescription();
|
||||||
void saveHistoryVisibility();
|
void saveHistoryVisibility();
|
||||||
void saveForum();
|
void saveForum();
|
||||||
|
void saveAutotranslate();
|
||||||
void saveSignatures();
|
void saveSignatures();
|
||||||
void saveForwards();
|
void saveForwards();
|
||||||
void saveJoinToWrite();
|
void saveJoinToWrite();
|
||||||
|
@ -446,12 +450,13 @@ private:
|
||||||
void subscribeToMigration();
|
void subscribeToMigration();
|
||||||
void migrate(not_null<ChannelData*> channel);
|
void migrate(not_null<ChannelData*> channel);
|
||||||
|
|
||||||
std::optional<ChannelData*> _linkedChatSavedValue;
|
std::optional<ChannelData*> _discussionLinkSavedValue;
|
||||||
ChannelData *_linkedChatOriginalValue = nullptr;
|
ChannelData *_discussionLinkOriginalValue = nullptr;
|
||||||
bool _channelHasLocationOriginalValue = false;
|
bool _channelHasLocationOriginalValue = false;
|
||||||
std::optional<HistoryVisibility> _historyVisibilitySavedValue;
|
std::optional<HistoryVisibility> _historyVisibilitySavedValue;
|
||||||
std::optional<EditPeerTypeData> _typeDataSavedValue;
|
std::optional<EditPeerTypeData> _typeDataSavedValue;
|
||||||
std::optional<bool> _forumSavedValue;
|
std::optional<bool> _forumSavedValue;
|
||||||
|
std::optional<bool> _autotranslateSavedValue;
|
||||||
std::optional<bool> _signaturesSavedValue;
|
std::optional<bool> _signaturesSavedValue;
|
||||||
std::optional<bool> _signatureProfilesSavedValue;
|
std::optional<bool> _signatureProfilesSavedValue;
|
||||||
|
|
||||||
|
@ -474,8 +479,8 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
const rpl::event_stream<PrivacyAndForwards> _privacyTypeUpdates;
|
const rpl::event_stream<PrivacyAndForwards> _privacyTypeUpdates;
|
||||||
const rpl::event_stream<ChannelData*> _linkedChatUpdates;
|
const rpl::event_stream<ChannelData*> _discussionLinkUpdates;
|
||||||
mtpRequestId _linkedChatsRequestId = 0;
|
mtpRequestId _discussionLinksRequestId = 0;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
@ -813,7 +818,7 @@ void Controller::refreshHistoryVisibility() {
|
||||||
_controls.historyVisibilityWrap->toggle(
|
_controls.historyVisibilityWrap->toggle(
|
||||||
(!withUsername
|
(!withUsername
|
||||||
&& !_channelHasLocationOriginalValue
|
&& !_channelHasLocationOriginalValue
|
||||||
&& (!_linkedChatSavedValue || !*_linkedChatSavedValue)
|
&& (!_discussionLinkSavedValue || !*_discussionLinkSavedValue)
|
||||||
&& (!_forumSavedValue || !*_forumSavedValue)),
|
&& (!_forumSavedValue || !*_forumSavedValue)),
|
||||||
anim::type::instant);
|
anim::type::instant);
|
||||||
}
|
}
|
||||||
|
@ -825,8 +830,8 @@ void Controller::showEditPeerTypeBox(
|
||||||
_typeDataSavedValue = data;
|
_typeDataSavedValue = data;
|
||||||
refreshHistoryVisibility();
|
refreshHistoryVisibility();
|
||||||
});
|
});
|
||||||
_typeDataSavedValue->hasLinkedChat
|
_typeDataSavedValue->hasDiscussionLink
|
||||||
= (_linkedChatSavedValue.value_or(nullptr) != nullptr);
|
= (_discussionLinkSavedValue.value_or(nullptr) != nullptr);
|
||||||
const auto box = _navigation->parentController()->show(
|
const auto box = _navigation->parentController()->show(
|
||||||
Box<EditPeerTypeBox>(
|
Box<EditPeerTypeBox>(
|
||||||
_navigation,
|
_navigation,
|
||||||
|
@ -841,7 +846,7 @@ void Controller::showEditPeerTypeBox(
|
||||||
}, box->lifetime());
|
}, box->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::showEditLinkedChatBox() {
|
void Controller::showEditDiscussionLinkBox() {
|
||||||
Expects(_peer->isChannel());
|
Expects(_peer->isChannel());
|
||||||
|
|
||||||
if (_forumSavedValue && *_forumSavedValue) {
|
if (_forumSavedValue && *_forumSavedValue) {
|
||||||
|
@ -855,8 +860,8 @@ void Controller::showEditLinkedChatBox() {
|
||||||
if (*box) {
|
if (*box) {
|
||||||
(*box)->closeBox();
|
(*box)->closeBox();
|
||||||
}
|
}
|
||||||
*_linkedChatSavedValue = result;
|
*_discussionLinkSavedValue = result;
|
||||||
_linkedChatUpdates.fire_copy(result);
|
_discussionLinkUpdates.fire_copy(result);
|
||||||
refreshHistoryVisibility();
|
refreshHistoryVisibility();
|
||||||
refreshForumToggleLocked();
|
refreshForumToggleLocked();
|
||||||
};
|
};
|
||||||
|
@ -867,31 +872,31 @@ void Controller::showEditLinkedChatBox() {
|
||||||
&& (!channel->hiddenPreHistory()
|
&& (!channel->hiddenPreHistory()
|
||||||
|| channel->canEditPreHistoryHidden()));
|
|| channel->canEditPreHistoryHidden()));
|
||||||
|
|
||||||
if (const auto chat = *_linkedChatSavedValue) {
|
if (const auto chat = *_discussionLinkSavedValue) {
|
||||||
*box = _navigation->parentController()->show(EditLinkedChatBox(
|
*box = _navigation->parentController()->show(EditDiscussionLinkBox(
|
||||||
_navigation,
|
_navigation,
|
||||||
channel,
|
channel,
|
||||||
chat,
|
chat,
|
||||||
canEdit,
|
canEdit,
|
||||||
callback));
|
callback));
|
||||||
return;
|
return;
|
||||||
} else if (!canEdit || _linkedChatsRequestId) {
|
} else if (!canEdit || _discussionLinksRequestId) {
|
||||||
return;
|
return;
|
||||||
} else if (channel->isMegagroup()) {
|
} else if (channel->isMegagroup()) {
|
||||||
if (_forumSavedValue
|
if (_forumSavedValue
|
||||||
&& *_forumSavedValue
|
&& *_forumSavedValue
|
||||||
&& _linkedChatOriginalValue) {
|
&& _discussionLinkOriginalValue) {
|
||||||
ShowForumForDiscussionError(_navigation);
|
ShowForumForDiscussionError(_navigation);
|
||||||
} else {
|
} else {
|
||||||
// Restore original linked channel.
|
// Restore original discussion link.
|
||||||
callback(_linkedChatOriginalValue);
|
callback(_discussionLinkOriginalValue);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_linkedChatsRequestId = _api.request(
|
_discussionLinksRequestId = _api.request(
|
||||||
MTPchannels_GetGroupsForDiscussion()
|
MTPchannels_GetGroupsForDiscussion()
|
||||||
).done([=](const MTPmessages_Chats &result) {
|
).done([=](const MTPmessages_Chats &result) {
|
||||||
_linkedChatsRequestId = 0;
|
_discussionLinksRequestId = 0;
|
||||||
const auto list = result.match([&](const auto &data) {
|
const auto list = result.match([&](const auto &data) {
|
||||||
return data.vchats().v;
|
return data.vchats().v;
|
||||||
});
|
});
|
||||||
|
@ -900,13 +905,13 @@ void Controller::showEditLinkedChatBox() {
|
||||||
for (const auto &item : list) {
|
for (const auto &item : list) {
|
||||||
chats.emplace_back(_peer->owner().processChat(item));
|
chats.emplace_back(_peer->owner().processChat(item));
|
||||||
}
|
}
|
||||||
*box = _navigation->parentController()->show(EditLinkedChatBox(
|
*box = _navigation->parentController()->show(EditDiscussionLinkBox(
|
||||||
_navigation,
|
_navigation,
|
||||||
channel,
|
channel,
|
||||||
std::move(chats),
|
std::move(chats),
|
||||||
callback));
|
callback));
|
||||||
}).fail([=] {
|
}).fail([=] {
|
||||||
_linkedChatsRequestId = 0;
|
_discussionLinksRequestId = 0;
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -972,11 +977,11 @@ void Controller::fillPrivacyTypeButton() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::fillLinkedChatButton() {
|
void Controller::fillDiscussionLinkButton() {
|
||||||
Expects(_controls.buttonsLayout != nullptr);
|
Expects(_controls.buttonsLayout != nullptr);
|
||||||
|
|
||||||
_linkedChatSavedValue = _linkedChatOriginalValue = _peer->isChannel()
|
_discussionLinkSavedValue = _discussionLinkOriginalValue = _peer->isChannel()
|
||||||
? _peer->asChannel()->linkedChat()
|
? _peer->asChannel()->discussionLink()
|
||||||
: nullptr;
|
: nullptr;
|
||||||
|
|
||||||
const auto isGroup = (_peer->isChat() || _peer->isMegagroup());
|
const auto isGroup = (_peer->isChat() || _peer->isMegagroup());
|
||||||
|
@ -985,7 +990,7 @@ void Controller::fillLinkedChatButton() {
|
||||||
: rpl::combine(
|
: rpl::combine(
|
||||||
tr::lng_manage_linked_channel(),
|
tr::lng_manage_linked_channel(),
|
||||||
tr::lng_manage_linked_channel_restore(),
|
tr::lng_manage_linked_channel_restore(),
|
||||||
_linkedChatUpdates.events()
|
_discussionLinkUpdates.events()
|
||||||
) | rpl::map([=](
|
) | rpl::map([=](
|
||||||
const QString &edit,
|
const QString &edit,
|
||||||
const QString &restore,
|
const QString &restore,
|
||||||
|
@ -993,13 +998,13 @@ void Controller::fillLinkedChatButton() {
|
||||||
return chat ? edit : restore;
|
return chat ? edit : restore;
|
||||||
});
|
});
|
||||||
auto label = isGroup
|
auto label = isGroup
|
||||||
? _linkedChatUpdates.events(
|
? _discussionLinkUpdates.events(
|
||||||
) | rpl::map([](ChannelData *chat) {
|
) | rpl::map([](ChannelData *chat) {
|
||||||
return chat ? chat->name() : QString();
|
return chat ? chat->name() : QString();
|
||||||
}) | rpl::type_erased()
|
}) | rpl::type_erased()
|
||||||
: rpl::combine(
|
: rpl::combine(
|
||||||
tr::lng_manage_discussion_group_add(),
|
tr::lng_manage_discussion_group_add(),
|
||||||
_linkedChatUpdates.events()
|
_discussionLinkUpdates.events()
|
||||||
) | rpl::map([=](const QString &add, ChannelData *chat) {
|
) | rpl::map([=](const QString &add, ChannelData *chat) {
|
||||||
return chat ? chat->name() : add;
|
return chat ? chat->name() : add;
|
||||||
}) | rpl::type_erased();
|
}) | rpl::type_erased();
|
||||||
|
@ -1007,9 +1012,9 @@ void Controller::fillLinkedChatButton() {
|
||||||
_controls.buttonsLayout,
|
_controls.buttonsLayout,
|
||||||
std::move(text),
|
std::move(text),
|
||||||
std::move(label),
|
std::move(label),
|
||||||
[=] { showEditLinkedChatBox(); },
|
[=] { showEditDiscussionLinkBox(); },
|
||||||
{ isGroup ? &st::menuIconChannel : &st::menuIconGroups });
|
{ isGroup ? &st::menuIconChannel : &st::menuIconGroups });
|
||||||
_linkedChatUpdates.fire_copy(*_linkedChatSavedValue);
|
_discussionLinkUpdates.fire_copy(*_discussionLinkSavedValue);
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
//void Controller::fillInviteLinkButton() {
|
//void Controller::fillInviteLinkButton() {
|
||||||
|
@ -1044,7 +1049,7 @@ void Controller::fillForumButton() {
|
||||||
) | rpl::start_with_next([=](bool toggled) {
|
) | rpl::start_with_next([=](bool toggled) {
|
||||||
if (_controls.forumToggleLocked && toggled) {
|
if (_controls.forumToggleLocked && toggled) {
|
||||||
unlocks->fire(false);
|
unlocks->fire(false);
|
||||||
if (_linkedChatSavedValue && *_linkedChatSavedValue) {
|
if (_discussionLinkSavedValue && *_discussionLinkSavedValue) {
|
||||||
ShowForumForDiscussionError(_navigation);
|
ShowForumForDiscussionError(_navigation);
|
||||||
} else {
|
} else {
|
||||||
_navigation->showToast(
|
_navigation->showToast(
|
||||||
|
@ -1074,8 +1079,8 @@ void Controller::refreshForumToggleLocked() {
|
||||||
const auto channel = _peer->asChannel();
|
const auto channel = _peer->asChannel();
|
||||||
const auto notenough = !_peer->isForum()
|
const auto notenough = !_peer->isForum()
|
||||||
&& ((chat ? chat->count : channel->membersCount()) < limit);
|
&& ((chat ? chat->count : channel->membersCount()) < limit);
|
||||||
const auto linked = _linkedChatSavedValue
|
const auto linked = _discussionLinkSavedValue
|
||||||
&& *_linkedChatSavedValue;
|
&& *_discussionLinkSavedValue;
|
||||||
const auto locked = _controls.forumToggleLocked = notenough || linked;
|
const auto locked = _controls.forumToggleLocked = notenough || linked;
|
||||||
_controls.forumToggle->setToggleLocked(locked);
|
_controls.forumToggle->setToggleLocked(locked);
|
||||||
}
|
}
|
||||||
|
@ -1091,6 +1096,69 @@ void Controller::fillColorIndexButton() {
|
||||||
st::managePeerColorsButton);
|
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<bool> toggled;
|
||||||
|
rpl::variable<bool> isLocked = false;
|
||||||
|
};
|
||||||
|
const auto state = autotranslate->lifetime().make_state<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() {
|
void Controller::fillSignaturesButton() {
|
||||||
Expects(_controls.buttonsLayout != nullptr);
|
Expects(_controls.buttonsLayout != nullptr);
|
||||||
|
|
||||||
|
@ -1253,6 +1321,8 @@ void Controller::fillManageSection() {
|
||||||
const auto canEditSignatures = isChannel
|
const auto canEditSignatures = isChannel
|
||||||
&& channel->canEditSignatures()
|
&& channel->canEditSignatures()
|
||||||
&& !channel->isMegagroup();
|
&& !channel->isMegagroup();
|
||||||
|
const auto canEditAutoTranslate = isChannel
|
||||||
|
&& channel->canEditAutoTranslate();
|
||||||
const auto canEditPreHistoryHidden = isChannel
|
const auto canEditPreHistoryHidden = isChannel
|
||||||
? channel->canEditPreHistoryHidden()
|
? channel->canEditPreHistoryHidden()
|
||||||
: chat->canEditPreHistoryHidden();
|
: chat->canEditPreHistoryHidden();
|
||||||
|
@ -1283,8 +1353,8 @@ void Controller::fillManageSection() {
|
||||||
const auto canEditStickers = isChannel && channel->canEditStickers();
|
const auto canEditStickers = isChannel && channel->canEditStickers();
|
||||||
const auto canDeleteChannel = isChannel && channel->canDelete();
|
const auto canDeleteChannel = isChannel && channel->canDelete();
|
||||||
const auto canEditColorIndex = isChannel && channel->canEditEmoji();
|
const auto canEditColorIndex = isChannel && channel->canEditEmoji();
|
||||||
const auto canViewOrEditLinkedChat = isChannel
|
const auto canViewOrEditDiscussionLink = isChannel
|
||||||
&& (channel->linkedChat()
|
&& (channel->discussionLink()
|
||||||
|| (channel->isBroadcast() && channel->canEditInformation()));
|
|| (channel->isBroadcast() && channel->canEditInformation()));
|
||||||
|
|
||||||
::AddSkip(_controls.buttonsLayout, 0);
|
::AddSkip(_controls.buttonsLayout, 0);
|
||||||
|
@ -1294,8 +1364,8 @@ void Controller::fillManageSection() {
|
||||||
//} else if (canEditInviteLinks) {
|
//} else if (canEditInviteLinks) {
|
||||||
// fillInviteLinkButton();
|
// fillInviteLinkButton();
|
||||||
}
|
}
|
||||||
if (canViewOrEditLinkedChat) {
|
if (canViewOrEditDiscussionLink) {
|
||||||
fillLinkedChatButton();
|
fillDiscussionLinkButton();
|
||||||
}
|
}
|
||||||
if (canEditPreHistoryHidden) {
|
if (canEditPreHistoryHidden) {
|
||||||
fillHistoryVisibilityButton();
|
fillHistoryVisibilityButton();
|
||||||
|
@ -1306,13 +1376,16 @@ void Controller::fillManageSection() {
|
||||||
if (canEditColorIndex) {
|
if (canEditColorIndex) {
|
||||||
fillColorIndexButton();
|
fillColorIndexButton();
|
||||||
}
|
}
|
||||||
|
if (canEditAutoTranslate) {
|
||||||
|
fillAutoTranslateButton();
|
||||||
|
}
|
||||||
if (canEditSignatures) {
|
if (canEditSignatures) {
|
||||||
fillSignaturesButton();
|
fillSignaturesButton();
|
||||||
} else if (canEditPreHistoryHidden
|
} else if (canEditPreHistoryHidden
|
||||||
|| canEditForum
|
|| canEditForum
|
||||||
|| canEditColorIndex
|
|| canEditColorIndex
|
||||||
//|| canEditInviteLinks
|
//|| canEditInviteLinks
|
||||||
|| canViewOrEditLinkedChat
|
|| canViewOrEditDiscussionLink
|
||||||
|| canEditType) {
|
|| canEditType) {
|
||||||
::AddSkip(_controls.buttonsLayout);
|
::AddSkip(_controls.buttonsLayout);
|
||||||
}
|
}
|
||||||
|
@ -1336,6 +1409,8 @@ void Controller::fillManageSection() {
|
||||||
? tr::lng_manage_peer_reactions_on(tr::now)
|
? tr::lng_manage_peer_reactions_on(tr::now)
|
||||||
: some
|
: some
|
||||||
? QString::number(some)
|
? QString::number(some)
|
||||||
|
: allowed.paidEnabled
|
||||||
|
? QString::number(1)
|
||||||
: tr::lng_manage_peer_reactions_off(tr::now);
|
: tr::lng_manage_peer_reactions_off(tr::now);
|
||||||
});
|
});
|
||||||
AddButtonWithCount(
|
AddButtonWithCount(
|
||||||
|
@ -1538,7 +1613,11 @@ void Controller::editReactions() {
|
||||||
strong->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
|
strong->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
|
||||||
.link = link,
|
.link = link,
|
||||||
.boost = counters,
|
.boost = counters,
|
||||||
|
.features = (_peer->isChannel()
|
||||||
|
? LookupBoostFeatures(_peer->asChannel())
|
||||||
|
: Ui::BoostFeatures()),
|
||||||
.reason = { Ui::AskBoostCustomReactions{ required } },
|
.reason = { Ui::AskBoostCustomReactions{ required } },
|
||||||
|
.group = !_peer->isBroadcast(),
|
||||||
}, openStatistics, nullptr));
|
}, openStatistics, nullptr));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1887,11 +1966,12 @@ std::optional<Controller::Saving> Controller::validate() const {
|
||||||
auto result = Saving();
|
auto result = Saving();
|
||||||
if (validateUsernamesOrder(result)
|
if (validateUsernamesOrder(result)
|
||||||
&& validateUsername(result)
|
&& validateUsername(result)
|
||||||
&& validateLinkedChat(result)
|
&& validateDiscussionLink(result)
|
||||||
&& validateTitle(result)
|
&& validateTitle(result)
|
||||||
&& validateDescription(result)
|
&& validateDescription(result)
|
||||||
&& validateHistoryVisibility(result)
|
&& validateHistoryVisibility(result)
|
||||||
&& validateForum(result)
|
&& validateForum(result)
|
||||||
|
&& validateAutotranslate(result)
|
||||||
&& validateSignatures(result)
|
&& validateSignatures(result)
|
||||||
&& validateForwards(result)
|
&& validateForwards(result)
|
||||||
&& validateJoinToWrite(result)
|
&& validateJoinToWrite(result)
|
||||||
|
@ -1928,11 +2008,11 @@ bool Controller::validateUsername(Saving &to) const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Controller::validateLinkedChat(Saving &to) const {
|
bool Controller::validateDiscussionLink(Saving &to) const {
|
||||||
if (!_linkedChatSavedValue) {
|
if (!_discussionLinkSavedValue) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
to.linkedChat = *_linkedChatSavedValue;
|
to.discussionLink = *_discussionLinkSavedValue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1979,6 +2059,14 @@ bool Controller::validateForum(Saving &to) const {
|
||||||
return true;
|
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 {
|
bool Controller::validateSignatures(Saving &to) const {
|
||||||
Expects(_signaturesSavedValue.has_value()
|
Expects(_signaturesSavedValue.has_value()
|
||||||
== _signatureProfilesSavedValue.has_value());
|
== _signatureProfilesSavedValue.has_value());
|
||||||
|
@ -2025,11 +2113,12 @@ void Controller::save() {
|
||||||
_savingData = *saving;
|
_savingData = *saving;
|
||||||
pushSaveStage([=] { saveUsernamesOrder(); });
|
pushSaveStage([=] { saveUsernamesOrder(); });
|
||||||
pushSaveStage([=] { saveUsername(); });
|
pushSaveStage([=] { saveUsername(); });
|
||||||
pushSaveStage([=] { saveLinkedChat(); });
|
pushSaveStage([=] { saveDiscussionLink(); });
|
||||||
pushSaveStage([=] { saveTitle(); });
|
pushSaveStage([=] { saveTitle(); });
|
||||||
pushSaveStage([=] { saveDescription(); });
|
pushSaveStage([=] { saveDescription(); });
|
||||||
pushSaveStage([=] { saveHistoryVisibility(); });
|
pushSaveStage([=] { saveHistoryVisibility(); });
|
||||||
pushSaveStage([=] { saveForum(); });
|
pushSaveStage([=] { saveForum(); });
|
||||||
|
pushSaveStage([=] { saveAutotranslate(); });
|
||||||
pushSaveStage([=] { saveSignatures(); });
|
pushSaveStage([=] { saveSignatures(); });
|
||||||
pushSaveStage([=] { saveForwards(); });
|
pushSaveStage([=] { saveForwards(); });
|
||||||
pushSaveStage([=] { saveJoinToWrite(); });
|
pushSaveStage([=] { saveJoinToWrite(); });
|
||||||
|
@ -2147,36 +2236,37 @@ void Controller::saveUsername() {
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::saveLinkedChat() {
|
void Controller::saveDiscussionLink() {
|
||||||
const auto channel = _peer->asChannel();
|
const auto channel = _peer->asChannel();
|
||||||
if (!channel) {
|
if (!channel) {
|
||||||
return continueSave();
|
return continueSave();
|
||||||
}
|
}
|
||||||
if (!_savingData.linkedChat
|
if (!_savingData.discussionLink
|
||||||
|| *_savingData.linkedChat == channel->linkedChat()) {
|
|| *_savingData.discussionLink == channel->discussionLink()) {
|
||||||
return continueSave();
|
return continueSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto chat = *_savingData.linkedChat;
|
const auto chat = *_savingData.discussionLink;
|
||||||
if (channel->isBroadcast() && chat && chat->hiddenPreHistory()) {
|
if (channel->isBroadcast() && chat && chat->hiddenPreHistory()) {
|
||||||
togglePreHistoryHidden(
|
togglePreHistoryHidden(
|
||||||
chat,
|
chat,
|
||||||
false,
|
false,
|
||||||
[=] { saveLinkedChat(); },
|
[=] { saveDiscussionLink(); },
|
||||||
[=] { cancelSave(); });
|
[=] { cancelSave(); });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto input = *_savingData.linkedChat
|
const auto input = *_savingData.discussionLink
|
||||||
? (*_savingData.linkedChat)->inputChannel
|
? (*_savingData.discussionLink)->inputChannel
|
||||||
: MTP_inputChannelEmpty();
|
: MTP_inputChannelEmpty();
|
||||||
_api.request(MTPchannels_SetDiscussionGroup(
|
_api.request(MTPchannels_SetDiscussionGroup(
|
||||||
(channel->isBroadcast() ? channel->inputChannel : input),
|
(channel->isBroadcast() ? channel->inputChannel : input),
|
||||||
(channel->isBroadcast() ? input : channel->inputChannel)
|
(channel->isBroadcast() ? input : channel->inputChannel)
|
||||||
)).done([=] {
|
)).done([=] {
|
||||||
channel->setLinkedChat(*_savingData.linkedChat);
|
channel->setDiscussionLink(*_savingData.discussionLink);
|
||||||
continueSave();
|
continueSave();
|
||||||
}).fail([=] {
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
_navigation->showToast(error.type());
|
||||||
cancelSave();
|
cancelSave();
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
@ -2416,6 +2506,30 @@ void Controller::saveForum() {
|
||||||
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
||||||
continueSave();
|
continueSave();
|
||||||
} else {
|
} 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();
|
cancelSave();
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
|
@ -2450,6 +2564,7 @@ void Controller::saveSignatures() {
|
||||||
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
||||||
continueSave();
|
continueSave();
|
||||||
} else {
|
} else {
|
||||||
|
_navigation->showToast(error.type());
|
||||||
cancelSave();
|
cancelSave();
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
|
@ -2470,6 +2585,7 @@ void Controller::saveForwards() {
|
||||||
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
||||||
continueSave();
|
continueSave();
|
||||||
} else {
|
} else {
|
||||||
|
_navigation->showToast(error.type());
|
||||||
cancelSave();
|
cancelSave();
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
|
@ -2492,6 +2608,7 @@ void Controller::saveJoinToWrite() {
|
||||||
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
||||||
continueSave();
|
continueSave();
|
||||||
} else {
|
} else {
|
||||||
|
_navigation->showToast(error.type());
|
||||||
cancelSave();
|
cancelSave();
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
|
@ -2514,6 +2631,7 @@ void Controller::saveRequestToJoin() {
|
||||||
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
||||||
continueSave();
|
continueSave();
|
||||||
} else {
|
} else {
|
||||||
|
_navigation->showToast(error.type());
|
||||||
cancelSave();
|
cancelSave();
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
|
|
|
@ -1140,7 +1140,7 @@ void ShowEditPeerPermissionsBox(
|
||||||
result.emplace(
|
result.emplace(
|
||||||
Flag::ChangeInfo | Flag::PinMessages,
|
Flag::ChangeInfo | Flag::PinMessages,
|
||||||
tr::lng_rights_permission_unavailable(tr::now));
|
tr::lng_rights_permission_unavailable(tr::now));
|
||||||
} else if (channel->isMegagroup() && channel->linkedChat()) {
|
} else if (channel->isMegagroup() && channel->discussionLink()) {
|
||||||
result.emplace(
|
result.emplace(
|
||||||
Flag::ChangeInfo | Flag::PinMessages,
|
Flag::ChangeInfo | Flag::PinMessages,
|
||||||
tr::lng_rights_permission_in_discuss(tr::now));
|
tr::lng_rights_permission_in_discuss(tr::now));
|
||||||
|
|
|
@ -220,7 +220,7 @@ void Controller::createContent() {
|
||||||
const auto wrap = _controls.whoSendWrap->entity();
|
const auto wrap = _controls.whoSendWrap->entity();
|
||||||
|
|
||||||
Ui::AddSkip(wrap);
|
Ui::AddSkip(wrap);
|
||||||
if (_dataSavedValue->hasLinkedChat) {
|
if (_dataSavedValue->hasDiscussionLink) {
|
||||||
Ui::AddSubsectionTitle(wrap, tr::lng_manage_peer_send_title());
|
Ui::AddSubsectionTitle(wrap, tr::lng_manage_peer_send_title());
|
||||||
|
|
||||||
_controls.joinToWrite = wrap->add(EditPeerInfoBox::CreateButton(
|
_controls.joinToWrite = wrap->add(EditPeerInfoBox::CreateButton(
|
||||||
|
@ -498,7 +498,7 @@ void Controller::privacyChanged(Privacy value) {
|
||||||
}
|
}
|
||||||
_controls.whoSendWrap->toggle(
|
_controls.whoSendWrap->toggle(
|
||||||
(value == Privacy::HasUsername
|
(value == Privacy::HasUsername
|
||||||
|| (_dataSavedValue && _dataSavedValue->hasLinkedChat)),
|
|| (_dataSavedValue && _dataSavedValue->hasDiscussionLink)),
|
||||||
anim::type::instant);
|
anim::type::instant);
|
||||||
};
|
};
|
||||||
const auto refreshVisibilities = [&] {
|
const auto refreshVisibilities = [&] {
|
||||||
|
|
|
@ -37,7 +37,7 @@ struct EditPeerTypeData {
|
||||||
Privacy privacy = Privacy::NoUsername;
|
Privacy privacy = Privacy::NoUsername;
|
||||||
QString username;
|
QString username;
|
||||||
std::vector<QString> usernamesOrder;
|
std::vector<QString> usernamesOrder;
|
||||||
bool hasLinkedChat = false;
|
bool hasDiscussionLink = false;
|
||||||
bool noForwards = false;
|
bool noForwards = false;
|
||||||
bool joinToWrite = false;
|
bool joinToWrite = false;
|
||||||
bool requestToJoin = false;
|
bool requestToJoin = false;
|
||||||
|
|
|
@ -449,6 +449,7 @@ Ui::BoostFeatures LookupBoostFeatures(not_null<ChannelData*> channel) {
|
||||||
.nameColorsByLevel = std::move(nameColorsByLevel),
|
.nameColorsByLevel = std::move(nameColorsByLevel),
|
||||||
.linkStylesByLevel = std::move(linkStylesByLevel),
|
.linkStylesByLevel = std::move(linkStylesByLevel),
|
||||||
.linkLogoLevel = group ? 0 : levelLimits.channelBgIconLevelMin(),
|
.linkLogoLevel = group ? 0 : levelLimits.channelBgIconLevelMin(),
|
||||||
|
.autotranslateLevel = group ? 0 : levelLimits.channelAutoTranslateLevelMin(),
|
||||||
.transcribeLevel = group ? levelLimits.groupTranscribeLevelMin() : 0,
|
.transcribeLevel = group ? levelLimits.groupTranscribeLevelMin() : 0,
|
||||||
.emojiPackLevel = group ? levelLimits.groupEmojiStickersLevelMin() : 0,
|
.emojiPackLevel = group ? levelLimits.groupEmojiStickersLevelMin() : 0,
|
||||||
.emojiStatusLevel = group
|
.emojiStatusLevel = group
|
||||||
|
|
|
@ -153,6 +153,10 @@ void EditPriceBox(
|
||||||
field->resize(width, field->height());
|
field->resize(width, field->height());
|
||||||
wrap->resize(width, field->height());
|
wrap->resize(width, field->height());
|
||||||
}, wrap->lifetime());
|
}, 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();
|
field->selectAll();
|
||||||
box->setFocusCallback([=] {
|
box->setFocusCallback([=] {
|
||||||
field->setFocusFast();
|
field->setFocusFast();
|
||||||
|
@ -173,11 +177,6 @@ void EditPriceBox(
|
||||||
return false;
|
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 save = [=] {
|
||||||
const auto now = field->getLastText().toULongLong();
|
const auto now = field->getLastText().toULongLong();
|
||||||
if (now > limit) {
|
if (now > limit) {
|
||||||
|
|
|
@ -17,8 +17,13 @@ namespace Data {
|
||||||
struct UniqueGift;
|
struct UniqueGift;
|
||||||
struct GiftCode;
|
struct GiftCode;
|
||||||
struct CreditsHistoryEntry;
|
struct CreditsHistoryEntry;
|
||||||
|
class SavedStarGiftId;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
|
namespace Main {
|
||||||
|
class SessionShow;
|
||||||
|
} // namespace Main
|
||||||
|
|
||||||
namespace Payments {
|
namespace Payments {
|
||||||
enum class CheckoutResult;
|
enum class CheckoutResult;
|
||||||
} // namespace Payments
|
} // namespace Payments
|
||||||
|
@ -52,7 +57,9 @@ void ShowStarGiftBox(
|
||||||
void AddUniqueGiftCover(
|
void AddUniqueGiftCover(
|
||||||
not_null<VerticalLayout*> container,
|
not_null<VerticalLayout*> container,
|
||||||
rpl::producer<Data::UniqueGift> data,
|
rpl::producer<Data::UniqueGift> data,
|
||||||
rpl::producer<QString> subtitleOverride = nullptr);
|
rpl::producer<QString> subtitleOverride = nullptr,
|
||||||
|
rpl::producer<int> resalePrice = nullptr,
|
||||||
|
Fn<void()> resaleClick = nullptr);
|
||||||
void AddWearGiftCover(
|
void AddWearGiftCover(
|
||||||
not_null<VerticalLayout*> container,
|
not_null<VerticalLayout*> container,
|
||||||
const Data::UniqueGift &data,
|
const Data::UniqueGift &data,
|
||||||
|
@ -64,6 +71,17 @@ void ShowUniqueGiftWearBox(
|
||||||
const Data::UniqueGift &gift,
|
const Data::UniqueGift &gift,
|
||||||
Settings::GiftWearBoxStyleOverride st);
|
Settings::GiftWearBoxStyleOverride st);
|
||||||
|
|
||||||
|
void UpdateGiftSellPrice(
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
std::shared_ptr<Data::UniqueGift> unique,
|
||||||
|
Data::SavedStarGiftId savedId,
|
||||||
|
int price);
|
||||||
|
void ShowUniqueGiftSellBox(
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
std::shared_ptr<Data::UniqueGift> unique,
|
||||||
|
Data::SavedStarGiftId savedId,
|
||||||
|
Settings::GiftWearBoxStyleOverride st);
|
||||||
|
|
||||||
struct PatternPoint {
|
struct PatternPoint {
|
||||||
QPointF position;
|
QPointF position;
|
||||||
float64 scale = 1.;
|
float64 scale = 1.;
|
||||||
|
@ -100,13 +118,31 @@ void AddUniqueCloseButton(
|
||||||
Settings::CreditsEntryBoxStyleOverrides st,
|
Settings::CreditsEntryBoxStyleOverrides st,
|
||||||
Fn<void(not_null<PopupMenu*>)> fillMenu = nullptr);
|
Fn<void(not_null<PopupMenu*>)> fillMenu = nullptr);
|
||||||
|
|
||||||
|
void SubmitStarsForm(
|
||||||
|
std::shared_ptr<Main::SessionShow> show,
|
||||||
|
MTPInputInvoice invoice,
|
||||||
|
uint64 formId,
|
||||||
|
uint64 price,
|
||||||
|
Fn<void(Payments::CheckoutResult, const MTPUpdates *)> done);
|
||||||
|
void RequestStarsForm(
|
||||||
|
std::shared_ptr<Main::SessionShow> show,
|
||||||
|
MTPInputInvoice invoice,
|
||||||
|
Fn<void(
|
||||||
|
uint64 formId,
|
||||||
|
uint64 price,
|
||||||
|
std::optional<Payments::CheckoutResult> failure)> done);
|
||||||
void RequestStarsFormAndSubmit(
|
void RequestStarsFormAndSubmit(
|
||||||
not_null<Window::SessionController*> window,
|
std::shared_ptr<Main::SessionShow> show,
|
||||||
MTPInputInvoice invoice,
|
MTPInputInvoice invoice,
|
||||||
Fn<void(Payments::CheckoutResult, const MTPUpdates *)> done);
|
Fn<void(Payments::CheckoutResult, const MTPUpdates *)> done);
|
||||||
|
|
||||||
void ShowGiftTransferredToast(
|
void ShowGiftTransferredToast(
|
||||||
base::weak_ptr<Window::SessionController> weak,
|
std::shared_ptr<Main::SessionShow> show,
|
||||||
|
not_null<PeerData*> to,
|
||||||
|
const Data::UniqueGift &gift);
|
||||||
|
|
||||||
|
void ShowResaleGiftBoughtToast(
|
||||||
|
std::shared_ptr<Main::SessionShow> show,
|
||||||
not_null<PeerData*> to,
|
not_null<PeerData*> to,
|
||||||
const Data::UniqueGift &gift);
|
const Data::UniqueGift &gift);
|
||||||
|
|
||||||
|
|
|
@ -25,12 +25,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/boxes/confirm_box.h"
|
#include "ui/boxes/confirm_box.h"
|
||||||
#include "ui/layers/generic_box.h"
|
#include "ui/layers/generic_box.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
|
#include "ui/toast/toast.h"
|
||||||
#include "ui/basic_click_handlers.h"
|
#include "ui/basic_click_handlers.h"
|
||||||
#include "ui/empty_userpic.h"
|
#include "ui/empty_userpic.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "ui/vertical_list.h"
|
#include "ui/vertical_list.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "styles/style_boxes.h" // peerListSingleRow.
|
#include "styles/style_boxes.h" // peerListSingleRow.
|
||||||
|
#include "styles/style_credits.h" // starIconEmoji.
|
||||||
#include "styles/style_dialogs.h" // recentPeersSpecialName.
|
#include "styles/style_dialogs.h" // recentPeersSpecialName.
|
||||||
#include "styles/style_layers.h" // boxLabel.
|
#include "styles/style_layers.h" // boxLabel.
|
||||||
|
|
||||||
|
@ -421,7 +423,8 @@ void TransferGift(
|
||||||
not_null<PeerData*> to,
|
not_null<PeerData*> to,
|
||||||
std::shared_ptr<Data::UniqueGift> gift,
|
std::shared_ptr<Data::UniqueGift> gift,
|
||||||
Data::SavedStarGiftId savedId,
|
Data::SavedStarGiftId savedId,
|
||||||
Fn<void(Payments::CheckoutResult)> done) {
|
Fn<void(Payments::CheckoutResult)> done,
|
||||||
|
bool skipPaymentForm = false) {
|
||||||
Expects(to->isUser());
|
Expects(to->isUser());
|
||||||
|
|
||||||
const auto session = &window->session();
|
const auto session = &window->session();
|
||||||
|
@ -429,38 +432,115 @@ void TransferGift(
|
||||||
auto formDone = [=](
|
auto formDone = [=](
|
||||||
Payments::CheckoutResult result,
|
Payments::CheckoutResult result,
|
||||||
const MTPUpdates *updates) {
|
const MTPUpdates *updates) {
|
||||||
|
if (result == Payments::CheckoutResult::Free) {
|
||||||
|
Assert(!skipPaymentForm);
|
||||||
|
TransferGift(window, to, gift, savedId, done, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
done(result);
|
done(result);
|
||||||
if (result == Payments::CheckoutResult::Paid) {
|
if (result == Payments::CheckoutResult::Paid) {
|
||||||
|
session->data().notifyGiftUpdate({
|
||||||
|
.id = savedId,
|
||||||
|
.action = Data::GiftUpdate::Action::Transfer,
|
||||||
|
});
|
||||||
if (const auto strong = weak.get()) {
|
if (const auto strong = weak.get()) {
|
||||||
strong->session().data().notifyGiftUpdate({
|
Ui::ShowGiftTransferredToast(strong->uiShow(), to, *gift);
|
||||||
.id = savedId,
|
|
||||||
.action = Data::GiftUpdate::Action::Transfer,
|
|
||||||
});
|
|
||||||
Ui::ShowGiftTransferredToast(strong, to, *gift);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (gift->starsForTransfer <= 0) {
|
if (skipPaymentForm) {
|
||||||
|
// We can't check (gift->starsForTransfer <= 0) here.
|
||||||
|
//
|
||||||
|
// Sometimes we don't know the price for transfer.
|
||||||
|
// Like when we transfer a gift from Resale tab.
|
||||||
session->api().request(MTPpayments_TransferStarGift(
|
session->api().request(MTPpayments_TransferStarGift(
|
||||||
Api::InputSavedStarGiftId(savedId),
|
Api::InputSavedStarGiftId(savedId, gift),
|
||||||
to->input
|
to->input
|
||||||
)).done([=](const MTPUpdates &result) {
|
)).done([=](const MTPUpdates &result) {
|
||||||
session->api().applyUpdates(result);
|
session->api().applyUpdates(result);
|
||||||
formDone(Payments::CheckoutResult::Paid, &result);
|
formDone(Payments::CheckoutResult::Paid, &result);
|
||||||
}).fail([=](const MTP::Error &error) {
|
}).fail([=](const MTP::Error &error) {
|
||||||
formDone(Payments::CheckoutResult::Failed, nullptr);
|
formDone(Payments::CheckoutResult::Failed, nullptr);
|
||||||
if (const auto strong = weak.get()) {
|
const auto earlyPrefix = u"STARGIFT_TRANSFER_TOO_EARLY_"_q;
|
||||||
|
const auto type = error.type();
|
||||||
|
if (type.startsWith(earlyPrefix)) {
|
||||||
|
const auto seconds = type.mid(earlyPrefix.size()).toInt();
|
||||||
|
const auto newAvailableAt = base::unixtime::now() + seconds;
|
||||||
|
gift->canTransferAt = newAvailableAt;
|
||||||
|
if (const auto strong = weak.get()) {
|
||||||
|
ShowTransferGiftLater(strong->uiShow(), gift);
|
||||||
|
}
|
||||||
|
} else if (const auto strong = weak.get()) {
|
||||||
strong->showToast(error.type());
|
strong->showToast(error.type());
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
return;
|
} else {
|
||||||
|
Ui::RequestStarsFormAndSubmit(
|
||||||
|
window->uiShow(),
|
||||||
|
MTP_inputInvoiceStarGiftTransfer(
|
||||||
|
Api::InputSavedStarGiftId(savedId, gift),
|
||||||
|
to->input),
|
||||||
|
std::move(formDone));
|
||||||
}
|
}
|
||||||
Ui::RequestStarsFormAndSubmit(
|
}
|
||||||
window,
|
|
||||||
MTP_inputInvoiceStarGiftTransfer(
|
void BuyResaleGift(
|
||||||
Api::InputSavedStarGiftId(savedId),
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
to->input),
|
not_null<PeerData*> to,
|
||||||
std::move(formDone));
|
std::shared_ptr<Data::UniqueGift> gift,
|
||||||
|
Fn<void(Payments::CheckoutResult)> done) {
|
||||||
|
auto paymentDone = [=](
|
||||||
|
Payments::CheckoutResult result,
|
||||||
|
const MTPUpdates *updates) {
|
||||||
|
done(result);
|
||||||
|
if (result == Payments::CheckoutResult::Paid) {
|
||||||
|
gift->starsForResale = 0;
|
||||||
|
to->owner().notifyGiftUpdate({
|
||||||
|
.slug = gift->slug,
|
||||||
|
.action = Data::GiftUpdate::Action::ResaleChange,
|
||||||
|
});
|
||||||
|
Ui::ShowResaleGiftBoughtToast(show, to, *gift);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto invoice = MTP_inputInvoiceStarGiftResale(
|
||||||
|
MTP_string(gift->slug),
|
||||||
|
to->input);
|
||||||
|
|
||||||
|
Ui::RequestStarsForm(show, invoice, [=](
|
||||||
|
uint64 formId,
|
||||||
|
uint64 price,
|
||||||
|
std::optional<Payments::CheckoutResult> failure) {
|
||||||
|
const auto submit = [=] {
|
||||||
|
SubmitStarsForm(show, invoice, formId, price, paymentDone);
|
||||||
|
};
|
||||||
|
if (failure) {
|
||||||
|
paymentDone(*failure, nullptr);
|
||||||
|
} else if (price != gift->starsForResale) {
|
||||||
|
const auto cost = Ui::Text::IconEmoji(&st::starIconEmoji).append(
|
||||||
|
Lang::FormatCountDecimal(price));
|
||||||
|
const auto cancelled = [=](Fn<void()> close) {
|
||||||
|
paymentDone(Payments::CheckoutResult::Cancelled, nullptr);
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
show->show(Ui::MakeConfirmBox({
|
||||||
|
.text = tr::lng_gift_buy_price_change_text(
|
||||||
|
tr::now,
|
||||||
|
lt_price,
|
||||||
|
Ui::Text::Wrapped(cost, EntityType::Bold),
|
||||||
|
Ui::Text::WithEntities),
|
||||||
|
.confirmed = [=](Fn<void()> close) { close(); submit(); },
|
||||||
|
.cancelled = cancelled,
|
||||||
|
.confirmText = tr::lng_gift_buy_resale_button(
|
||||||
|
lt_cost,
|
||||||
|
rpl::single(cost),
|
||||||
|
Ui::Text::WithEntities),
|
||||||
|
.title = tr::lng_gift_buy_price_change_title(),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
submit();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -545,6 +625,9 @@ void ShowTransferGiftBox(
|
||||||
not_null<Window::SessionController*> window,
|
not_null<Window::SessionController*> window,
|
||||||
std::shared_ptr<Data::UniqueGift> gift,
|
std::shared_ptr<Data::UniqueGift> gift,
|
||||||
Data::SavedStarGiftId savedId) {
|
Data::SavedStarGiftId savedId) {
|
||||||
|
if (ShowTransferGiftLater(window->uiShow(), gift)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto controller = std::make_unique<Controller>(
|
auto controller = std::make_unique<Controller>(
|
||||||
window,
|
window,
|
||||||
gift,
|
gift,
|
||||||
|
@ -566,3 +649,116 @@ void ShowTransferGiftBox(
|
||||||
Box<PeerListBox>(std::move(controller), std::move(initBox)),
|
Box<PeerListBox>(std::move(controller), std::move(initBox)),
|
||||||
Ui::LayerOption::KeepOther);
|
Ui::LayerOption::KeepOther);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShowBuyResaleGiftBox(
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
std::shared_ptr<Data::UniqueGift> gift,
|
||||||
|
not_null<PeerData*> to,
|
||||||
|
Fn<void()> closeParentBox) {
|
||||||
|
show->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||||
|
box->setTitle(tr::lng_gift_buy_resale_title(
|
||||||
|
lt_name,
|
||||||
|
rpl::single(UniqueGiftName(*gift))));
|
||||||
|
|
||||||
|
auto transfer = tr::lng_gift_buy_resale_button(
|
||||||
|
lt_cost,
|
||||||
|
rpl::single(
|
||||||
|
Ui::Text::IconEmoji(&st::starIconEmoji).append(
|
||||||
|
Lang::FormatCountDecimal(gift->starsForResale))),
|
||||||
|
Ui::Text::WithEntities);
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
bool sent = false;
|
||||||
|
};
|
||||||
|
const auto state = std::make_shared<State>();
|
||||||
|
auto callback = [=](Fn<void()> close) {
|
||||||
|
if (state->sent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state->sent = true;
|
||||||
|
const auto weak = Ui::MakeWeak(box);
|
||||||
|
const auto done = [=](Payments::CheckoutResult result) {
|
||||||
|
if (result == Payments::CheckoutResult::Cancelled) {
|
||||||
|
closeParentBox();
|
||||||
|
close();
|
||||||
|
} else if (result != Payments::CheckoutResult::Paid) {
|
||||||
|
state->sent = false;
|
||||||
|
} else {
|
||||||
|
show->showToast(u"done!"_q);
|
||||||
|
closeParentBox();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
BuyResaleGift(show, to, gift, done);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ui::ConfirmBox(box, {
|
||||||
|
.text = to->isSelf()
|
||||||
|
? tr::lng_gift_buy_resale_confirm_self(
|
||||||
|
lt_name,
|
||||||
|
rpl::single(Ui::Text::Bold(UniqueGiftName(*gift))),
|
||||||
|
lt_price,
|
||||||
|
tr::lng_action_gift_for_stars(
|
||||||
|
lt_count,
|
||||||
|
rpl::single(gift->starsForResale * 1.),
|
||||||
|
Ui::Text::Bold),
|
||||||
|
Ui::Text::WithEntities)
|
||||||
|
: tr::lng_gift_buy_resale_confirm(
|
||||||
|
lt_name,
|
||||||
|
rpl::single(Ui::Text::Bold(UniqueGiftName(*gift))),
|
||||||
|
lt_price,
|
||||||
|
tr::lng_action_gift_for_stars(
|
||||||
|
lt_count,
|
||||||
|
rpl::single(gift->starsForResale * 1.),
|
||||||
|
Ui::Text::Bold),
|
||||||
|
lt_user,
|
||||||
|
rpl::single(Ui::Text::Bold(to->shortName())),
|
||||||
|
Ui::Text::WithEntities),
|
||||||
|
.confirmed = std::move(callback),
|
||||||
|
.confirmText = std::move(transfer),
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShowResaleGiftLater(
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
std::shared_ptr<Data::UniqueGift> gift) {
|
||||||
|
const auto now = base::unixtime::now();
|
||||||
|
if (gift->canResellAt <= now) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto seconds = gift->canResellAt - now;
|
||||||
|
const auto days = seconds / 86400;
|
||||||
|
const auto hours = seconds / 3600;
|
||||||
|
const auto minutes = std::max(seconds / 60, 1);
|
||||||
|
show->showToast({
|
||||||
|
.title = tr::lng_gift_resale_transfer_early_title(tr::now),
|
||||||
|
.text = { tr::lng_gift_resale_early(tr::now, lt_duration, days
|
||||||
|
? tr::lng_days(tr::now, lt_count, days)
|
||||||
|
: hours
|
||||||
|
? tr::lng_hours(tr::now, lt_count, hours)
|
||||||
|
: tr::lng_minutes(tr::now, lt_count, minutes)) },
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShowTransferGiftLater(
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
std::shared_ptr<Data::UniqueGift> gift) {
|
||||||
|
const auto seconds = gift->canTransferAt - base::unixtime::now();
|
||||||
|
if (seconds <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto days = seconds / 86400;
|
||||||
|
const auto hours = seconds / 3600;
|
||||||
|
const auto minutes = std::max(seconds / 60, 1);
|
||||||
|
show->showToast({
|
||||||
|
.title = tr::lng_gift_resale_transfer_early_title(tr::now),
|
||||||
|
.text = { tr::lng_gift_transfer_early(tr::now, lt_duration, days
|
||||||
|
? tr::lng_days(tr::now, lt_count, days)
|
||||||
|
: hours
|
||||||
|
? tr::lng_hours(tr::now, lt_count, hours)
|
||||||
|
: tr::lng_minutes(tr::now, lt_count, minutes)) },
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,10 @@ namespace Window {
|
||||||
class SessionController;
|
class SessionController;
|
||||||
} // namespace Window
|
} // namespace Window
|
||||||
|
|
||||||
|
namespace ChatHelpers {
|
||||||
|
class Show;
|
||||||
|
} // namespace ChatHelpers
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
struct UniqueGift;
|
struct UniqueGift;
|
||||||
class SavedStarGiftId;
|
class SavedStarGiftId;
|
||||||
|
@ -27,3 +31,16 @@ void ShowTransferGiftBox(
|
||||||
not_null<Window::SessionController*> window,
|
not_null<Window::SessionController*> window,
|
||||||
std::shared_ptr<Data::UniqueGift> gift,
|
std::shared_ptr<Data::UniqueGift> gift,
|
||||||
Data::SavedStarGiftId savedId);
|
Data::SavedStarGiftId savedId);
|
||||||
|
|
||||||
|
void ShowBuyResaleGiftBox(
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
std::shared_ptr<Data::UniqueGift> gift,
|
||||||
|
not_null<PeerData*> to,
|
||||||
|
Fn<void()> closeParentBox);
|
||||||
|
|
||||||
|
bool ShowResaleGiftLater(
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
std::shared_ptr<Data::UniqueGift> gift);
|
||||||
|
bool ShowTransferGiftLater(
|
||||||
|
std::shared_ptr<ChatHelpers::Show> show,
|
||||||
|
std::shared_ptr<Data::UniqueGift> gift);
|
||||||
|
|
|
@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_group_call.h"
|
#include "data/data_group_call.h"
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "boxes/abstract_box.h"
|
#include "boxes/abstract_box.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
|
@ -580,28 +581,33 @@ void TopBar::initBlobsUnder(
|
||||||
|
|
||||||
void TopBar::subscribeToMembersChanges(not_null<GroupCall*> call) {
|
void TopBar::subscribeToMembersChanges(not_null<GroupCall*> call) {
|
||||||
const auto peer = call->peer();
|
const auto peer = call->peer();
|
||||||
peer->session().changes().peerFlagsValue(
|
const auto group = _groupCall.get();
|
||||||
peer,
|
const auto conference = group && group->conference();
|
||||||
Data::PeerUpdate::Flag::GroupCall
|
auto realValue = conference
|
||||||
) | rpl::map([=] {
|
? (rpl::single(group->conferenceCall().get()) | rpl::type_erased())
|
||||||
return peer->groupCall();
|
: peer->session().changes().peerFlagsValue(
|
||||||
}) | rpl::filter([=](Data::GroupCall *real) {
|
peer,
|
||||||
const auto call = _groupCall.get();
|
Data::PeerUpdate::Flag::GroupCall
|
||||||
return call && real && (real->id() == call->id());
|
) | rpl::map([=] {
|
||||||
}) | rpl::take(
|
return peer->groupCall();
|
||||||
1
|
}) | rpl::filter([=](Data::GroupCall *real) {
|
||||||
|
const auto call = _groupCall.get();
|
||||||
|
return call && real && (real->id() == call->id());
|
||||||
|
}) | rpl::take(1);
|
||||||
|
std::move(
|
||||||
|
realValue
|
||||||
) | rpl::before_next([=](not_null<Data::GroupCall*> real) {
|
) | rpl::before_next([=](not_null<Data::GroupCall*> real) {
|
||||||
real->titleValue() | rpl::start_with_next([=] {
|
real->titleValue() | rpl::start_with_next([=] {
|
||||||
updateInfoLabels();
|
updateInfoLabels();
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
}) | rpl::map([=](not_null<Data::GroupCall*> real) {
|
}) | rpl::map([=](not_null<Data::GroupCall*> real) {
|
||||||
|
|
||||||
return HistoryView::GroupCallBarContentByCall(
|
return HistoryView::GroupCallBarContentByCall(
|
||||||
real,
|
real,
|
||||||
st::groupCallTopBarUserpics.size);
|
st::groupCallTopBarUserpics.size);
|
||||||
}) | rpl::flatten_latest(
|
}) | rpl::flatten_latest(
|
||||||
) | rpl::filter([=](const Ui::GroupCallBarContent &content) {
|
) | rpl::filter([=](const Ui::GroupCallBarContent &content) {
|
||||||
if (_users.size() != content.users.size()) {
|
if (_users.size() != content.users.size()
|
||||||
|
|| (conference && _usersCount != content.count)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
for (auto i = 0, count = int(_users.size()); i != count; ++i) {
|
for (auto i = 0, count = int(_users.size()); i != count; ++i) {
|
||||||
|
@ -613,10 +619,14 @@ void TopBar::subscribeToMembersChanges(not_null<GroupCall*> call) {
|
||||||
return false;
|
return false;
|
||||||
}) | rpl::start_with_next([=](const Ui::GroupCallBarContent &content) {
|
}) | rpl::start_with_next([=](const Ui::GroupCallBarContent &content) {
|
||||||
_users = content.users;
|
_users = content.users;
|
||||||
|
_usersCount = content.count;
|
||||||
for (auto &user : _users) {
|
for (auto &user : _users) {
|
||||||
user.speaking = false;
|
user.speaking = false;
|
||||||
}
|
}
|
||||||
_userpics->update(_users, !isHidden());
|
_userpics->update(_users, !isHidden());
|
||||||
|
if (conference) {
|
||||||
|
updateInfoLabels();
|
||||||
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_userpics->widthValue(
|
_userpics->widthValue(
|
||||||
|
@ -655,14 +665,62 @@ void TopBar::setInfoLabels() {
|
||||||
} else if (const auto group = _groupCall.get()) {
|
} else if (const auto group = _groupCall.get()) {
|
||||||
const auto peer = group->peer();
|
const auto peer = group->peer();
|
||||||
const auto real = peer->groupCall();
|
const auto real = peer->groupCall();
|
||||||
const auto name = peer->name();
|
const auto connecting = _isGroupConnecting.current();
|
||||||
const auto text = _isGroupConnecting.current()
|
if (!group->conference()) {
|
||||||
? tr::lng_group_call_connecting(tr::now)
|
_shortInfoLabel.destroy();
|
||||||
: (real && real->id() == group->id() && !real->title().isEmpty())
|
}
|
||||||
? real->title()
|
if (!group->conference() || connecting) {
|
||||||
: name;
|
const auto name = peer->name();
|
||||||
_fullInfoLabel->setText(text);
|
const auto title = (real && real->id() == group->id())
|
||||||
_shortInfoLabel->setText(text);
|
? real->title()
|
||||||
|
: QString();
|
||||||
|
const auto text = _isGroupConnecting.current()
|
||||||
|
? tr::lng_group_call_connecting(tr::now)
|
||||||
|
: !title.isEmpty()
|
||||||
|
? title
|
||||||
|
: name;
|
||||||
|
_fullInfoLabel->setText(text);
|
||||||
|
if (_shortInfoLabel) {
|
||||||
|
_shortInfoLabel->setText(text);
|
||||||
|
}
|
||||||
|
} else if (!_usersCount
|
||||||
|
|| _users.empty()
|
||||||
|
|| (_users.size() == 1
|
||||||
|
&& _users.front().id == peer->session().userPeerId().value
|
||||||
|
&& _usersCount == 1)) {
|
||||||
|
_fullInfoLabel->setText(tr::lng_confcall_join_title(tr::now));
|
||||||
|
_shortInfoLabel->setText(tr::lng_confcall_join_title(tr::now));
|
||||||
|
} else {
|
||||||
|
const auto textWithUserpics = [&](int userpics) {
|
||||||
|
const auto other = std::max(_usersCount - userpics, 0);
|
||||||
|
auto names = QStringList();
|
||||||
|
for (const auto &entry : _users) {
|
||||||
|
const auto user = peer->owner().peer(PeerId(entry.id));
|
||||||
|
names.push_back(user->shortName());
|
||||||
|
if (names.size() >= userpics) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (other > 0) {
|
||||||
|
return tr::lng_forwarding_from(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
other,
|
||||||
|
lt_user,
|
||||||
|
names.join(u", "_q));
|
||||||
|
} else if (userpics > 1) {
|
||||||
|
return tr::lng_forwarding_from_two(
|
||||||
|
tr::now,
|
||||||
|
lt_user,
|
||||||
|
names.mid(0, userpics - 1).join(u", "_q),
|
||||||
|
lt_second_user,
|
||||||
|
names.back());
|
||||||
|
}
|
||||||
|
return names.back();
|
||||||
|
};
|
||||||
|
_fullInfoLabel->setText(textWithUserpics(int(_users.size())));
|
||||||
|
_shortInfoLabel->setText(textWithUserpics(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -732,10 +790,8 @@ void TopBar::updateControlsGeometry() {
|
||||||
height());
|
height());
|
||||||
|
|
||||||
auto fullWidth = _fullInfoLabel->textMaxWidth();
|
auto fullWidth = _fullInfoLabel->textMaxWidth();
|
||||||
auto showFull = (left + fullWidth + right <= width());
|
auto showFull = !_shortInfoLabel
|
||||||
_fullInfoLabel->setVisible(showFull);
|
|| (left + fullWidth + right <= width());
|
||||||
_shortInfoLabel->setVisible(!showFull);
|
|
||||||
|
|
||||||
auto setInfoLabelGeometry = [this, left, right](auto &&infoLabel) {
|
auto setInfoLabelGeometry = [this, left, right](auto &&infoLabel) {
|
||||||
auto minPadding = qMax(left, right);
|
auto minPadding = qMax(left, right);
|
||||||
auto infoWidth = infoLabel->textMaxWidth();
|
auto infoWidth = infoLabel->textMaxWidth();
|
||||||
|
@ -746,8 +802,13 @@ void TopBar::updateControlsGeometry() {
|
||||||
}
|
}
|
||||||
infoLabel->setGeometryToLeft(infoLeft, st::callBarLabelTop, infoWidth, st::callBarInfoLabel.style.font->height);
|
infoLabel->setGeometryToLeft(infoLeft, st::callBarLabelTop, infoWidth, st::callBarInfoLabel.style.font->height);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_fullInfoLabel->setVisible(showFull);
|
||||||
setInfoLabelGeometry(_fullInfoLabel);
|
setInfoLabelGeometry(_fullInfoLabel);
|
||||||
setInfoLabelGeometry(_shortInfoLabel);
|
if (_shortInfoLabel) {
|
||||||
|
_shortInfoLabel->setVisible(!showFull);
|
||||||
|
setInfoLabelGeometry(_shortInfoLabel);
|
||||||
|
}
|
||||||
|
|
||||||
_gradients.set_points(
|
_gradients.set_points(
|
||||||
QPointF(0, st::callBarHeight / 2),
|
QPointF(0, st::callBarHeight / 2),
|
||||||
|
|
|
@ -85,6 +85,7 @@ private:
|
||||||
|
|
||||||
bool _muted = false;
|
bool _muted = false;
|
||||||
std::vector<Ui::GroupCallUser> _users;
|
std::vector<Ui::GroupCallUser> _users;
|
||||||
|
int _usersCount = 0;
|
||||||
std::unique_ptr<Ui::GroupCallUserpics> _userpics;
|
std::unique_ptr<Ui::GroupCallUserpics> _userpics;
|
||||||
int _userpicsWidth = 0;
|
int _userpicsWidth = 0;
|
||||||
object_ptr<Ui::LabelSimple> _durationLabel;
|
object_ptr<Ui::LabelSimple> _durationLabel;
|
||||||
|
@ -99,6 +100,9 @@ private:
|
||||||
|
|
||||||
rpl::variable<bool> _isGroupConnecting = false;
|
rpl::variable<bool> _isGroupConnecting = false;
|
||||||
|
|
||||||
|
std::vector<not_null<PeerData*>> _conferenceFirstUsers;
|
||||||
|
int _conferenceUsersCount = 0;
|
||||||
|
|
||||||
QBrush _groupBrush;
|
QBrush _groupBrush;
|
||||||
anim::linear_gradients<BarState> _gradients;
|
anim::linear_gradients<BarState> _gradients;
|
||||||
Ui::Animations::Simple _switchStateAnimation;
|
Ui::Animations::Simple _switchStateAnimation;
|
||||||
|
|
|
@ -332,7 +332,7 @@ QString ImagesOrAllFilter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PhotoVideoFilesFilter() {
|
QString PhotoVideoFilesFilter() {
|
||||||
return u"Image and Video Files (*"_q + Ui::ImageExtensions().join(u" *"_q) + u" *.m4v);;"_q
|
return u"Image and Video Files (*"_q + Ui::ImageExtensions().join(u" *"_q) + u" *.mp4 *.mov *.m4v);;"_q
|
||||||
+ AllFilesFilter();
|
+ AllFilesFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1932,7 +1932,7 @@ void ResolveAndShowUniqueGift(
|
||||||
session->data().processUsers(data.vusers());
|
session->data().processUsers(data.vusers());
|
||||||
if (const auto gift = Api::FromTL(session, data.vgift())) {
|
if (const auto gift = Api::FromTL(session, data.vgift())) {
|
||||||
using namespace ::Settings;
|
using namespace ::Settings;
|
||||||
show->show(Box(GlobalStarGiftBox, show, *gift, st));
|
show->show(Box(GlobalStarGiftBox, show, *gift, PeerId(), st));
|
||||||
}
|
}
|
||||||
}).fail([=](const MTP::Error &error) {
|
}).fail([=](const MTP::Error &error) {
|
||||||
clear();
|
clear();
|
||||||
|
|
|
@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
|
||||||
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
||||||
constexpr auto AppName = "AyuGram Desktop"_cs;
|
constexpr auto AppName = "AyuGram Desktop"_cs;
|
||||||
constexpr auto AppFile = "AyuGram"_cs;
|
constexpr auto AppFile = "AyuGram"_cs;
|
||||||
constexpr auto AppVersion = 5014001;
|
constexpr auto AppVersion = 5014003;
|
||||||
constexpr auto AppVersionStr = "5.14.1";
|
constexpr auto AppVersionStr = "5.14.3";
|
||||||
constexpr auto AppBetaVersion = false;
|
constexpr auto AppBetaVersion = false;
|
||||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||||
|
|
|
@ -135,10 +135,17 @@ void Credits::apply(StarsAmount balance) {
|
||||||
|
|
||||||
void Credits::apply(PeerId peerId, StarsAmount balance) {
|
void Credits::apply(PeerId peerId, StarsAmount balance) {
|
||||||
_cachedPeerBalances[peerId] = balance;
|
_cachedPeerBalances[peerId] = balance;
|
||||||
|
_refreshedByPeerId.fire_copy(peerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Credits::applyCurrency(PeerId peerId, uint64 balance) {
|
void Credits::applyCurrency(PeerId peerId, uint64 balance) {
|
||||||
_cachedPeerCurrencyBalances[peerId] = balance;
|
_cachedPeerCurrencyBalances[peerId] = balance;
|
||||||
|
_refreshedByPeerId.fire_copy(peerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> Credits::refreshedByPeerId(PeerId peerId) {
|
||||||
|
return _refreshedByPeerId.events(
|
||||||
|
) | rpl::filter(rpl::mappers::_1 == peerId) | rpl::to_empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
|
@ -37,6 +37,8 @@ public:
|
||||||
[[nodiscard]] rpl::producer<float64> rateValue(
|
[[nodiscard]] rpl::producer<float64> rateValue(
|
||||||
not_null<PeerData*> ownedBotOrChannel);
|
not_null<PeerData*> ownedBotOrChannel);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<> refreshedByPeerId(PeerId peerId);
|
||||||
|
|
||||||
void applyCurrency(PeerId peerId, uint64 balance);
|
void applyCurrency(PeerId peerId, uint64 balance);
|
||||||
[[nodiscard]] uint64 balanceCurrency(PeerId peerId) const;
|
[[nodiscard]] uint64 balanceCurrency(PeerId peerId) const;
|
||||||
|
|
||||||
|
@ -64,6 +66,8 @@ private:
|
||||||
crl::time _lastLoaded = 0;
|
crl::time _lastLoaded = 0;
|
||||||
float64 _rate = 0.;
|
float64 _rate = 0.;
|
||||||
|
|
||||||
|
rpl::event_stream<PeerId> _refreshedByPeerId;
|
||||||
|
|
||||||
SingleQueuedInvokation _reload;
|
SingleQueuedInvokation _reload;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
307
Telegram/SourceFiles/data/components/promo_suggestions.cpp
Normal file
|
@ -0,0 +1,307 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "data/components/promo_suggestions.h"
|
||||||
|
|
||||||
|
#include "api/api_text_entities.h"
|
||||||
|
#include "apiwrap.h"
|
||||||
|
#include "base/unixtime.h"
|
||||||
|
#include "core/application.h"
|
||||||
|
#include "core/core_settings.h"
|
||||||
|
#include "data/data_changes.h"
|
||||||
|
#include "data/data_histories.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "history/history.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using UserIds = std::vector<UserId>;
|
||||||
|
|
||||||
|
constexpr auto kTopPromotionInterval = TimeId(60 * 60);
|
||||||
|
constexpr auto kTopPromotionMinDelay = TimeId(10);
|
||||||
|
|
||||||
|
[[nodiscard]] CustomSuggestion CustomFromTL(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
const MTPPendingSuggestion &r) {
|
||||||
|
return CustomSuggestion({
|
||||||
|
.suggestion = qs(r.data().vsuggestion()),
|
||||||
|
.title = Api::ParseTextWithEntities(session, r.data().vtitle()),
|
||||||
|
.description = Api::ParseTextWithEntities(
|
||||||
|
session,
|
||||||
|
r.data().vdescription()),
|
||||||
|
.url = qs(r.data().vurl()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
PromoSuggestions::PromoSuggestions(not_null<Main::Session*> session)
|
||||||
|
: _session(session)
|
||||||
|
, _topPromotionTimer([=] { refreshTopPromotion(); }) {
|
||||||
|
Core::App().settings().proxy().connectionTypeValue(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
refreshTopPromotion();
|
||||||
|
}, _lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
PromoSuggestions::~PromoSuggestions() = default;
|
||||||
|
|
||||||
|
void PromoSuggestions::refreshTopPromotion() {
|
||||||
|
const auto now = base::unixtime::now();
|
||||||
|
const auto next = (_topPromotionNextRequestTime != 0)
|
||||||
|
? _topPromotionNextRequestTime
|
||||||
|
: now;
|
||||||
|
if (_topPromotionRequestId) {
|
||||||
|
topPromotionDelayed(now, next);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto key = [&]() -> std::pair<QString, uint32> {
|
||||||
|
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) {
|
||||||
|
topPromotionDelayed(now, next);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_topPromotionKey = key;
|
||||||
|
_topPromotionRequestId = _session->api().request(MTPhelp_GetPromoData(
|
||||||
|
)).done([=](const MTPhelp_PromoData &result) {
|
||||||
|
_topPromotionRequestId = 0;
|
||||||
|
|
||||||
|
_topPromotionNextRequestTime = result.match([&](const auto &data) {
|
||||||
|
return data.vexpires().v;
|
||||||
|
});
|
||||||
|
topPromotionDelayed(
|
||||||
|
base::unixtime::now(),
|
||||||
|
_topPromotionNextRequestTime);
|
||||||
|
|
||||||
|
result.match([&](const MTPDhelp_promoDataEmpty &data) {
|
||||||
|
setTopPromoted(nullptr, QString(), QString());
|
||||||
|
}, [&](const MTPDhelp_promoData &data) {
|
||||||
|
_session->data().processChats(data.vchats());
|
||||||
|
_session->data().processUsers(data.vusers());
|
||||||
|
|
||||||
|
auto changedPendingSuggestions = false;
|
||||||
|
auto pendingSuggestions = ranges::views::all(
|
||||||
|
data.vpending_suggestions().v
|
||||||
|
) | ranges::views::transform([](const auto &suggestion) {
|
||||||
|
return qs(suggestion);
|
||||||
|
}) | ranges::to_vector;
|
||||||
|
if (!ranges::equal(_pendingSuggestions, pendingSuggestions)) {
|
||||||
|
_pendingSuggestions = std::move(pendingSuggestions);
|
||||||
|
changedPendingSuggestions = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto changedDismissedSuggestions = false;
|
||||||
|
for (const auto &suggestion : data.vdismissed_suggestions().v) {
|
||||||
|
changedDismissedSuggestions
|
||||||
|
|= _dismissedSuggestions.emplace(qs(suggestion)).second;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto peer = data.vpeer()) {
|
||||||
|
const auto peerId = peerFromMTP(*peer);
|
||||||
|
const auto history = _session->data().history(peerId);
|
||||||
|
setTopPromoted(
|
||||||
|
history,
|
||||||
|
data.vpsa_type().value_or_empty(),
|
||||||
|
data.vpsa_message().value_or_empty());
|
||||||
|
} else {
|
||||||
|
setTopPromoted(nullptr, QString(), QString());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto changedCustom = false;
|
||||||
|
auto custom = data.vcustom_pending_suggestion()
|
||||||
|
? std::make_optional(
|
||||||
|
CustomFromTL(
|
||||||
|
_session,
|
||||||
|
*data.vcustom_pending_suggestion()))
|
||||||
|
: std::nullopt;
|
||||||
|
if (_custom != custom) {
|
||||||
|
_custom = std::move(custom);
|
||||||
|
changedCustom = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto changedContactBirthdaysLastDayRequest =
|
||||||
|
_contactBirthdaysLastDayRequest != -1
|
||||||
|
&& _contactBirthdaysLastDayRequest
|
||||||
|
!= QDate::currentDate().day();
|
||||||
|
|
||||||
|
if (changedPendingSuggestions
|
||||||
|
|| changedDismissedSuggestions
|
||||||
|
|| changedCustom
|
||||||
|
|| changedContactBirthdaysLastDayRequest) {
|
||||||
|
_refreshed.fire({});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).fail([=] {
|
||||||
|
_topPromotionRequestId = 0;
|
||||||
|
const auto now = base::unixtime::now();
|
||||||
|
const auto next = _topPromotionNextRequestTime = now
|
||||||
|
+ kTopPromotionInterval;
|
||||||
|
if (!_topPromotionTimer.isActive()) {
|
||||||
|
topPromotionDelayed(now, next);
|
||||||
|
}
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PromoSuggestions::topPromotionDelayed(TimeId now, TimeId next) {
|
||||||
|
_topPromotionTimer.callOnce(std::min(
|
||||||
|
std::max(next - now, kTopPromotionMinDelay),
|
||||||
|
kTopPromotionInterval) * crl::time(1000));
|
||||||
|
};
|
||||||
|
|
||||||
|
rpl::producer<> PromoSuggestions::value() const {
|
||||||
|
return _refreshed.events_starting_with({});
|
||||||
|
}
|
||||||
|
|
||||||
|
void PromoSuggestions::setTopPromoted(
|
||||||
|
History *promoted,
|
||||||
|
const QString &type,
|
||||||
|
const QString &message) {
|
||||||
|
const auto changed = (_topPromoted != promoted);
|
||||||
|
if (!changed
|
||||||
|
&& (!promoted || promoted->topPromotionMessage() == message)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
if (_topPromoted) {
|
||||||
|
_topPromoted->cacheTopPromotion(false, QString(), QString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto old = std::exchange(_topPromoted, promoted);
|
||||||
|
if (_topPromoted) {
|
||||||
|
_session->data().histories().requestDialogEntry(_topPromoted);
|
||||||
|
_topPromoted->cacheTopPromotion(true, type, message);
|
||||||
|
_topPromoted->requestChatListMessage();
|
||||||
|
_session->changes().historyUpdated(
|
||||||
|
_topPromoted,
|
||||||
|
HistoryUpdate::Flag::TopPromoted);
|
||||||
|
}
|
||||||
|
if (changed && old) {
|
||||||
|
_session->changes().historyUpdated(
|
||||||
|
old,
|
||||||
|
HistoryUpdate::Flag::TopPromoted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PromoSuggestions::current(const QString &key) const {
|
||||||
|
if (key == u"BIRTHDAY_CONTACTS_TODAY"_q) {
|
||||||
|
if (_dismissedSuggestions.contains(key)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
const auto known
|
||||||
|
= PromoSuggestions::knownBirthdaysToday();
|
||||||
|
if (!known) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !known->empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !_dismissedSuggestions.contains(key)
|
||||||
|
&& ranges::contains(_pendingSuggestions, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> PromoSuggestions::requested(const QString &key) const {
|
||||||
|
return value() | rpl::filter([=] { return current(key); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void PromoSuggestions::dismiss(const QString &key) {
|
||||||
|
if (!_dismissedSuggestions.emplace(key).second) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_session->api().request(MTPhelp_DismissSuggestion(
|
||||||
|
MTP_inputPeerEmpty(),
|
||||||
|
MTP_string(key)
|
||||||
|
)).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PromoSuggestions::invalidate() {
|
||||||
|
if (_topPromotionRequestId) {
|
||||||
|
_session->api().request(_topPromotionRequestId).cancel();
|
||||||
|
}
|
||||||
|
_topPromotionNextRequestTime = 0;
|
||||||
|
_topPromotionTimer.callOnce(crl::time(200));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<CustomSuggestion> PromoSuggestions::custom() const {
|
||||||
|
return _custom;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PromoSuggestions::requestContactBirthdays(Fn<void()> done, bool force) {
|
||||||
|
if ((_contactBirthdaysLastDayRequest != -1)
|
||||||
|
&& (_contactBirthdaysLastDayRequest == QDate::currentDate().day())
|
||||||
|
&& !force) {
|
||||||
|
return done();
|
||||||
|
}
|
||||||
|
if (_contactBirthdaysRequestId) {
|
||||||
|
_session->api().request(_contactBirthdaysRequestId).cancel();
|
||||||
|
}
|
||||||
|
_contactBirthdaysRequestId = _session->api().request(
|
||||||
|
MTPcontacts_GetBirthdays()
|
||||||
|
).done([=](const MTPcontacts_ContactBirthdays &result) {
|
||||||
|
_contactBirthdaysRequestId = 0;
|
||||||
|
_contactBirthdaysLastDayRequest = QDate::currentDate().day();
|
||||||
|
auto users = UserIds();
|
||||||
|
auto today = UserIds();
|
||||||
|
_session->data().processUsers(result.data().vusers());
|
||||||
|
for (const auto &tlContact : result.data().vcontacts().v) {
|
||||||
|
const auto peerId = tlContact.data().vcontact_id().v;
|
||||||
|
if (const auto user = _session->data().user(peerId)) {
|
||||||
|
const auto &data = tlContact.data().vbirthday().data();
|
||||||
|
user->setBirthday(Data::Birthday(
|
||||||
|
data.vday().v,
|
||||||
|
data.vmonth().v,
|
||||||
|
data.vyear().value_or_empty()));
|
||||||
|
if (user->isSelf()
|
||||||
|
|| user->isInaccessible()
|
||||||
|
|| user->isBlocked()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Data::IsBirthdayToday(user->birthday())) {
|
||||||
|
today.push_back(peerToUser(user->id));
|
||||||
|
}
|
||||||
|
users.push_back(peerToUser(user->id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_contactBirthdays = std::move(users);
|
||||||
|
_contactBirthdaysToday = std::move(today);
|
||||||
|
done();
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
_contactBirthdaysRequestId = 0;
|
||||||
|
_contactBirthdaysLastDayRequest = QDate::currentDate().day();
|
||||||
|
_contactBirthdays = {};
|
||||||
|
_contactBirthdaysToday = {};
|
||||||
|
done();
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<UserIds> PromoSuggestions::knownContactBirthdays() const {
|
||||||
|
if ((_contactBirthdaysLastDayRequest == -1)
|
||||||
|
|| (_contactBirthdaysLastDayRequest != QDate::currentDate().day())) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return _contactBirthdays;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<UserIds> PromoSuggestions::knownBirthdaysToday() const {
|
||||||
|
if ((_contactBirthdaysLastDayRequest == -1)
|
||||||
|
|| (_contactBirthdaysLastDayRequest != QDate::currentDate().day())) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return _contactBirthdaysToday;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Data
|
85
Telegram/SourceFiles/data/components/promo_suggestions.h
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/timer.h"
|
||||||
|
|
||||||
|
class History;
|
||||||
|
|
||||||
|
namespace Main {
|
||||||
|
class Session;
|
||||||
|
} // namespace Main
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
|
||||||
|
struct CustomSuggestion final {
|
||||||
|
QString suggestion;
|
||||||
|
TextWithEntities title;
|
||||||
|
TextWithEntities description;
|
||||||
|
QString url;
|
||||||
|
|
||||||
|
friend inline auto operator<=>(
|
||||||
|
const CustomSuggestion &,
|
||||||
|
const CustomSuggestion &) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PromoSuggestions final {
|
||||||
|
public:
|
||||||
|
explicit PromoSuggestions(not_null<Main::Session*> session);
|
||||||
|
~PromoSuggestions();
|
||||||
|
|
||||||
|
[[nodiscard]] bool current(const QString &key) const;
|
||||||
|
[[nodiscard]] std::optional<CustomSuggestion> custom() const;
|
||||||
|
[[nodiscard]] rpl::producer<> requested(const QString &key) const;
|
||||||
|
void dismiss(const QString &key);
|
||||||
|
|
||||||
|
void refreshTopPromotion();
|
||||||
|
|
||||||
|
void invalidate();
|
||||||
|
|
||||||
|
rpl::producer<> value() const;
|
||||||
|
// Create rpl::producer<> refreshed() const; on memand.
|
||||||
|
|
||||||
|
void requestContactBirthdays(Fn<void()> done, bool force = false);
|
||||||
|
[[nodiscard]] auto knownContactBirthdays() const
|
||||||
|
-> std::optional<std::vector<UserId>>;
|
||||||
|
[[nodiscard]] auto knownBirthdaysToday() const
|
||||||
|
-> std::optional<std::vector<UserId>>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setTopPromoted(
|
||||||
|
History *promoted,
|
||||||
|
const QString &type,
|
||||||
|
const QString &message);
|
||||||
|
|
||||||
|
void topPromotionDelayed(TimeId now, TimeId next);
|
||||||
|
|
||||||
|
const not_null<Main::Session*> _session;
|
||||||
|
base::flat_set<QString> _dismissedSuggestions;
|
||||||
|
std::vector<QString> _pendingSuggestions;
|
||||||
|
std::optional<CustomSuggestion> _custom;
|
||||||
|
|
||||||
|
History *_topPromoted = nullptr;
|
||||||
|
|
||||||
|
mtpRequestId _contactBirthdaysRequestId = 0;
|
||||||
|
int _contactBirthdaysLastDayRequest = -1;
|
||||||
|
std::vector<UserId> _contactBirthdays;
|
||||||
|
std::vector<UserId> _contactBirthdaysToday;
|
||||||
|
|
||||||
|
mtpRequestId _topPromotionRequestId = 0;
|
||||||
|
std::pair<QString, uint32> _topPromotionKey;
|
||||||
|
TimeId _topPromotionNextRequestTime = TimeId(0);
|
||||||
|
base::Timer _topPromotionTimer;
|
||||||
|
|
||||||
|
rpl::event_stream<> _refreshed;
|
||||||
|
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Data
|
|
@ -111,7 +111,7 @@ struct PeerUpdate {
|
||||||
ChannelAmIn = (1ULL << 45),
|
ChannelAmIn = (1ULL << 45),
|
||||||
StickersSet = (1ULL << 46),
|
StickersSet = (1ULL << 46),
|
||||||
EmojiSet = (1ULL << 47),
|
EmojiSet = (1ULL << 47),
|
||||||
ChannelLinkedChat = (1ULL << 48),
|
DiscussionLink = (1ULL << 48),
|
||||||
ChannelLocation = (1ULL << 49),
|
ChannelLocation = (1ULL << 49),
|
||||||
Slowmode = (1ULL << 50),
|
Slowmode = (1ULL << 50),
|
||||||
GroupCall = (1ULL << 51),
|
GroupCall = (1ULL << 51),
|
||||||
|
|
|
@ -7,7 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
|
|
||||||
|
#include "api/api_credits.h"
|
||||||
#include "api/api_global_privacy.h"
|
#include "api/api_global_privacy.h"
|
||||||
|
#include "api/api_statistics.h"
|
||||||
|
#include "base/timer_rpl.h"
|
||||||
#include "data/components/credits.h"
|
#include "data/components/credits.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_channel_admins.h"
|
#include "data/data_channel_admins.h"
|
||||||
|
@ -272,22 +275,22 @@ const ChannelLocation *ChannelData::getLocation() const {
|
||||||
return mgInfo ? mgInfo->getLocation() : nullptr;
|
return mgInfo ? mgInfo->getLocation() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChannelData::setLinkedChat(ChannelData *linked) {
|
void ChannelData::setDiscussionLink(ChannelData *linked) {
|
||||||
if (_linkedChat != linked) {
|
if (_discussionLink != linked) {
|
||||||
_linkedChat = linked;
|
_discussionLink = linked;
|
||||||
if (const auto history = owner().historyLoaded(this)) {
|
if (const auto history = owner().historyLoaded(this)) {
|
||||||
history->forceFullResize();
|
history->forceFullResize();
|
||||||
}
|
}
|
||||||
session().changes().peerUpdated(this, UpdateFlag::ChannelLinkedChat);
|
session().changes().peerUpdated(this, UpdateFlag::DiscussionLink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChannelData *ChannelData::linkedChat() const {
|
ChannelData *ChannelData::discussionLink() const {
|
||||||
return _linkedChat.value_or(nullptr);
|
return _discussionLink.value_or(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChannelData::linkedChatKnown() const {
|
bool ChannelData::discussionLinkKnown() const {
|
||||||
return _linkedChat.has_value();
|
return _discussionLink.has_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChannelData::setMembersCount(int newMembersCount) {
|
void ChannelData::setMembersCount(int newMembersCount) {
|
||||||
|
@ -649,7 +652,11 @@ bool ChannelData::canEditPermissions() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChannelData::canEditSignatures() const {
|
bool ChannelData::canEditSignatures() const {
|
||||||
return isChannel() && canEditInformation();
|
return isBroadcast() && canEditInformation();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChannelData::canEditAutoTranslate() const {
|
||||||
|
return isBroadcast() && canEditInformation();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChannelData::canEditPreHistoryHidden() const {
|
bool ChannelData::canEditPreHistoryHidden() const {
|
||||||
|
@ -1229,9 +1236,9 @@ void ApplyChannelUpdate(
|
||||||
channel->setLocation(MTP_channelLocationEmpty());
|
channel->setLocation(MTP_channelLocationEmpty());
|
||||||
}
|
}
|
||||||
if (const auto chat = update.vlinked_chat_id()) {
|
if (const auto chat = update.vlinked_chat_id()) {
|
||||||
channel->setLinkedChat(channel->owner().channelLoaded(chat->v));
|
channel->setDiscussionLink(channel->owner().channelLoaded(chat->v));
|
||||||
} else {
|
} else {
|
||||||
channel->setLinkedChat(nullptr);
|
channel->setDiscussionLink(nullptr);
|
||||||
}
|
}
|
||||||
if (const auto history = channel->owner().historyLoaded(channel)) {
|
if (const auto history = channel->owner().historyLoaded(channel)) {
|
||||||
if (const auto available = update.vavailable_min_id()) {
|
if (const auto available = update.vavailable_min_id()) {
|
||||||
|
@ -1353,6 +1360,41 @@ void ApplyChannelUpdate(
|
||||||
channel->setWallPaper({});
|
channel->setWallPaper({});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((channel->flags() & Flag::CanViewRevenue)
|
||||||
|
|| (channel->flags() & Flag::CanViewCreditsRevenue)) {
|
||||||
|
static constexpr auto kTimeout = crl::time(60000);
|
||||||
|
const auto id = channel->id;
|
||||||
|
const auto weak = base::make_weak(&channel->session());
|
||||||
|
const auto creditsLoadLifetime = std::make_shared<rpl::lifetime>();
|
||||||
|
const auto creditsLoad
|
||||||
|
= creditsLoadLifetime->make_state<Api::CreditsStatus>(channel);
|
||||||
|
creditsLoad->request({}, [=](Data::CreditsStatusSlice slice) {
|
||||||
|
if (const auto strong = weak.get()) {
|
||||||
|
strong->credits().apply(id, slice.balance);
|
||||||
|
}
|
||||||
|
creditsLoadLifetime->destroy();
|
||||||
|
});
|
||||||
|
base::timer_once(kTimeout) | rpl::start_with_next([=] {
|
||||||
|
creditsLoadLifetime->destroy();
|
||||||
|
}, *creditsLoadLifetime);
|
||||||
|
const auto currencyLoadLifetime = std::make_shared<rpl::lifetime>();
|
||||||
|
const auto currencyLoad
|
||||||
|
= currencyLoadLifetime->make_state<Api::EarnStatistics>(channel);
|
||||||
|
const auto apply = [=](Data::EarnInt balance) {
|
||||||
|
if (const auto strong = weak.get()) {
|
||||||
|
strong->credits().applyCurrency(id, balance);
|
||||||
|
}
|
||||||
|
currencyLoadLifetime->destroy();
|
||||||
|
};
|
||||||
|
currencyLoad->request() | rpl::start_with_error_done(
|
||||||
|
[=](const QString &error) { apply(0); },
|
||||||
|
[=] { apply(currencyLoad->data().currentBalance); },
|
||||||
|
*currencyLoadLifetime);
|
||||||
|
base::timer_once(kTimeout) | rpl::start_with_next([=] {
|
||||||
|
currencyLoadLifetime->destroy();
|
||||||
|
}, *currencyLoadLifetime);
|
||||||
|
}
|
||||||
|
|
||||||
// For clearUpTill() call.
|
// For clearUpTill() call.
|
||||||
channel->owner().sendHistoryChangeNotifications();
|
channel->owner().sendHistoryChangeNotifications();
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ enum class ChannelDataFlag : uint64 {
|
||||||
SignatureProfiles = (1ULL << 35),
|
SignatureProfiles = (1ULL << 35),
|
||||||
StargiftsAvailable = (1ULL << 36),
|
StargiftsAvailable = (1ULL << 36),
|
||||||
PaidMessagesAvailable = (1ULL << 37),
|
PaidMessagesAvailable = (1ULL << 37),
|
||||||
|
AutoTranslation = (1ULL << 38),
|
||||||
};
|
};
|
||||||
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
|
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
|
||||||
using ChannelDataFlags = base::flags<ChannelDataFlag>;
|
using ChannelDataFlags = base::flags<ChannelDataFlag>;
|
||||||
|
@ -321,6 +322,9 @@ public:
|
||||||
[[nodiscard]] bool antiSpamMode() const {
|
[[nodiscard]] bool antiSpamMode() const {
|
||||||
return flags() & Flag::AntiSpam;
|
return flags() & Flag::AntiSpam;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] bool autoTranslation() const {
|
||||||
|
return flags() & Flag::AutoTranslation;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] auto adminRights() const {
|
[[nodiscard]] auto adminRights() const {
|
||||||
return _adminRights.current();
|
return _adminRights.current();
|
||||||
|
@ -382,6 +386,7 @@ public:
|
||||||
[[nodiscard]] bool canViewAdmins() const;
|
[[nodiscard]] bool canViewAdmins() const;
|
||||||
[[nodiscard]] bool canViewBanned() const;
|
[[nodiscard]] bool canViewBanned() const;
|
||||||
[[nodiscard]] bool canEditSignatures() const;
|
[[nodiscard]] bool canEditSignatures() const;
|
||||||
|
[[nodiscard]] bool canEditAutoTranslate() const;
|
||||||
[[nodiscard]] bool canEditStickers() const;
|
[[nodiscard]] bool canEditStickers() const;
|
||||||
[[nodiscard]] bool canEditEmoji() const;
|
[[nodiscard]] bool canEditEmoji() const;
|
||||||
[[nodiscard]] bool canDelete() const;
|
[[nodiscard]] bool canDelete() const;
|
||||||
|
@ -404,9 +409,9 @@ public:
|
||||||
void setLocation(const MTPChannelLocation &data);
|
void setLocation(const MTPChannelLocation &data);
|
||||||
[[nodiscard]] const ChannelLocation *getLocation() const;
|
[[nodiscard]] const ChannelLocation *getLocation() const;
|
||||||
|
|
||||||
void setLinkedChat(ChannelData *linked);
|
void setDiscussionLink(ChannelData *link);
|
||||||
[[nodiscard]] ChannelData *linkedChat() const;
|
[[nodiscard]] ChannelData *discussionLink() const;
|
||||||
[[nodiscard]] bool linkedChatKnown() const;
|
[[nodiscard]] bool discussionLinkKnown() const;
|
||||||
|
|
||||||
void ptsInit(int32 pts) {
|
void ptsInit(int32 pts) {
|
||||||
_ptsWaiter.init(pts);
|
_ptsWaiter.init(pts);
|
||||||
|
@ -565,7 +570,7 @@ private:
|
||||||
std::vector<Data::UnavailableReason> _unavailableReasons;
|
std::vector<Data::UnavailableReason> _unavailableReasons;
|
||||||
std::unique_ptr<InvitePeek> _invitePeek;
|
std::unique_ptr<InvitePeek> _invitePeek;
|
||||||
QString _inviteLink;
|
QString _inviteLink;
|
||||||
std::optional<ChannelData*> _linkedChat;
|
std::optional<ChannelData*> _discussionLink;
|
||||||
|
|
||||||
Data::AllowedReactions _allowedReactions;
|
Data::AllowedReactions _allowedReactions;
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,7 @@ struct CreditsHistoryEntry final {
|
||||||
uint64 bareGiveawayMsgId = 0;
|
uint64 bareGiveawayMsgId = 0;
|
||||||
uint64 bareGiftStickerId = 0;
|
uint64 bareGiftStickerId = 0;
|
||||||
uint64 bareGiftOwnerId = 0;
|
uint64 bareGiftOwnerId = 0;
|
||||||
|
uint64 bareGiftResaleRecipientId = 0;
|
||||||
uint64 bareActorId = 0;
|
uint64 bareActorId = 0;
|
||||||
uint64 bareEntryOwnerId = 0;
|
uint64 bareEntryOwnerId = 0;
|
||||||
uint64 giftChannelSavedId = 0;
|
uint64 giftChannelSavedId = 0;
|
||||||
|
@ -94,6 +95,7 @@ struct CreditsHistoryEntry final {
|
||||||
bool giftTransferred : 1 = false;
|
bool giftTransferred : 1 = false;
|
||||||
bool giftRefunded : 1 = false;
|
bool giftRefunded : 1 = false;
|
||||||
bool giftUpgraded : 1 = false;
|
bool giftUpgraded : 1 = false;
|
||||||
|
bool giftResale : 1 = false;
|
||||||
bool giftPinned : 1 = false;
|
bool giftPinned : 1 = false;
|
||||||
bool savedToProfile : 1 = false;
|
bool savedToProfile : 1 = false;
|
||||||
bool fromGiftsList : 1 = false;
|
bool fromGiftsList : 1 = false;
|
||||||
|
|
|
@ -704,6 +704,13 @@ bool PeerData::canExportChatHistory() const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PeerData::autoTranslation() const {
|
||||||
|
if (const auto channel = asChannel()) {
|
||||||
|
return channel->autoTranslation();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool PeerData::setAbout(const QString &newAbout) {
|
bool PeerData::setAbout(const QString &newAbout) {
|
||||||
if (_about == newAbout) {
|
if (_about == newAbout) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -396,6 +396,7 @@ public:
|
||||||
[[nodiscard]] bool canManageGifts() const;
|
[[nodiscard]] bool canManageGifts() const;
|
||||||
[[nodiscard]] bool canTransferGifts() const;
|
[[nodiscard]] bool canTransferGifts() const;
|
||||||
[[nodiscard]] bool canExportChatHistory() const;
|
[[nodiscard]] bool canExportChatHistory() const;
|
||||||
|
[[nodiscard]] bool autoTranslation() const;
|
||||||
|
|
||||||
// Returns true if about text was changed.
|
// Returns true if about text was changed.
|
||||||
bool setAbout(const QString &newAbout);
|
bool setAbout(const QString &newAbout);
|
||||||
|
|
|
@ -262,6 +262,12 @@ int LevelLimits::channelRestrictSponsoredLevelMin() const {
|
||||||
20);
|
20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int LevelLimits::channelAutoTranslateLevelMin() const {
|
||||||
|
return _session->appConfig().get<int>(
|
||||||
|
u"channel_autotranslation_level_min"_q,
|
||||||
|
3);
|
||||||
|
}
|
||||||
|
|
||||||
int LevelLimits::groupTranscribeLevelMin() const {
|
int LevelLimits::groupTranscribeLevelMin() const {
|
||||||
return _session->appConfig().get<int>(
|
return _session->appConfig().get<int>(
|
||||||
u"group_transcribe_level_min"_q,
|
u"group_transcribe_level_min"_q,
|
||||||
|
|
|
@ -102,6 +102,7 @@ public:
|
||||||
[[nodiscard]] int channelWallpaperLevelMin() const;
|
[[nodiscard]] int channelWallpaperLevelMin() const;
|
||||||
[[nodiscard]] int channelCustomWallpaperLevelMin() const;
|
[[nodiscard]] int channelCustomWallpaperLevelMin() const;
|
||||||
[[nodiscard]] int channelRestrictSponsoredLevelMin() const;
|
[[nodiscard]] int channelRestrictSponsoredLevelMin() const;
|
||||||
|
[[nodiscard]] int channelAutoTranslateLevelMin() const;
|
||||||
[[nodiscard]] int groupTranscribeLevelMin() const;
|
[[nodiscard]] int groupTranscribeLevelMin() const;
|
||||||
[[nodiscard]] int groupEmojiStickersLevelMin() const;
|
[[nodiscard]] int groupEmojiStickersLevelMin() const;
|
||||||
[[nodiscard]] int groupProfileBgIconLevelMin() const;
|
[[nodiscard]] int groupProfileBgIconLevelMin() const;
|
||||||
|
|
|
@ -91,7 +91,6 @@ namespace Data {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using ViewElement = HistoryView::Element;
|
using ViewElement = HistoryView::Element;
|
||||||
using UserIds = std::vector<UserId>;
|
|
||||||
|
|
||||||
// s: box 100x100
|
// s: box 100x100
|
||||||
// m: box 320x320
|
// m: box 320x320
|
||||||
|
@ -977,7 +976,8 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
||||||
| Flag::Forum
|
| Flag::Forum
|
||||||
| ((!minimal && !data.is_stories_hidden_min())
|
| ((!minimal && !data.is_stories_hidden_min())
|
||||||
? Flag::StoriesHidden
|
? Flag::StoriesHidden
|
||||||
: Flag());
|
: Flag())
|
||||||
|
| Flag::AutoTranslation;
|
||||||
const auto storiesState = minimal
|
const auto storiesState = minimal
|
||||||
? std::optional<Data::Stories::PeerSourceState>()
|
? std::optional<Data::Stories::PeerSourceState>()
|
||||||
: data.is_stories_unavailable()
|
: data.is_stories_unavailable()
|
||||||
|
@ -1016,7 +1016,8 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
||||||
&& !data.is_stories_hidden_min()
|
&& !data.is_stories_hidden_min()
|
||||||
&& data.is_stories_hidden())
|
&& data.is_stories_hidden())
|
||||||
? Flag::StoriesHidden
|
? Flag::StoriesHidden
|
||||||
: Flag());
|
: Flag())
|
||||||
|
| (data.is_autotranslation() ? Flag::AutoTranslation : Flag());
|
||||||
channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
|
channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
|
||||||
channel->setBotVerifyDetailsIcon(
|
channel->setBotVerifyDetailsIcon(
|
||||||
data.vbot_verification_icon().value_or_empty());
|
data.vbot_verification_icon().value_or_empty());
|
||||||
|
@ -4865,36 +4866,6 @@ MessageIdsList Session::takeMimeForwardIds() {
|
||||||
return std::move(_mimeForwardIds);
|
return std::move(_mimeForwardIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::setTopPromoted(
|
|
||||||
History *promoted,
|
|
||||||
const QString &type,
|
|
||||||
const QString &message) {
|
|
||||||
const auto changed = (_topPromoted != promoted);
|
|
||||||
if (!changed
|
|
||||||
&& (!promoted || promoted->topPromotionMessage() == message)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (changed) {
|
|
||||||
if (_topPromoted) {
|
|
||||||
_topPromoted->cacheTopPromotion(false, QString(), QString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const auto old = std::exchange(_topPromoted, promoted);
|
|
||||||
if (_topPromoted) {
|
|
||||||
histories().requestDialogEntry(_topPromoted);
|
|
||||||
_topPromoted->cacheTopPromotion(true, type, message);
|
|
||||||
_topPromoted->requestChatListMessage();
|
|
||||||
session().changes().historyUpdated(
|
|
||||||
_topPromoted,
|
|
||||||
HistoryUpdate::Flag::TopPromoted);
|
|
||||||
}
|
|
||||||
if (changed && old) {
|
|
||||||
session().changes().historyUpdated(
|
|
||||||
old,
|
|
||||||
HistoryUpdate::Flag::TopPromoted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Session::updateWallpapers(const MTPaccount_WallPapers &data) {
|
bool Session::updateWallpapers(const MTPaccount_WallPapers &data) {
|
||||||
return data.match([&](const MTPDaccount_wallPapers &data) {
|
return data.match([&](const MTPDaccount_wallPapers &data) {
|
||||||
setWallpapers(data.vwallpapers().v, data.vhash().v);
|
setWallpapers(data.vwallpapers().v, data.vhash().v);
|
||||||
|
@ -5046,69 +5017,4 @@ void Session::clearLocalStorage() {
|
||||||
_bigFileCache->clear();
|
_bigFileCache->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<UserIds> Session::contactBirthdays(bool force) {
|
|
||||||
if ((_contactBirthdaysLastDayRequest != -1)
|
|
||||||
&& (_contactBirthdaysLastDayRequest == QDate::currentDate().day())
|
|
||||||
&& !force) {
|
|
||||||
return rpl::single(_contactBirthdays);
|
|
||||||
}
|
|
||||||
if (_contactBirthdaysRequestId) {
|
|
||||||
_session->api().request(_contactBirthdaysRequestId).cancel();
|
|
||||||
}
|
|
||||||
return [=](auto consumer) {
|
|
||||||
auto lifetime = rpl::lifetime();
|
|
||||||
|
|
||||||
_contactBirthdaysRequestId = _session->api().request(
|
|
||||||
MTPcontacts_GetBirthdays()
|
|
||||||
).done([=](const MTPcontacts_ContactBirthdays &result) {
|
|
||||||
_contactBirthdaysRequestId = 0;
|
|
||||||
_contactBirthdaysLastDayRequest = QDate::currentDate().day();
|
|
||||||
auto users = UserIds();
|
|
||||||
auto today = UserIds();
|
|
||||||
Session::processUsers(result.data().vusers());
|
|
||||||
for (const auto &tlContact : result.data().vcontacts().v) {
|
|
||||||
const auto peerId = tlContact.data().vcontact_id().v;
|
|
||||||
if (const auto user = Session::user(peerId)) {
|
|
||||||
const auto &data = tlContact.data().vbirthday().data();
|
|
||||||
user->setBirthday(Data::Birthday(
|
|
||||||
data.vday().v,
|
|
||||||
data.vmonth().v,
|
|
||||||
data.vyear().value_or_empty()));
|
|
||||||
if (Data::IsBirthdayToday(user->birthday())) {
|
|
||||||
today.push_back(peerToUser(user->id));
|
|
||||||
}
|
|
||||||
users.push_back(peerToUser(user->id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_contactBirthdays = std::move(users);
|
|
||||||
_contactBirthdaysToday = std::move(today);
|
|
||||||
consumer.put_next_copy(_contactBirthdays);
|
|
||||||
}).fail([=](const MTP::Error &error) {
|
|
||||||
_contactBirthdaysRequestId = 0;
|
|
||||||
_contactBirthdaysLastDayRequest = QDate::currentDate().day();
|
|
||||||
_contactBirthdays = {};
|
|
||||||
_contactBirthdaysToday = {};
|
|
||||||
consumer.put_next({});
|
|
||||||
}).send();
|
|
||||||
|
|
||||||
return lifetime;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<UserIds> Session::knownContactBirthdays() const {
|
|
||||||
if ((_contactBirthdaysLastDayRequest == -1)
|
|
||||||
|| (_contactBirthdaysLastDayRequest != QDate::currentDate().day())) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
return _contactBirthdays;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<UserIds> Session::knownBirthdaysToday() const {
|
|
||||||
if ((_contactBirthdaysLastDayRequest == -1)
|
|
||||||
|| (_contactBirthdaysLastDayRequest != QDate::currentDate().day())) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
return _contactBirthdaysToday;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
|
@ -89,9 +89,11 @@ struct GiftUpdate {
|
||||||
Delete,
|
Delete,
|
||||||
Pin,
|
Pin,
|
||||||
Unpin,
|
Unpin,
|
||||||
|
ResaleChange,
|
||||||
};
|
};
|
||||||
|
|
||||||
Data::SavedStarGiftId id;
|
Data::SavedStarGiftId id;
|
||||||
|
QString slug;
|
||||||
Action action = {};
|
Action action = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -799,11 +801,6 @@ public:
|
||||||
void setMimeForwardIds(MessageIdsList &&list);
|
void setMimeForwardIds(MessageIdsList &&list);
|
||||||
MessageIdsList takeMimeForwardIds();
|
MessageIdsList takeMimeForwardIds();
|
||||||
|
|
||||||
void setTopPromoted(
|
|
||||||
History *promoted,
|
|
||||||
const QString &type,
|
|
||||||
const QString &message);
|
|
||||||
|
|
||||||
bool updateWallpapers(const MTPaccount_WallPapers &data);
|
bool updateWallpapers(const MTPaccount_WallPapers &data);
|
||||||
void removeWallpaper(const WallPaper &paper);
|
void removeWallpaper(const WallPaper &paper);
|
||||||
const std::vector<WallPaper> &wallpapers() const;
|
const std::vector<WallPaper> &wallpapers() const;
|
||||||
|
@ -833,13 +830,6 @@ public:
|
||||||
void sentFromScheduled(SentFromScheduled value);
|
void sentFromScheduled(SentFromScheduled value);
|
||||||
[[nodiscard]] rpl::producer<SentFromScheduled> sentFromScheduled() const;
|
[[nodiscard]] rpl::producer<SentFromScheduled> sentFromScheduled() const;
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<std::vector<UserId>> contactBirthdays(
|
|
||||||
bool force = false);
|
|
||||||
[[nodiscard]] auto knownContactBirthdays() const
|
|
||||||
-> std::optional<std::vector<UserId>>;
|
|
||||||
[[nodiscard]] auto knownBirthdaysToday() const
|
|
||||||
-> std::optional<std::vector<UserId>>;
|
|
||||||
|
|
||||||
void clearLocalStorage();
|
void clearLocalStorage();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -1127,8 +1117,6 @@ private:
|
||||||
ReactionId,
|
ReactionId,
|
||||||
base::flat_set<not_null<ViewElement*>>> _viewsByTag;
|
base::flat_set<not_null<ViewElement*>>> _viewsByTag;
|
||||||
|
|
||||||
History *_topPromoted = nullptr;
|
|
||||||
|
|
||||||
std::unordered_map<PeerId, std::unique_ptr<PeerData>> _peers;
|
std::unordered_map<PeerId, std::unique_ptr<PeerData>> _peers;
|
||||||
|
|
||||||
MessageIdsList _mimeForwardIds;
|
MessageIdsList _mimeForwardIds;
|
||||||
|
@ -1155,11 +1143,6 @@ private:
|
||||||
not_null<ChannelData*>,
|
not_null<ChannelData*>,
|
||||||
mtpRequestId> _viewAsMessagesRequests;
|
mtpRequestId> _viewAsMessagesRequests;
|
||||||
|
|
||||||
mtpRequestId _contactBirthdaysRequestId = 0;
|
|
||||||
int _contactBirthdaysLastDayRequest = -1;
|
|
||||||
std::vector<UserId> _contactBirthdays;
|
|
||||||
std::vector<UserId> _contactBirthdaysToday;
|
|
||||||
|
|
||||||
Groups _groups;
|
Groups _groups;
|
||||||
const std::unique_ptr<ChatFilters> _chatsFilters;
|
const std::unique_ptr<ChatFilters> _chatsFilters;
|
||||||
const std::unique_ptr<CloudThemes> _cloudThemes;
|
const std::unique_ptr<CloudThemes> _cloudThemes;
|
||||||
|
|
|
@ -27,6 +27,7 @@ struct UniqueGiftBackdrop : UniqueGiftAttribute {
|
||||||
QColor edgeColor;
|
QColor edgeColor;
|
||||||
QColor patternColor;
|
QColor patternColor;
|
||||||
QColor textColor;
|
QColor textColor;
|
||||||
|
int id = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct UniqueGiftOriginalDetails {
|
struct UniqueGiftOriginalDetails {
|
||||||
|
@ -45,7 +46,10 @@ struct UniqueGift {
|
||||||
PeerId ownerId = 0;
|
PeerId ownerId = 0;
|
||||||
int number = 0;
|
int number = 0;
|
||||||
int starsForTransfer = -1;
|
int starsForTransfer = -1;
|
||||||
|
int starsForResale = -1;
|
||||||
TimeId exportAt = 0;
|
TimeId exportAt = 0;
|
||||||
|
TimeId canTransferAt = 0;
|
||||||
|
TimeId canResellAt = 0;
|
||||||
UniqueGiftModel model;
|
UniqueGiftModel model;
|
||||||
UniqueGiftPattern pattern;
|
UniqueGiftPattern pattern;
|
||||||
UniqueGiftBackdrop backdrop;
|
UniqueGiftBackdrop backdrop;
|
||||||
|
@ -62,7 +66,10 @@ struct StarGift {
|
||||||
int64 stars = 0;
|
int64 stars = 0;
|
||||||
int64 starsConverted = 0;
|
int64 starsConverted = 0;
|
||||||
int64 starsToUpgrade = 0;
|
int64 starsToUpgrade = 0;
|
||||||
|
int64 starsResellMin = 0;
|
||||||
not_null<DocumentData*> document;
|
not_null<DocumentData*> document;
|
||||||
|
QString resellTitle;
|
||||||
|
int resellCount = 0;
|
||||||
int limitedLeft = 0;
|
int limitedLeft = 0;
|
||||||
int limitedCount = 0;
|
int limitedCount = 0;
|
||||||
TimeId firstSaleDate = 0;
|
TimeId firstSaleDate = 0;
|
||||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_global_privacy.h"
|
#include "api/api_global_privacy.h"
|
||||||
#include "api/api_sensitive_content.h"
|
#include "api/api_sensitive_content.h"
|
||||||
#include "api/api_statistics.h"
|
#include "api/api_statistics.h"
|
||||||
|
#include "base/timer_rpl.h"
|
||||||
#include "storage/localstorage.h"
|
#include "storage/localstorage.h"
|
||||||
#include "storage/storage_account.h"
|
#include "storage/storage_account.h"
|
||||||
#include "storage/storage_user_photos.h"
|
#include "storage/storage_user_photos.h"
|
||||||
|
@ -781,6 +782,7 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
||||||
Data::PeerUpdate::Flag::Rights);
|
Data::PeerUpdate::Flag::Rights);
|
||||||
}
|
}
|
||||||
if (info->canEditInformation) {
|
if (info->canEditInformation) {
|
||||||
|
static constexpr auto kTimeout = crl::time(60000);
|
||||||
const auto id = user->id;
|
const auto id = user->id;
|
||||||
const auto weak = base::make_weak(&user->session());
|
const auto weak = base::make_weak(&user->session());
|
||||||
const auto creditsLoadLifetime
|
const auto creditsLoadLifetime
|
||||||
|
@ -790,23 +792,28 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
||||||
creditsLoad->request({}, [=](Data::CreditsStatusSlice slice) {
|
creditsLoad->request({}, [=](Data::CreditsStatusSlice slice) {
|
||||||
if (const auto strong = weak.get()) {
|
if (const auto strong = weak.get()) {
|
||||||
strong->credits().apply(id, slice.balance);
|
strong->credits().apply(id, slice.balance);
|
||||||
creditsLoadLifetime->destroy();
|
|
||||||
}
|
}
|
||||||
|
creditsLoadLifetime->destroy();
|
||||||
});
|
});
|
||||||
|
base::timer_once(kTimeout) | rpl::start_with_next([=] {
|
||||||
|
creditsLoadLifetime->destroy();
|
||||||
|
}, *creditsLoadLifetime);
|
||||||
const auto currencyLoadLifetime
|
const auto currencyLoadLifetime
|
||||||
= std::make_shared<rpl::lifetime>();
|
= std::make_shared<rpl::lifetime>();
|
||||||
const auto currencyLoad
|
const auto currencyLoad
|
||||||
= currencyLoadLifetime->make_state<Api::EarnStatistics>(user);
|
= currencyLoadLifetime->make_state<Api::EarnStatistics>(user);
|
||||||
currencyLoad->request(
|
const auto apply = [=](Data::EarnInt balance) {
|
||||||
) | rpl::start_with_error_done([=](const QString &error) {
|
|
||||||
currencyLoadLifetime->destroy();
|
|
||||||
}, [=] {
|
|
||||||
if (const auto strong = weak.get()) {
|
if (const auto strong = weak.get()) {
|
||||||
strong->credits().applyCurrency(
|
strong->credits().applyCurrency(id, balance);
|
||||||
id,
|
|
||||||
currencyLoad->data().currentBalance);
|
|
||||||
currencyLoadLifetime->destroy();
|
|
||||||
}
|
}
|
||||||
|
currencyLoadLifetime->destroy();
|
||||||
|
};
|
||||||
|
currencyLoad->request() | rpl::start_with_error_done(
|
||||||
|
[=](const QString &error) { apply(0); },
|
||||||
|
[=] { apply(currencyLoad->data().currentBalance); },
|
||||||
|
*currencyLoadLifetime);
|
||||||
|
base::timer_once(kTimeout) | rpl::start_with_next([=] {
|
||||||
|
currencyLoadLifetime->destroy();
|
||||||
}, *currencyLoadLifetime);
|
}, *currencyLoadLifetime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3815,7 +3815,11 @@ void InnerWidget::searchReceived(
|
||||||
clearPreviewResults();
|
clearPreviewResults();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto key = (!_openedForum || _searchState.inChat.topic())
|
const auto globalSearch = (_searchState.tab == ChatSearchTab::MyMessages)
|
||||||
|
|| (_searchState.tab == ChatSearchTab::PublicPosts);
|
||||||
|
const auto key = globalSearch
|
||||||
|
? Key()
|
||||||
|
: (!_openedForum || _searchState.inChat.topic())
|
||||||
? _searchState.inChat
|
? _searchState.inChat
|
||||||
: Key(_openedForum->history());
|
: Key(_openedForum->history());
|
||||||
if (inject
|
if (inject
|
||||||
|
@ -4220,7 +4224,8 @@ void InnerWidget::repaintPreviewResult(int index) {
|
||||||
|
|
||||||
bool InnerWidget::computeSearchWithPostsPreview() const {
|
bool InnerWidget::computeSearchWithPostsPreview() const {
|
||||||
return (_searchHashOrCashtag != HashOrCashtag::None)
|
return (_searchHashOrCashtag != HashOrCashtag::None)
|
||||||
&& (_searchState.tab == ChatSearchTab::MyMessages);
|
&& (_searchState.tab == ChatSearchTab::MyMessages)
|
||||||
|
&& !_searchState.inChat;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::clearFilter() {
|
void InnerWidget::clearFilter() {
|
||||||
|
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "boxes/star_gift_box.h" // ShowStarGiftBox.
|
#include "boxes/star_gift_box.h" // ShowStarGiftBox.
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/click_handler_types.h"
|
#include "core/click_handler_types.h"
|
||||||
|
#include "core/ui_integration.h"
|
||||||
#include "data/data_birthday.h"
|
#include "data/data_birthday.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
@ -23,11 +24,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/history_view_group_call_bar.h"
|
#include "history/view/history_view_group_call_bar.h"
|
||||||
#include "info/profile/info_profile_values.h"
|
#include "info/profile/info_profile_values.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "main/main_app_config.h"
|
#include "data/components/promo_suggestions.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "settings/settings_credits_graphics.h"
|
#include "settings/settings_credits_graphics.h"
|
||||||
#include "settings/settings_premium.h"
|
#include "settings/settings_premium.h"
|
||||||
#include "ui/controls/userpic_button.h"
|
#include "ui/controls/userpic_button.h"
|
||||||
|
#include "ui/effects/credits_graphics.h"
|
||||||
#include "ui/layers/generic_box.h"
|
#include "ui/layers/generic_box.h"
|
||||||
#include "ui/text/format_values.h"
|
#include "ui/text/format_values.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
|
@ -120,10 +122,34 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
const auto content = state->content;
|
const auto content = state->content;
|
||||||
const auto wrap = state->wrap;
|
const auto wrap = state->wrap;
|
||||||
using RightIcon = TopBarSuggestionContent::RightIcon;
|
using RightIcon = TopBarSuggestionContent::RightIcon;
|
||||||
const auto config = &session->appConfig();
|
const auto promo = &session->promoSuggestions();
|
||||||
if (session->premiumCanBuy()
|
if (const auto custom = promo->custom()) {
|
||||||
&& config->suggestionCurrent(kSugPremiumGrace.utf8())) {
|
|
||||||
content->setRightIcon(RightIcon::Close);
|
content->setRightIcon(RightIcon::Close);
|
||||||
|
content->setLeftPadding(0);
|
||||||
|
content->setClickedCallback([=] {
|
||||||
|
const auto controller = FindSessionController(parent);
|
||||||
|
UrlClickHandler::Open(
|
||||||
|
custom->url,
|
||||||
|
QVariant::fromValue(ClickHandlerContext{
|
||||||
|
.sessionWindow = base::make_weak(controller),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
content->setHideCallback([=] {
|
||||||
|
promo->dismiss(custom->suggestion);
|
||||||
|
repeat(repeat);
|
||||||
|
});
|
||||||
|
|
||||||
|
content->setContent(
|
||||||
|
custom->title,
|
||||||
|
custom->description,
|
||||||
|
Core::TextContext({ .session = session }));
|
||||||
|
state->desiredWrapToggle.force_assign(
|
||||||
|
Toggle{ true, anim::type::normal });
|
||||||
|
return;
|
||||||
|
} else if (session->premiumCanBuy()
|
||||||
|
&& promo->current(kSugPremiumGrace.utf8())) {
|
||||||
|
content->setRightIcon(RightIcon::Close);
|
||||||
|
content->setLeftPadding(0);
|
||||||
content->setClickedCallback([=] {
|
content->setClickedCallback([=] {
|
||||||
const auto controller = FindSessionController(parent);
|
const auto controller = FindSessionController(parent);
|
||||||
UrlClickHandler::Open(
|
UrlClickHandler::Open(
|
||||||
|
@ -133,7 +159,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
content->setHideCallback([=] {
|
content->setHideCallback([=] {
|
||||||
config->dismissSuggestion(kSugPremiumGrace.utf8());
|
promo->dismiss(kSugPremiumGrace.utf8());
|
||||||
repeat(repeat);
|
repeat(repeat);
|
||||||
});
|
});
|
||||||
content->setContent(
|
content->setContent(
|
||||||
|
@ -147,7 +173,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
Toggle{ true, anim::type::normal });
|
Toggle{ true, anim::type::normal });
|
||||||
return;
|
return;
|
||||||
} else if (session->premiumCanBuy()
|
} else if (session->premiumCanBuy()
|
||||||
&& config->suggestionCurrent(kSugLowCreditsSubs.utf8())) {
|
&& promo->current(kSugLowCreditsSubs.utf8())) {
|
||||||
state->creditsHistory = std::make_unique<Api::CreditsHistory>(
|
state->creditsHistory = std::make_unique<Api::CreditsHistory>(
|
||||||
session->user(),
|
session->user(),
|
||||||
false,
|
false,
|
||||||
|
@ -156,7 +182,11 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
const QString &peers,
|
const QString &peers,
|
||||||
uint64 needed,
|
uint64 needed,
|
||||||
uint64 whole) {
|
uint64 whole) {
|
||||||
|
if (whole > needed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
content->setRightIcon(RightIcon::Close);
|
content->setRightIcon(RightIcon::Close);
|
||||||
|
content->setLeftPadding(0);
|
||||||
content->setClickedCallback([=] {
|
content->setClickedCallback([=] {
|
||||||
const auto controller = FindSessionController(parent);
|
const auto controller = FindSessionController(parent);
|
||||||
controller->uiShow()->show(Box(
|
controller->uiShow()->show(Box(
|
||||||
|
@ -165,15 +195,27 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
needed,
|
needed,
|
||||||
Settings::SmallBalanceSubscription{ peers },
|
Settings::SmallBalanceSubscription{ peers },
|
||||||
[=] {
|
[=] {
|
||||||
config->dismissSuggestion(
|
promo->dismiss(kSugLowCreditsSubs.utf8());
|
||||||
kSugLowCreditsSubs.utf8());
|
|
||||||
repeat(repeat);
|
repeat(repeat);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
content->setHideCallback([=] {
|
content->setHideCallback([=] {
|
||||||
config->dismissSuggestion(kSugLowCreditsSubs.utf8());
|
promo->dismiss(kSugLowCreditsSubs.utf8());
|
||||||
repeat(repeat);
|
repeat(repeat);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const auto fontH = content->contentTitleSt().font->height;
|
||||||
|
auto customEmojiFactory = [=](
|
||||||
|
QStringView data,
|
||||||
|
const Ui::Text::MarkedContext &context
|
||||||
|
) -> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||||
|
return Ui::MakeCreditsIconEmoji(fontH, 1);
|
||||||
|
};
|
||||||
|
using namespace Ui::Text;
|
||||||
|
auto context = MarkedContext{
|
||||||
|
.customEmojiFactory = std::move(customEmojiFactory),
|
||||||
|
};
|
||||||
|
|
||||||
content->setContent(
|
content->setContent(
|
||||||
tr::lng_dialogs_suggestions_credits_sub_low_title(
|
tr::lng_dialogs_suggestions_credits_sub_low_title(
|
||||||
tr::now,
|
tr::now,
|
||||||
|
@ -187,7 +229,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
tr::lng_dialogs_suggestions_credits_sub_low_about(
|
tr::lng_dialogs_suggestions_credits_sub_low_about(
|
||||||
tr::now,
|
tr::now,
|
||||||
TextWithEntities::Simple),
|
TextWithEntities::Simple),
|
||||||
true);
|
std::move(context));
|
||||||
state->desiredWrapToggle.force_assign(
|
state->desiredWrapToggle.force_assign(
|
||||||
Toggle{ true, anim::type::normal });
|
Toggle{ true, anim::type::normal });
|
||||||
};
|
};
|
||||||
|
@ -219,12 +261,10 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else if (session->premiumCanBuy()
|
} else if (session->premiumCanBuy()
|
||||||
&& config->suggestionCurrent(kSugBirthdayContacts.utf8())) {
|
&& promo->current(kSugBirthdayContacts.utf8())) {
|
||||||
session->data().contactBirthdays(
|
promo->requestContactBirthdays(crl::guard(content, [=] {
|
||||||
) | rpl::start_with_next(crl::guard(content, [=] {
|
const auto users = promo->knownBirthdaysToday().value_or(
|
||||||
const auto users = session->data()
|
std::vector<UserId>());
|
||||||
.knownBirthdaysToday().value_or(
|
|
||||||
std::vector<UserId>());
|
|
||||||
if (users.empty()) {
|
if (users.empty()) {
|
||||||
repeat(repeat);
|
repeat(repeat);
|
||||||
return;
|
return;
|
||||||
|
@ -242,8 +282,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
content->setHideCallback([=] {
|
content->setHideCallback([=] {
|
||||||
config->dismissSuggestion(
|
promo->dismiss(kSugBirthdayContacts.utf8());
|
||||||
kSugBirthdayContacts.utf8());
|
|
||||||
controller->showToast(
|
controller->showToast(
|
||||||
tr::lng_dialogs_suggestions_birthday_contact_dismiss(
|
tr::lng_dialogs_suggestions_birthday_contact_dismiss(
|
||||||
tr::now));
|
tr::now));
|
||||||
|
@ -283,6 +322,8 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
s->widget = base::make_unique_q<Ui::RpWidget>(
|
s->widget = base::make_unique_q<Ui::RpWidget>(
|
||||||
content);
|
content);
|
||||||
const auto widget = s->widget.get();
|
const auto widget = s->widget.get();
|
||||||
|
widget->setAttribute(
|
||||||
|
Qt::WA_TransparentForMouseEvents);
|
||||||
content->sizeValue() | rpl::filter_size(
|
content->sizeValue() | rpl::filter_size(
|
||||||
) | rpl::start_with_next([=](const QSize &size) {
|
) | rpl::start_with_next([=](const QSize &size) {
|
||||||
widget->resize(size);
|
widget->resize(size);
|
||||||
|
@ -343,17 +384,18 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
fake->moveToLeft(
|
fake->moveToLeft(
|
||||||
leftPadding,
|
leftPadding,
|
||||||
(s.height() - fake->height()) / 2);
|
(s.height() - fake->height()) / 2);
|
||||||
}, content->lifetime());
|
}, fake->lifetime());
|
||||||
content->setLeftPadding(fake->width() + leftPadding);
|
content->setLeftPadding(fake->width() + leftPadding);
|
||||||
}
|
}
|
||||||
|
|
||||||
state->desiredWrapToggle.force_assign(
|
state->desiredWrapToggle.force_assign(
|
||||||
Toggle{ true, anim::type::normal });
|
Toggle{ true, anim::type::normal });
|
||||||
}), state->giftsLifetime);
|
}));
|
||||||
return;
|
return;
|
||||||
} else if (config->suggestionCurrent(kSugSetBirthday.utf8())
|
} else if (promo->current(kSugSetBirthday.utf8())
|
||||||
&& !Data::IsBirthdayToday(session->user()->birthday())) {
|
&& !Data::IsBirthdayToday(session->user()->birthday())) {
|
||||||
content->setRightIcon(RightIcon::Close);
|
content->setRightIcon(RightIcon::Close);
|
||||||
|
content->setLeftPadding(0);
|
||||||
content->setClickedCallback([=] {
|
content->setClickedCallback([=] {
|
||||||
const auto controller = FindSessionController(parent);
|
const auto controller = FindSessionController(parent);
|
||||||
Core::App().openInternalUrl(
|
Core::App().openInternalUrl(
|
||||||
|
@ -373,7 +415,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
content->setHideCallback([=] {
|
content->setHideCallback([=] {
|
||||||
config->dismissSuggestion(kSugSetBirthday.utf8());
|
promo->dismiss(kSugSetBirthday.utf8());
|
||||||
repeat(repeat);
|
repeat(repeat);
|
||||||
});
|
});
|
||||||
content->setContent(
|
content->setContent(
|
||||||
|
@ -387,13 +429,13 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
Toggle{ true, anim::type::normal });
|
Toggle{ true, anim::type::normal });
|
||||||
return;
|
return;
|
||||||
} else if (session->premiumPossible() && !session->premium()) {
|
} else if (session->premiumPossible() && !session->premium()) {
|
||||||
const auto isPremiumAnnual = config->suggestionCurrent(
|
const auto isPremiumAnnual = promo->current(
|
||||||
kSugPremiumAnnual.utf8());
|
kSugPremiumAnnual.utf8());
|
||||||
const auto isPremiumRestore = !isPremiumAnnual
|
const auto isPremiumRestore = !isPremiumAnnual
|
||||||
&& config->suggestionCurrent(kSugPremiumRestore.utf8());
|
&& promo->current(kSugPremiumRestore.utf8());
|
||||||
const auto isPremiumUpgrade = !isPremiumAnnual
|
const auto isPremiumUpgrade = !isPremiumAnnual
|
||||||
&& !isPremiumRestore
|
&& !isPremiumRestore
|
||||||
&& config->suggestionCurrent(kSugPremiumUpgrade.utf8());
|
&& promo->current(kSugPremiumUpgrade.utf8());
|
||||||
const auto set = [=](QString discount) {
|
const auto set = [=](QString discount) {
|
||||||
constexpr auto kMinus = QChar(0x2212);
|
constexpr auto kMinus = QChar(0x2212);
|
||||||
const auto &title = isPremiumAnnual
|
const auto &title = isPremiumAnnual
|
||||||
|
@ -416,7 +458,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
content->setClickedCallback([=] {
|
content->setClickedCallback([=] {
|
||||||
const auto controller = FindSessionController(parent);
|
const auto controller = FindSessionController(parent);
|
||||||
Settings::ShowPremium(controller, "dialogs_hint");
|
Settings::ShowPremium(controller, "dialogs_hint");
|
||||||
config->dismissSuggestion(isPremiumAnnual
|
promo->dismiss(isPremiumAnnual
|
||||||
? kSugPremiumAnnual.utf8()
|
? kSugPremiumAnnual.utf8()
|
||||||
: isPremiumRestore
|
: isPremiumRestore
|
||||||
? kSugPremiumRestore.utf8()
|
? kSugPremiumRestore.utf8()
|
||||||
|
@ -428,6 +470,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
};
|
};
|
||||||
if (isPremiumAnnual || isPremiumRestore || isPremiumUpgrade) {
|
if (isPremiumAnnual || isPremiumRestore || isPremiumUpgrade) {
|
||||||
content->setRightIcon(RightIcon::Arrow);
|
content->setRightIcon(RightIcon::Arrow);
|
||||||
|
content->setLeftPadding(0);
|
||||||
const auto api = &session->api().premium();
|
const auto api = &session->api().premium();
|
||||||
api->statusTextValue() | rpl::start_with_next([=] {
|
api->statusTextValue() | rpl::start_with_next([=] {
|
||||||
for (const auto &o : api->subscriptionOptions()) {
|
for (const auto &o : api->subscriptionOptions()) {
|
||||||
|
@ -442,7 +485,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (config->suggestionCurrent(kSugSetUserpic.utf8())
|
if (promo->current(kSugSetUserpic.utf8())
|
||||||
&& !session->user()->userpicPhotoId()) {
|
&& !session->user()->userpicPhotoId()) {
|
||||||
const auto controller = FindSessionController(parent);
|
const auto controller = FindSessionController(parent);
|
||||||
content->setRightIcon(RightIcon::Close);
|
content->setRightIcon(RightIcon::Close);
|
||||||
|
@ -484,7 +527,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
});
|
});
|
||||||
|
|
||||||
content->setHideCallback([=] {
|
content->setHideCallback([=] {
|
||||||
config->dismissSuggestion(kSugSetUserpic.utf8());
|
promo->dismiss(kSugSetUserpic.utf8());
|
||||||
repeat(repeat);
|
repeat(repeat);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -541,7 +584,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
||||||
(was == now) ? toggle.type : anim::type::instant);
|
(was == now) ? toggle.type : anim::type::instant);
|
||||||
}, lifetime);
|
}, lifetime);
|
||||||
|
|
||||||
session->appConfig().value() | rpl::start_with_next([=] {
|
session->promoSuggestions().value() | rpl::start_with_next([=] {
|
||||||
const auto was = state->wrap;
|
const auto was = state->wrap;
|
||||||
processCurrentSuggestion(processCurrentSuggestion);
|
processCurrentSuggestion(processCurrentSuggestion);
|
||||||
if (was != state->wrap) {
|
if (was != state->wrap) {
|
||||||
|
|
|
@ -109,8 +109,8 @@ constexpr auto kSearchRequestDelay = crl::time(900);
|
||||||
|
|
||||||
base::options::toggle OptionForumHideChatsList({
|
base::options::toggle OptionForumHideChatsList({
|
||||||
.id = kOptionForumHideChatsList,
|
.id = kOptionForumHideChatsList,
|
||||||
.name = "Hide chats list in forums",
|
.name = "Hide chat list in forums",
|
||||||
.description = "Don't keep a narrow column of chats list.",
|
.description = "Don't keep a narrow column of chat list.",
|
||||||
});
|
});
|
||||||
|
|
||||||
[[nodiscard]] bool RedirectTextToSearch(const QString &text) {
|
[[nodiscard]] bool RedirectTextToSearch(const QString &text) {
|
||||||
|
@ -2528,7 +2528,19 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
|
||||||
};
|
};
|
||||||
if (trimmed.isEmpty() && !fromPeer && inTags.empty()) {
|
if (trimmed.isEmpty() && !fromPeer && inTags.empty()) {
|
||||||
cancelSearchRequest();
|
cancelSearchRequest();
|
||||||
|
|
||||||
|
// Otherwise inside first searchApplyEmpty we call searchMode(),
|
||||||
|
// which tries to load migrated search results for empty query.
|
||||||
|
_migratedProcess.full = true;
|
||||||
|
|
||||||
searchApplyEmpty(fromStartType, currentSearchProcess());
|
searchApplyEmpty(fromStartType, currentSearchProcess());
|
||||||
|
if (_searchInMigrated) {
|
||||||
|
const auto type = SearchRequestType{
|
||||||
|
.migrated = true,
|
||||||
|
.start = true,
|
||||||
|
};
|
||||||
|
searchApplyEmpty(type, &_migratedProcess);
|
||||||
|
}
|
||||||
if (_searchWithPostsPreview) {
|
if (_searchWithPostsPreview) {
|
||||||
searchApplyEmpty(
|
searchApplyEmpty(
|
||||||
{ .posts = true, .start = true },
|
{ .posts = true, .start = true },
|
||||||
|
@ -2950,7 +2962,8 @@ auto Widget::currentSearchProcess() -> not_null<SearchProcessState*> {
|
||||||
|
|
||||||
bool Widget::computeSearchWithPostsPreview() const {
|
bool Widget::computeSearchWithPostsPreview() const {
|
||||||
return (_searchHashOrCashtag != HashOrCashtag::None)
|
return (_searchHashOrCashtag != HashOrCashtag::None)
|
||||||
&& (_searchState.tab == ChatSearchTab::MyMessages);
|
&& (_searchState.tab == ChatSearchTab::MyMessages)
|
||||||
|
&& !_searchState.inChat;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Widget::searchReceived(
|
void Widget::searchReceived(
|
||||||
|
@ -3488,7 +3501,9 @@ bool Widget::applySearchState(SearchState state) {
|
||||||
: ChatSearchTab::MyMessages;
|
: ChatSearchTab::MyMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto migrateFrom = (peer && !topic)
|
const auto migrateFrom = (peer
|
||||||
|
&& !topic
|
||||||
|
&& state.tab == ChatSearchTab::ThisPeer)
|
||||||
? peer->migrateFrom()
|
? peer->migrateFrom()
|
||||||
: nullptr;
|
: nullptr;
|
||||||
_searchInMigrated = migrateFrom
|
_searchInMigrated = migrateFrom
|
||||||
|
|
|
@ -6,7 +6,7 @@ For license and copyright information please follow this link:
|
||||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "dialogs/ui/dialogs_top_bar_suggestion_content.h"
|
#include "dialogs/ui/dialogs_top_bar_suggestion_content.h"
|
||||||
#include "ui/effects/credits_graphics.h"
|
#include "ui/text/format_values.h"
|
||||||
#include "ui/text/text_custom_emoji.h"
|
#include "ui/text/text_custom_emoji.h"
|
||||||
#include "ui/ui_rpl_filter.h"
|
#include "ui/ui_rpl_filter.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
@ -141,28 +141,19 @@ void TopBarSuggestionContent::draw(QPainter &p) {
|
||||||
void TopBarSuggestionContent::setContent(
|
void TopBarSuggestionContent::setContent(
|
||||||
TextWithEntities title,
|
TextWithEntities title,
|
||||||
TextWithEntities description,
|
TextWithEntities description,
|
||||||
bool makeContext) {
|
std::optional<Ui::Text::MarkedContext> context) {
|
||||||
if (makeContext) {
|
if (context) {
|
||||||
auto customEmojiFactory = [=, h = _contentTitleSt.font->height](
|
context->repaint = [=] { update(); };
|
||||||
QStringView data,
|
|
||||||
const Ui::Text::MarkedContext &context
|
|
||||||
) -> std::unique_ptr<Ui::Text::CustomEmoji> {
|
|
||||||
return Ui::MakeCreditsIconEmoji(h, 1);
|
|
||||||
};
|
|
||||||
const auto context = Ui::Text::MarkedContext{
|
|
||||||
.repaint = [=] { update(); },
|
|
||||||
.customEmojiFactory = std::move(customEmojiFactory),
|
|
||||||
};
|
|
||||||
_contentTitle.setMarkedText(
|
_contentTitle.setMarkedText(
|
||||||
_contentTitleSt,
|
_contentTitleSt,
|
||||||
std::move(title),
|
std::move(title),
|
||||||
kMarkupTextOptions,
|
kMarkupTextOptions,
|
||||||
context);
|
*context);
|
||||||
_contentText.setMarkedText(
|
_contentText.setMarkedText(
|
||||||
_contentTextSt,
|
_contentTextSt,
|
||||||
std::move(description),
|
std::move(description),
|
||||||
kMarkupTextOptions,
|
kMarkupTextOptions,
|
||||||
context);
|
base::take(*context));
|
||||||
} else {
|
} else {
|
||||||
_contentTitle.setMarkedText(_contentTitleSt, std::move(title));
|
_contentTitle.setMarkedText(_contentTitleSt, std::move(title));
|
||||||
_contentText.setMarkedText(_contentTextSt, std::move(description));
|
_contentText.setMarkedText(_contentTextSt, std::move(description));
|
||||||
|
@ -194,6 +185,7 @@ rpl::producer<int> TopBarSuggestionContent::desiredHeightValue() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void TopBarSuggestionContent::setHideCallback(Fn<void()> hideCallback) {
|
void TopBarSuggestionContent::setHideCallback(Fn<void()> hideCallback) {
|
||||||
|
Expects(_rightHide != nullptr);
|
||||||
_rightHide->setClickedCallback(std::move(hideCallback));
|
_rightHide->setClickedCallback(std::move(hideCallback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,4 +194,8 @@ void TopBarSuggestionContent::setLeftPadding(int value) {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const style::TextStyle & TopBarSuggestionContent::contentTitleSt() const {
|
||||||
|
return _contentTitleSt;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Dialogs
|
} // namespace Dialogs
|
||||||
|
|
|
@ -14,6 +14,10 @@ class DynamicImage;
|
||||||
class IconButton;
|
class IconButton;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Ui::Text {
|
||||||
|
struct MarkedContext;
|
||||||
|
} // namespace Ui::Text
|
||||||
|
|
||||||
namespace Dialogs {
|
namespace Dialogs {
|
||||||
|
|
||||||
class TopBarSuggestionContent : public Ui::RippleButton {
|
class TopBarSuggestionContent : public Ui::RippleButton {
|
||||||
|
@ -29,7 +33,7 @@ public:
|
||||||
void setContent(
|
void setContent(
|
||||||
TextWithEntities title,
|
TextWithEntities title,
|
||||||
TextWithEntities description,
|
TextWithEntities description,
|
||||||
bool makeContext = false);
|
std::optional<Ui::Text::MarkedContext> context = std::nullopt);
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<int> desiredHeightValue() const override;
|
[[nodiscard]] rpl::producer<int> desiredHeightValue() const override;
|
||||||
|
|
||||||
|
@ -37,6 +41,8 @@ public:
|
||||||
void setRightIcon(RightIcon);
|
void setRightIcon(RightIcon);
|
||||||
void setLeftPadding(int);
|
void setLeftPadding(int);
|
||||||
|
|
||||||
|
[[nodiscard]] const style::TextStyle &contentTitleSt() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent *) override;
|
void paintEvent(QPaintEvent *) override;
|
||||||
|
|
||||||
|
|
|
@ -25,12 +25,18 @@ EditFlagsDescriptor<FilterValue::Flags> FilterValueLabels(bool isChannel) {
|
||||||
| Flag::Unkick;
|
| Flag::Unkick;
|
||||||
const auto membersNew = Flag::Join | Flag::Invite;
|
const auto membersNew = Flag::Join | Flag::Invite;
|
||||||
const auto membersRemoved = Flag::Leave;
|
const auto membersRemoved = Flag::Leave;
|
||||||
|
auto membersNewText = (isChannel
|
||||||
|
? tr::lng_admin_log_filter_subscribers_new
|
||||||
|
: tr::lng_admin_log_filter_members_new)(tr::now);
|
||||||
|
auto membersRemovedText = (isChannel
|
||||||
|
? tr::lng_admin_log_filter_subscribers_removed
|
||||||
|
: tr::lng_admin_log_filter_members_removed)(tr::now);
|
||||||
|
|
||||||
auto members = std::vector<Label>{
|
auto members = std::vector<Label>{
|
||||||
{ adminRights, tr::lng_admin_log_filter_admins_new(tr::now) },
|
{ adminRights, tr::lng_admin_log_filter_admins_new(tr::now) },
|
||||||
{ restrictions, tr::lng_admin_log_filter_restrictions(tr::now) },
|
{ restrictions, tr::lng_admin_log_filter_restrictions(tr::now) },
|
||||||
{ membersNew, tr::lng_admin_log_filter_members_new(tr::now) },
|
{ membersNew, std::move(membersNewText) },
|
||||||
{ membersRemoved, tr::lng_admin_log_filter_members_removed(tr::now) },
|
{ membersRemoved, std::move(membersRemovedText) },
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto info = Flag::Info | Flag::Settings;
|
const auto info = Flag::Info | Flag::Settings;
|
||||||
|
@ -76,11 +82,15 @@ EditFlagsDescriptor<FilterValue::Flags> FilterValueLabels(bool isChannel) {
|
||||||
}
|
}
|
||||||
return { .labels = {
|
return { .labels = {
|
||||||
{
|
{
|
||||||
tr::lng_admin_log_filter_actions_member_section(),
|
!isChannel
|
||||||
|
? tr::lng_admin_log_filter_actions_member_section()
|
||||||
|
: tr::lng_admin_log_filter_actions_subscriber_section(),
|
||||||
std::move(members),
|
std::move(members),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tr::lng_admin_log_filter_actions_settings_section(),
|
!isChannel
|
||||||
|
? tr::lng_admin_log_filter_actions_settings_section()
|
||||||
|
: tr::lng_admin_log_filter_actions_channel_settings_section(),
|
||||||
std::move(settings),
|
std::move(settings),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -831,6 +831,7 @@ void GenerateItems(
|
||||||
using LogChangeEmojiStatus = MTPDchannelAdminLogEventActionChangeEmojiStatus;
|
using LogChangeEmojiStatus = MTPDchannelAdminLogEventActionChangeEmojiStatus;
|
||||||
using LogToggleSignatureProfiles = MTPDchannelAdminLogEventActionToggleSignatureProfiles;
|
using LogToggleSignatureProfiles = MTPDchannelAdminLogEventActionToggleSignatureProfiles;
|
||||||
using LogParticipantSubExtend = MTPDchannelAdminLogEventActionParticipantSubExtend;
|
using LogParticipantSubExtend = MTPDchannelAdminLogEventActionParticipantSubExtend;
|
||||||
|
using LogToggleAutotranslation = MTPDchannelAdminLogEventActionToggleAutotranslation;
|
||||||
|
|
||||||
const auto session = &history->session();
|
const auto session = &history->session();
|
||||||
const auto id = event.vid().v;
|
const auto id = event.vid().v;
|
||||||
|
@ -2115,6 +2116,18 @@ void GenerateItems(
|
||||||
participantPeerLink);
|
participantPeerLink);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const auto createToggleAutotranslation = [&](const LogToggleAutotranslation &action) {
|
||||||
|
const auto enabled = mtpIsTrue(action.vnew_value());
|
||||||
|
const auto text = (enabled
|
||||||
|
? tr::lng_admin_log_autotranslate_enabled
|
||||||
|
: tr::lng_admin_log_autotranslate_disabled)(
|
||||||
|
tr::now,
|
||||||
|
lt_from,
|
||||||
|
fromLinkText,
|
||||||
|
Ui::Text::WithEntities);
|
||||||
|
addSimpleServiceMessage(text);
|
||||||
|
};
|
||||||
|
|
||||||
action.match(
|
action.match(
|
||||||
createChangeTitle,
|
createChangeTitle,
|
||||||
createChangeAbout,
|
createChangeAbout,
|
||||||
|
@ -2165,7 +2178,8 @@ void GenerateItems(
|
||||||
createChangeWallpaper,
|
createChangeWallpaper,
|
||||||
createChangeEmojiStatus,
|
createChangeEmojiStatus,
|
||||||
createToggleSignatureProfiles,
|
createToggleSignatureProfiles,
|
||||||
createParticipantSubExtend);
|
createParticipantSubExtend,
|
||||||
|
createToggleAutotranslation);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace AdminLog
|
} // namespace AdminLog
|
||||||
|
|
|
@ -3696,6 +3696,11 @@ void History::translateOfferFrom(LanguageId id) {
|
||||||
}
|
}
|
||||||
} else if (!_translation) {
|
} else if (!_translation) {
|
||||||
_translation = std::make_unique<HistoryTranslation>(this, id);
|
_translation = std::make_unique<HistoryTranslation>(this, id);
|
||||||
|
using Flag = PeerData::TranslationFlag;
|
||||||
|
if (peer->autoTranslation()
|
||||||
|
&& (peer->translationFlag() == Flag::Enabled)) {
|
||||||
|
translateTo(Core::App().settings().translateTo());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
_translation->offerFrom(id);
|
_translation->offerFrom(id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -580,7 +580,8 @@ void HistoryInner::setupSwipeReplyAndBack() {
|
||||||
}
|
}
|
||||||
const auto peer = _peer;
|
const auto peer = _peer;
|
||||||
|
|
||||||
auto update = [=, history = _history](Ui::Controls::SwipeContextData data) {
|
auto update = [=, history = _history](
|
||||||
|
Ui::Controls::SwipeContextData data) {
|
||||||
if (data.translation > 0) {
|
if (data.translation > 0) {
|
||||||
if (!_swipeBackData.callback) {
|
if (!_swipeBackData.callback) {
|
||||||
_swipeBackData = Ui::Controls::SetupSwipeBack(
|
_swipeBackData = Ui::Controls::SetupSwipeBack(
|
||||||
|
@ -616,9 +617,24 @@ void HistoryInner::setupSwipeReplyAndBack() {
|
||||||
int cursorTop,
|
int cursorTop,
|
||||||
Qt::LayoutDirection direction) {
|
Qt::LayoutDirection direction) {
|
||||||
if (direction == Qt::RightToLeft) {
|
if (direction == Qt::RightToLeft) {
|
||||||
return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] {
|
auto good = true;
|
||||||
_controller->showBackFromStack();
|
enumerateItems<EnumItemsDirection::BottomToTop>([&](
|
||||||
|
not_null<Element*> view,
|
||||||
|
int itemtop,
|
||||||
|
int itembottom) {
|
||||||
|
if (view->data()->showSimilarChannels()) {
|
||||||
|
good = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
|
if (good) {
|
||||||
|
return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] {
|
||||||
|
_controller->showBackFromStack();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Ui::Controls::SwipeHandlerFinishData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
auto result = Ui::Controls::SwipeHandlerFinishData();
|
auto result = Ui::Controls::SwipeHandlerFinishData();
|
||||||
if (inSelectionMode().inSelectionMode
|
if (inSelectionMode().inSelectionMode
|
||||||
|
@ -632,6 +648,7 @@ void HistoryInner::setupSwipeReplyAndBack() {
|
||||||
if ((cursorTop < itemtop)
|
if ((cursorTop < itemtop)
|
||||||
|| (cursorTop > itembottom)
|
|| (cursorTop > itembottom)
|
||||||
|| !view->data()->isRegular()
|
|| !view->data()->isRegular()
|
||||||
|
|| view->data()->showSimilarChannels()
|
||||||
|| view->data()->isService()) {
|
|| view->data()->isService()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -3873,9 +3890,7 @@ void HistoryInner::elementSendBotCommand(
|
||||||
void HistoryInner::elementSearchInList(
|
void HistoryInner::elementSearchInList(
|
||||||
const QString &query,
|
const QString &query,
|
||||||
const FullMsgId &context) {
|
const FullMsgId &context) {
|
||||||
const auto inChat = _history->peer->isUser()
|
const auto inChat = Dialogs::Key(_history);
|
||||||
? Dialogs::Key()
|
|
||||||
: Dialogs::Key(_history);
|
|
||||||
_controller->searchMessages(query, inChat);
|
_controller->searchMessages(query, inChat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ template <typename T>
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] TextWithEntities SpoilerLoginCode(TextWithEntities text) {
|
[[nodiscard]] TextWithEntities SpoilerLoginCode(TextWithEntities text) {
|
||||||
const auto r = QRegularExpression(u"([\\d\\-]{4,8})"_q);
|
const auto r = QRegularExpression(u"(?<!#)([\\d\\-]{4,8})"_q);
|
||||||
const auto m = r.match(text.text);
|
const auto m = r.match(text.text);
|
||||||
if (!m.hasMatch()) {
|
if (!m.hasMatch()) {
|
||||||
return text;
|
return text;
|
||||||
|
@ -1099,14 +1099,14 @@ void HistoryItem::setGroupId(MessageGroupId groupId) {
|
||||||
_history->owner().groups().registerMessage(this);
|
_history->owner().groups().registerMessage(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryItem::checkCommentsLinkedChat(ChannelId id) const {
|
bool HistoryItem::checkDiscussionLink(ChannelId id) const {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return true;
|
return true;
|
||||||
} else if (const auto channel = _history->peer->asChannel()) {
|
} else if (const auto channel = _history->peer->asChannel()) {
|
||||||
if (channel->linkedChatKnown()
|
if (channel->discussionLinkKnown()
|
||||||
|| !(channel->flags() & ChannelDataFlag::HasLink)) {
|
|| !(channel->flags() & ChannelDataFlag::HasLink)) {
|
||||||
const auto linked = channel->linkedChat();
|
const auto link = channel->discussionLink();
|
||||||
if (!linked || peerToChannel(linked->id) != id) {
|
if (!link || peerToChannel(link->id) != id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1209,7 +1209,7 @@ bool HistoryItem::areCommentsUnread() const {
|
||||||
const auto views = Get<HistoryMessageViews>();
|
const auto views = Get<HistoryMessageViews>();
|
||||||
if (!views
|
if (!views
|
||||||
|| !views->commentsMegagroupId
|
|| !views->commentsMegagroupId
|
||||||
|| !checkCommentsLinkedChat(views->commentsMegagroupId)) {
|
|| !checkDiscussionLink(views->commentsMegagroupId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto till = views->commentsInboxReadTillId;
|
const auto till = views->commentsInboxReadTillId;
|
||||||
|
@ -3469,7 +3469,7 @@ int HistoryItem::viewsCount() const {
|
||||||
|
|
||||||
int HistoryItem::repliesCount() const {
|
int HistoryItem::repliesCount() const {
|
||||||
if (const auto views = Get<HistoryMessageViews>()) {
|
if (const auto views = Get<HistoryMessageViews>()) {
|
||||||
if (!checkCommentsLinkedChat(views->commentsMegagroupId)) {
|
if (!checkDiscussionLink(views->commentsMegagroupId)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return std::max(views->replies.count, 0);
|
return std::max(views->replies.count, 0);
|
||||||
|
@ -3480,7 +3480,7 @@ int HistoryItem::repliesCount() const {
|
||||||
bool HistoryItem::repliesAreComments() const {
|
bool HistoryItem::repliesAreComments() const {
|
||||||
if (const auto views = Get<HistoryMessageViews>()) {
|
if (const auto views = Get<HistoryMessageViews>()) {
|
||||||
return (views->commentsMegagroupId != 0)
|
return (views->commentsMegagroupId != 0)
|
||||||
&& checkCommentsLinkedChat(views->commentsMegagroupId);
|
&& checkDiscussionLink(views->commentsMegagroupId);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -3977,9 +3977,9 @@ void HistoryItem::createComponents(CreateConfig &&config) {
|
||||||
&& isSending()
|
&& isSending()
|
||||||
&& config.markup.isNull()) {
|
&& config.markup.isNull()) {
|
||||||
if (const auto broadcast = _history->peer->asBroadcast()) {
|
if (const auto broadcast = _history->peer->asBroadcast()) {
|
||||||
if (const auto linked = broadcast->linkedChat()) {
|
if (const auto link = broadcast->discussionLink()) {
|
||||||
config.replies.isNull = false;
|
config.replies.isNull = false;
|
||||||
config.replies.channelId = peerToChannel(linked->id);
|
config.replies.channelId = peerToChannel(link->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5783,6 +5783,11 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||||
const MTPDmessageActionStarGiftUnique &action) {
|
const MTPDmessageActionStarGiftUnique &action) {
|
||||||
auto result = PreparedServiceText();
|
auto result = PreparedServiceText();
|
||||||
const auto isSelf = _from->isSelf();
|
const auto isSelf = _from->isSelf();
|
||||||
|
const auto resaleStars = action.vresale_stars().value_or_empty();
|
||||||
|
const auto resaleCost = TextWithEntities{ resaleStars
|
||||||
|
? tr::lng_action_gift_for_stars(tr::now, lt_count, resaleStars)
|
||||||
|
: QString()
|
||||||
|
};
|
||||||
const auto giftPeer = action.vpeer()
|
const auto giftPeer = action.vpeer()
|
||||||
? peerFromMTP(*action.vpeer())
|
? peerFromMTP(*action.vpeer())
|
||||||
: PeerId();
|
: PeerId();
|
||||||
|
@ -5798,49 +5803,92 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||||
peerToChannel(giftPeer));
|
peerToChannel(giftPeer));
|
||||||
if (!from->isServiceUser() && !from->isSelf()) {
|
if (!from->isServiceUser() && !from->isSelf()) {
|
||||||
result.links.push_back(from->createOpenLink());
|
result.links.push_back(from->createOpenLink());
|
||||||
result.text = (action.is_upgrade()
|
if (resaleStars) {
|
||||||
? tr::lng_action_gift_upgraded_channel
|
result.links.push_back(channel->createOpenLink());
|
||||||
: tr::lng_action_gift_transferred_channel)(
|
}
|
||||||
|
result.text = resaleStars
|
||||||
|
? tr::lng_action_gift_sent_channel(
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_user,
|
lt_user,
|
||||||
Ui::Text::Link(from->shortName(), 1),
|
Ui::Text::Link(from->shortName(), 1),
|
||||||
lt_channel,
|
lt_name,
|
||||||
Ui::Text::Link(channel->name(), 2),
|
Ui::Text::Link(channel->name(), 2),
|
||||||
Ui::Text::WithEntities);
|
lt_cost,
|
||||||
|
resaleCost,
|
||||||
|
Ui::Text::WithEntities)
|
||||||
|
: (action.is_upgrade()
|
||||||
|
? tr::lng_action_gift_upgraded_channel
|
||||||
|
: tr::lng_action_gift_transferred_channel)(
|
||||||
|
tr::now,
|
||||||
|
lt_user,
|
||||||
|
Ui::Text::Link(from->shortName(), 1),
|
||||||
|
lt_channel,
|
||||||
|
Ui::Text::Link(channel->name(), 2),
|
||||||
|
Ui::Text::WithEntities);
|
||||||
} else {
|
} else {
|
||||||
result.text = (from->isServiceUser()
|
result.text = resaleStars
|
||||||
? tr::lng_action_gift_transferred_unknown_channel
|
? tr::lng_action_gift_sent_self_channel(
|
||||||
: action.is_upgrade()
|
|
||||||
? tr::lng_action_gift_upgraded_self_channel
|
|
||||||
: tr::lng_action_gift_transferred_self_channel)(
|
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_channel,
|
lt_name,
|
||||||
Ui::Text::Link(channel->name(), 1),
|
Ui::Text::Link(channel->name(), 1),
|
||||||
Ui::Text::WithEntities);
|
lt_cost,
|
||||||
|
resaleCost,
|
||||||
|
Ui::Text::WithEntities)
|
||||||
|
: (from->isServiceUser()
|
||||||
|
? tr::lng_action_gift_transferred_unknown_channel
|
||||||
|
: action.is_upgrade()
|
||||||
|
? tr::lng_action_gift_upgraded_self_channel
|
||||||
|
: tr::lng_action_gift_transferred_self_channel)(
|
||||||
|
tr::now,
|
||||||
|
lt_channel,
|
||||||
|
Ui::Text::Link(channel->name(), 1),
|
||||||
|
Ui::Text::WithEntities);
|
||||||
}
|
}
|
||||||
result.links.push_back(channel->createOpenLink());
|
result.links.push_back(channel->createOpenLink());
|
||||||
} else {
|
} else {
|
||||||
if (!from->isServiceUser() && !_history->peer->isSelf()) {
|
if (!from->isServiceUser() && !_history->peer->isSelf()) {
|
||||||
result.links.push_back(from->createOpenLink());
|
if (!resaleStars || !isSelf) {
|
||||||
result.text = (action.is_upgrade()
|
result.links.push_back(from->createOpenLink());
|
||||||
|
}
|
||||||
|
result.text = resaleStars
|
||||||
? (isSelf
|
? (isSelf
|
||||||
? tr::lng_action_gift_upgraded_mine
|
? tr::lng_action_gift_sent(
|
||||||
: tr::lng_action_gift_upgraded)
|
tr::now,
|
||||||
: (isSelf
|
lt_cost,
|
||||||
? tr::lng_action_gift_transferred_mine
|
resaleCost,
|
||||||
: tr::lng_action_gift_transferred))(
|
Ui::Text::WithEntities)
|
||||||
|
: tr::lng_action_gift_received(
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_user,
|
lt_user,
|
||||||
Ui::Text::Link(from->shortName(), 1), // Link 1.
|
Ui::Text::Link(peer->shortName(), 1), // Link 1.
|
||||||
Ui::Text::WithEntities);
|
lt_cost,
|
||||||
|
resaleCost,
|
||||||
|
Ui::Text::WithEntities))
|
||||||
|
: (action.is_upgrade()
|
||||||
|
? (isSelf
|
||||||
|
? tr::lng_action_gift_upgraded_mine
|
||||||
|
: tr::lng_action_gift_upgraded)
|
||||||
|
: (isSelf
|
||||||
|
? tr::lng_action_gift_transferred_mine
|
||||||
|
: tr::lng_action_gift_transferred))(
|
||||||
|
tr::now,
|
||||||
|
lt_user,
|
||||||
|
Ui::Text::Link(from->shortName(), 1),
|
||||||
|
Ui::Text::WithEntities);
|
||||||
} else {
|
} else {
|
||||||
result.text = (from->isServiceUser()
|
result.text = resaleStars
|
||||||
? tr::lng_action_gift_transferred_unknown
|
? tr::lng_action_gift_self_bought(
|
||||||
: action.is_upgrade()
|
|
||||||
? tr::lng_action_gift_upgraded_self
|
|
||||||
: tr::lng_action_gift_transferred_self)(
|
|
||||||
tr::now,
|
tr::now,
|
||||||
Ui::Text::WithEntities);
|
lt_cost,
|
||||||
|
resaleCost,
|
||||||
|
Ui::Text::WithEntities)
|
||||||
|
: (from->isServiceUser()
|
||||||
|
? tr::lng_action_gift_transferred_unknown
|
||||||
|
: action.is_upgrade()
|
||||||
|
? tr::lng_action_gift_upgraded_self
|
||||||
|
: tr::lng_action_gift_transferred_self)(
|
||||||
|
tr::now,
|
||||||
|
Ui::Text::WithEntities);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -6148,6 +6196,8 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
|
||||||
unique->starsForTransfer
|
unique->starsForTransfer
|
||||||
= data.vtransfer_stars().value_or(-1);
|
= data.vtransfer_stars().value_or(-1);
|
||||||
unique->exportAt = data.vcan_export_at().value_or_empty();
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_media = std::make_unique<Data::MediaGiftBox>(
|
_media = std::make_unique<Data::MediaGiftBox>(
|
||||||
|
|
|
@ -323,7 +323,7 @@ public:
|
||||||
return (_flags & MessageFlag::HideEdited);
|
return (_flags & MessageFlag::HideEdited);
|
||||||
}
|
}
|
||||||
[[nodiscard]] bool hideDisplayDate() const {
|
[[nodiscard]] bool hideDisplayDate() const {
|
||||||
return (_flags & MessageFlag::HideDisplayDate);
|
return isEmpty() || (_flags & MessageFlag::HideDisplayDate);
|
||||||
}
|
}
|
||||||
[[nodiscard]] bool isLocal() const {
|
[[nodiscard]] bool isLocal() const {
|
||||||
return _flags & MessageFlag::Local;
|
return _flags & MessageFlag::Local;
|
||||||
|
@ -605,7 +605,7 @@ private:
|
||||||
return _flags & MessageFlag::Legacy;
|
return _flags & MessageFlag::Legacy;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool checkCommentsLinkedChat(ChannelId id) const;
|
[[nodiscard]] bool checkDiscussionLink(ChannelId id) const;
|
||||||
|
|
||||||
void setReplyMarkup(HistoryMessageMarkupData &&markup);
|
void setReplyMarkup(HistoryMessageMarkupData &&markup);
|
||||||
|
|
||||||
|
|
|
@ -872,7 +872,7 @@ HistoryWidget::HistoryWidget(
|
||||||
| PeerUpdateFlag::OnlineStatus
|
| PeerUpdateFlag::OnlineStatus
|
||||||
| PeerUpdateFlag::Notifications
|
| PeerUpdateFlag::Notifications
|
||||||
| PeerUpdateFlag::ChannelAmIn
|
| PeerUpdateFlag::ChannelAmIn
|
||||||
| PeerUpdateFlag::ChannelLinkedChat
|
| PeerUpdateFlag::DiscussionLink
|
||||||
| PeerUpdateFlag::Slowmode
|
| PeerUpdateFlag::Slowmode
|
||||||
| PeerUpdateFlag::BotStartToken
|
| PeerUpdateFlag::BotStartToken
|
||||||
| PeerUpdateFlag::MessagesTTL
|
| PeerUpdateFlag::MessagesTTL
|
||||||
|
@ -944,7 +944,7 @@ HistoryWidget::HistoryWidget(
|
||||||
| PeerUpdateFlag::OnlineStatus
|
| PeerUpdateFlag::OnlineStatus
|
||||||
| PeerUpdateFlag::Rights
|
| PeerUpdateFlag::Rights
|
||||||
| PeerUpdateFlag::ChannelAmIn
|
| PeerUpdateFlag::ChannelAmIn
|
||||||
| PeerUpdateFlag::ChannelLinkedChat)) {
|
| PeerUpdateFlag::DiscussionLink)) {
|
||||||
handlePeerUpdate();
|
handlePeerUpdate();
|
||||||
}
|
}
|
||||||
if (flags & PeerUpdateFlag::MessagesTTL) {
|
if (flags & PeerUpdateFlag::MessagesTTL) {
|
||||||
|
@ -7819,12 +7819,12 @@ void HistoryWidget::checkPinnedBarState() {
|
||||||
}
|
}
|
||||||
return (count > 1);
|
return (count > 1);
|
||||||
}) | rpl::distinct_until_changed();
|
}) | rpl::distinct_until_changed();
|
||||||
auto markupRefreshed = HistoryView::PinnedBarItemWithReplyMarkup(
|
auto customButtonItem = HistoryView::PinnedBarItemWithCustomButton(
|
||||||
&session(),
|
&session(),
|
||||||
_pinnedTracker->shownMessageId());
|
_pinnedTracker->shownMessageId());
|
||||||
rpl::combine(
|
rpl::combine(
|
||||||
rpl::duplicate(pinnedRefreshed),
|
rpl::duplicate(pinnedRefreshed),
|
||||||
rpl::duplicate(markupRefreshed)
|
rpl::duplicate(customButtonItem)
|
||||||
) | rpl::start_with_next([=](bool many, HistoryItem *item) {
|
) | rpl::start_with_next([=](bool many, HistoryItem *item) {
|
||||||
refreshPinnedBarButton(many, item);
|
refreshPinnedBarButton(many, item);
|
||||||
}, _pinnedBar->lifetime());
|
}, _pinnedBar->lifetime());
|
||||||
|
@ -7835,7 +7835,7 @@ void HistoryWidget::checkPinnedBarState() {
|
||||||
_pinnedTracker->shownMessageId(),
|
_pinnedTracker->shownMessageId(),
|
||||||
[bar = _pinnedBar.get()] { bar->customEmojiRepaint(); }),
|
[bar = _pinnedBar.get()] { bar->customEmojiRepaint(); }),
|
||||||
std::move(pinnedRefreshed),
|
std::move(pinnedRefreshed),
|
||||||
std::move(markupRefreshed)
|
std::move(customButtonItem)
|
||||||
) | rpl::map([=](Ui::MessageBarContent &&content, bool, HistoryItem*) {
|
) | rpl::map([=](Ui::MessageBarContent &&content, bool, HistoryItem*) {
|
||||||
const auto id = (!content.title.isEmpty() || !content.text.empty())
|
const auto id = (!content.title.isEmpty() || !content.text.empty())
|
||||||
? _pinnedTracker->currentMessageId().message
|
? _pinnedTracker->currentMessageId().message
|
||||||
|
@ -7973,58 +7973,31 @@ void HistoryWidget::refreshPinnedBarButton(bool many, HistoryItem *item) {
|
||||||
? id.message.msg
|
? id.message.msg
|
||||||
: (id.message.msg - ServerMaxMsgId))));
|
: (id.message.msg - ServerMaxMsgId))));
|
||||||
};
|
};
|
||||||
if (const auto replyMarkup = item ? item->inlineReplyMarkup() : nullptr) {
|
const auto context = [copy = _list](FullMsgId itemId) {
|
||||||
const auto &rows = replyMarkup->data.rows;
|
if (const auto raw = copy.data()) {
|
||||||
if ((rows.size() == 1) && (rows.front().size() == 1)) {
|
return raw->prepareClickHandlerContext(itemId);
|
||||||
const auto text = rows.front().front().text;
|
|
||||||
if (!text.isEmpty()) {
|
|
||||||
const auto &st = st::historyPinnedBotButton;
|
|
||||||
auto button = object_ptr<Ui::RoundButton>(
|
|
||||||
this,
|
|
||||||
rpl::never<QString>(),
|
|
||||||
st);
|
|
||||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
|
||||||
button.data(),
|
|
||||||
text,
|
|
||||||
st::historyPinnedBotLabel);
|
|
||||||
if (label->width() > st::historyPinnedBotButtonMaxWidth) {
|
|
||||||
label->resizeToWidth(st::historyPinnedBotButtonMaxWidth);
|
|
||||||
}
|
|
||||||
button->setFullWidth(label->width()
|
|
||||||
+ st.padding.left()
|
|
||||||
+ st.padding.right()
|
|
||||||
+ st.height);
|
|
||||||
label->moveToLeft(
|
|
||||||
st.padding.left() + st.height / 2,
|
|
||||||
(button->height() - label->height()) / 2);
|
|
||||||
label->setTextColorOverride(st.textFg->c);
|
|
||||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
||||||
button->setTextTransform(
|
|
||||||
Ui::RoundButton::TextTransform::NoTransform);
|
|
||||||
button->setFullRadius(true);
|
|
||||||
button->setClickedCallback([=] {
|
|
||||||
Api::ActivateBotCommand(
|
|
||||||
_list->prepareClickHandlerContext(item->fullId()),
|
|
||||||
0,
|
|
||||||
0);
|
|
||||||
});
|
|
||||||
struct State {
|
|
||||||
base::unique_qptr<Ui::PopupMenu> menu;
|
|
||||||
};
|
|
||||||
const auto state = button->lifetime().make_state<State>();
|
|
||||||
_pinnedBar->contextMenuRequested(
|
|
||||||
) | rpl::start_with_next([=, raw = button.data()] {
|
|
||||||
state->menu = base::make_unique_q<Ui::PopupMenu>(raw);
|
|
||||||
state->menu->addAction(
|
|
||||||
tr::lng_settings_events_pinned(tr::now),
|
|
||||||
openSection);
|
|
||||||
state->menu->popup(QCursor::pos());
|
|
||||||
}, button->lifetime());
|
|
||||||
_pinnedBar->setRightButton(std::move(button));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return ClickHandlerContext();
|
||||||
|
};
|
||||||
|
auto customButton = CreatePinnedBarCustomButton(this, item, context);
|
||||||
|
if (customButton) {
|
||||||
|
struct State {
|
||||||
|
base::unique_qptr<Ui::PopupMenu> menu;
|
||||||
|
};
|
||||||
|
const auto buttonRaw = customButton.data();
|
||||||
|
const auto state = buttonRaw->lifetime().make_state<State>();
|
||||||
|
_pinnedBar->contextMenuRequested(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
state->menu = base::make_unique_q<Ui::PopupMenu>(buttonRaw);
|
||||||
|
state->menu->addAction(
|
||||||
|
tr::lng_settings_events_pinned(tr::now),
|
||||||
|
openSection);
|
||||||
|
state->menu->popup(QCursor::pos());
|
||||||
|
}, buttonRaw->lifetime());
|
||||||
|
_pinnedBar->setRightButton(std::move(customButton));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto close = !many;
|
const auto close = !many;
|
||||||
auto button = object_ptr<Ui::IconButton>(
|
auto button = object_ptr<Ui::IconButton>(
|
||||||
this,
|
this,
|
||||||
|
|
|
@ -2325,11 +2325,17 @@ void SetupRestrictionView(
|
||||||
) | rpl::start_with_next([=](Controls::WriteRestriction value) {
|
) | rpl::start_with_next([=](Controls::WriteRestriction value) {
|
||||||
using Type = Controls::WriteRestriction::Type;
|
using Type = Controls::WriteRestriction::Type;
|
||||||
if (value.type == Type::Frozen) {
|
if (value.type == Type::Frozen) {
|
||||||
|
state->icon = nullptr;
|
||||||
|
state->unlock = nullptr;
|
||||||
|
state->label = nullptr;
|
||||||
state->button = FrozenWriteRestriction(
|
state->button = FrozenWriteRestriction(
|
||||||
widget,
|
widget,
|
||||||
show,
|
show,
|
||||||
FrozenWriteRestrictionType::MessageField);
|
FrozenWriteRestrictionType::MessageField);
|
||||||
} else if (const auto lifting = value.boostsToLift) {
|
} else if (const auto lifting = value.boostsToLift) {
|
||||||
|
state->icon = nullptr;
|
||||||
|
state->unlock = nullptr;
|
||||||
|
state->label = nullptr;
|
||||||
state->button = BoostsToLiftWriteRestriction(
|
state->button = BoostsToLiftWriteRestriction(
|
||||||
widget,
|
widget,
|
||||||
show,
|
show,
|
||||||
|
|
|
@ -1421,7 +1421,7 @@ void Element::recountDisplayDateInBlocks() {
|
||||||
|
|
||||||
if (const auto previous = previousDisplayedInBlocks()) {
|
if (const auto previous = previousDisplayedInBlocks()) {
|
||||||
const auto prev = previous->data();
|
const auto prev = previous->data();
|
||||||
return prev->isEmpty()
|
return prev->hideDisplayDate()
|
||||||
|| (previous->dateTime().date() != dateTime().date());
|
|| (previous->dateTime().date() != dateTime().date());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -4087,7 +4087,8 @@ void ListWidget::refreshAttachmentsFromTill(int from, int till) {
|
||||||
const auto viewDate = view->dateTime();
|
const auto viewDate = view->dateTime();
|
||||||
const auto nextDate = next->dateTime();
|
const auto nextDate = next->dateTime();
|
||||||
next->setDisplayDate(_context != Context::ShortcutMessages
|
next->setDisplayDate(_context != Context::ShortcutMessages
|
||||||
&& nextDate.date() != viewDate.date());
|
&& (nextDate.date() != viewDate.date()
|
||||||
|
|| view->data()->hideDisplayDate()));
|
||||||
auto attached = next->computeIsAttachToPrevious(view);
|
auto attached = next->computeIsAttachToPrevious(view);
|
||||||
next->setAttachToPrevious(attached, view);
|
next->setAttachToPrevious(attached, view);
|
||||||
view->setAttachToNext(attached, next);
|
view->setAttachToNext(attached, next);
|
||||||
|
|
|
@ -1224,6 +1224,9 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||||
}
|
}
|
||||||
if (!mediaOnBottom && (!_viewButton || !reactionsInBubble)) {
|
if (!mediaOnBottom && (!_viewButton || !reactionsInBubble)) {
|
||||||
localMediaBottom -= st::msgPadding.bottom();
|
localMediaBottom -= st::msgPadding.bottom();
|
||||||
|
if (mediaDisplayed) {
|
||||||
|
localMediaBottom -= st::mediaInBubbleSkip;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (check) {
|
if (check) {
|
||||||
localMediaBottom -= check->height();
|
localMediaBottom -= check->height();
|
||||||
|
|
|
@ -7,17 +7,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "history/view/history_view_pinned_bar.h"
|
#include "history/view/history_view_pinned_bar.h"
|
||||||
|
|
||||||
|
#include "api/api_bot.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_poll.h"
|
#include "data/data_poll.h"
|
||||||
|
#include "data/data_web_page.h"
|
||||||
#include "history/view/history_view_pinned_tracker.h"
|
#include "history/view/history_view_pinned_tracker.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
|
#include "core/click_handler_types.h"
|
||||||
#include "core/ui_integration.h"
|
#include "core/ui_integration.h"
|
||||||
#include "base/weak_ptr.h"
|
#include "base/weak_ptr.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/labels.h"
|
||||||
|
#include "ui/basic_click_handlers.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
#include "styles/style_chat_helpers.h"
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
|
@ -153,6 +160,45 @@ auto WithPinnedTitle(not_null<Main::Session*> session, PinnedId id) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] object_ptr<Ui::RoundButton> MakePinnedBarCustomButton(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
const QString &buttonText,
|
||||||
|
Fn<void()> clickCallback) {
|
||||||
|
const auto &stButton = st::historyPinnedBotButton;
|
||||||
|
const auto &stLabel = st::historyPinnedBotLabel;
|
||||||
|
|
||||||
|
auto button = object_ptr<Ui::RoundButton>(
|
||||||
|
parent,
|
||||||
|
rpl::never<QString>(), // Text is handled by the inner label.
|
||||||
|
stButton);
|
||||||
|
|
||||||
|
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
button.data(),
|
||||||
|
buttonText,
|
||||||
|
stLabel);
|
||||||
|
|
||||||
|
if (label->width() > st::historyPinnedBotButtonMaxWidth) {
|
||||||
|
label->resizeToWidth(st::historyPinnedBotButtonMaxWidth);
|
||||||
|
}
|
||||||
|
button->setFullWidth(label->width()
|
||||||
|
+ stButton.padding.left()
|
||||||
|
+ stButton.padding.right()
|
||||||
|
+ stButton.height); // stButton.height is likely for icon spacing.
|
||||||
|
|
||||||
|
label->moveToLeft(
|
||||||
|
stButton.padding.left() + stButton.height / 2,
|
||||||
|
(button->height() - label->height()) / 2);
|
||||||
|
|
||||||
|
label->setTextColorOverride(stButton.textFg->c); // Use button's text color for label.
|
||||||
|
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
|
||||||
|
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||||
|
button->setFullRadius(true);
|
||||||
|
button->setClickedCallback(std::move(clickCallback));
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
rpl::producer<Ui::MessageBarContent> MessageBarContentByItemId(
|
rpl::producer<Ui::MessageBarContent> MessageBarContentByItemId(
|
||||||
|
@ -178,7 +224,7 @@ rpl::producer<Ui::MessageBarContent> PinnedBarContent(
|
||||||
}) | rpl::flatten_latest();
|
}) | rpl::flatten_latest();
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
|
rpl::producer<HistoryItem*> PinnedBarItemWithCustomButton(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
rpl::producer<PinnedId> id) {
|
rpl::producer<PinnedId> id) {
|
||||||
return rpl::make_producer<HistoryItem*>([=,
|
return rpl::make_producer<HistoryItem*>([=,
|
||||||
|
@ -187,7 +233,7 @@ rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
|
||||||
consumer.put_next(nullptr);
|
consumer.put_next(nullptr);
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
bool hasReplyMarkup = false;
|
bool hasCustomButton = false;
|
||||||
base::has_weak_ptr guard;
|
base::has_weak_ptr guard;
|
||||||
rpl::lifetime lifetime;
|
rpl::lifetime lifetime;
|
||||||
FullMsgId resolvedId;
|
FullMsgId resolvedId;
|
||||||
|
@ -196,10 +242,17 @@ rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
|
||||||
|
|
||||||
const auto pushUnique = [=](not_null<HistoryItem*> item) {
|
const auto pushUnique = [=](not_null<HistoryItem*> item) {
|
||||||
const auto replyMarkup = item->inlineReplyMarkup();
|
const auto replyMarkup = item->inlineReplyMarkup();
|
||||||
if (!state->hasReplyMarkup && !replyMarkup) {
|
const auto media = item->media();
|
||||||
|
const auto page = media ? media->webpage() : nullptr;
|
||||||
|
const auto possiblyHasCustomButton = replyMarkup
|
||||||
|
|| (page
|
||||||
|
&& (page->type == WebPageType::VoiceChat
|
||||||
|
|| page->type == WebPageType::Livestream
|
||||||
|
|| page->type == WebPageType::ConferenceCall));
|
||||||
|
if (!state->hasCustomButton && !possiblyHasCustomButton) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state->hasReplyMarkup = (replyMarkup != nullptr);
|
state->hasCustomButton = possiblyHasCustomButton;
|
||||||
consumer.put_next(item.get());
|
consumer.put_next(item.get());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -217,12 +270,14 @@ rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
|
||||||
using Update = Data::MessageUpdate;
|
using Update = Data::MessageUpdate;
|
||||||
session->changes().messageUpdates(
|
session->changes().messageUpdates(
|
||||||
item,
|
item,
|
||||||
Update::Flag::ReplyMarkup | Update::Flag::Destroyed
|
(Update::Flag::ReplyMarkup
|
||||||
|
| Update::Flag::Edited
|
||||||
|
| Update::Flag::Destroyed)
|
||||||
) | rpl::start_with_next([=](const Update &update) {
|
) | rpl::start_with_next([=](const Update &update) {
|
||||||
if (update.flags & Update::Flag::Destroyed) {
|
if (update.flags & Update::Flag::Destroyed) {
|
||||||
state->lifetime.destroy();
|
state->lifetime.destroy();
|
||||||
invalidate_weak_ptrs(&state->guard);
|
invalidate_weak_ptrs(&state->guard);
|
||||||
state->hasReplyMarkup = false;
|
state->hasCustomButton = false;
|
||||||
consumer.put_next(nullptr);
|
consumer.put_next(nullptr);
|
||||||
} else {
|
} else {
|
||||||
pushUnique(update.item);
|
pushUnique(update.item);
|
||||||
|
@ -248,4 +303,42 @@ rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] object_ptr<Ui::RoundButton> CreatePinnedBarCustomButton(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
HistoryItem *item,
|
||||||
|
Fn<ClickHandlerContext(FullMsgId)> context) {
|
||||||
|
if (!item) {
|
||||||
|
return nullptr;
|
||||||
|
} else if (const auto replyMarkup = item->inlineReplyMarkup()) {
|
||||||
|
const auto &rows = replyMarkup->data.rows;
|
||||||
|
if ((rows.size() == 1) && (rows.front().size() == 1)) {
|
||||||
|
const auto text = rows.front().front().text;
|
||||||
|
if (!text.isEmpty()) {
|
||||||
|
const auto contextId = item->fullId();
|
||||||
|
const auto callback = [=] {
|
||||||
|
Api::ActivateBotCommand(context(contextId), 0, 0);
|
||||||
|
};
|
||||||
|
return MakePinnedBarCustomButton(parent, text, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (const auto media = item->media()) {
|
||||||
|
if (const auto page = media->webpage()) {
|
||||||
|
if (page->type == WebPageType::VoiceChat
|
||||||
|
|| page->type == WebPageType::Livestream
|
||||||
|
|| page->type == WebPageType::ConferenceCall) {
|
||||||
|
const auto url = page->url;
|
||||||
|
const auto contextId = item->fullId();
|
||||||
|
const auto callback = [=] {
|
||||||
|
UrlClickHandler::Open(
|
||||||
|
url,
|
||||||
|
QVariant::fromValue(context(contextId)));
|
||||||
|
};
|
||||||
|
const auto text = tr::lng_group_call_join(tr::now);
|
||||||
|
return MakePinnedBarCustomButton(parent, text, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
|
@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/object_ptr.h"
|
||||||
#include "ui/chat/message_bar.h"
|
#include "ui/chat/message_bar.h"
|
||||||
|
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
|
struct ClickHandlerContext;
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
class Session;
|
class Session;
|
||||||
} // namespace Main
|
} // namespace Main
|
||||||
|
@ -18,6 +21,7 @@ class Session;
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class IconButton;
|
class IconButton;
|
||||||
class PlainShadow;
|
class PlainShadow;
|
||||||
|
class RoundButton;
|
||||||
struct MessageBarContent;
|
struct MessageBarContent;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
|
@ -51,8 +55,13 @@ struct PinnedId {
|
||||||
rpl::producer<PinnedId> id,
|
rpl::producer<PinnedId> id,
|
||||||
Fn<void()> repaint);
|
Fn<void()> repaint);
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
|
[[nodiscard]] rpl::producer<HistoryItem*> PinnedBarItemWithCustomButton(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
rpl::producer<PinnedId> id);
|
rpl::producer<PinnedId> id);
|
||||||
|
|
||||||
|
[[nodiscard]] object_ptr<Ui::RoundButton> CreatePinnedBarCustomButton(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
HistoryItem *item,
|
||||||
|
Fn<ClickHandlerContext(FullMsgId)> context);
|
||||||
|
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
|
@ -1846,12 +1846,12 @@ void RepliesWidget::checkPinnedBarState() {
|
||||||
}
|
}
|
||||||
return (count > 1);
|
return (count > 1);
|
||||||
}) | rpl::distinct_until_changed();
|
}) | rpl::distinct_until_changed();
|
||||||
auto markupRefreshed = HistoryView::PinnedBarItemWithReplyMarkup(
|
auto customButtonItem = HistoryView::PinnedBarItemWithCustomButton(
|
||||||
&session(),
|
&session(),
|
||||||
_pinnedTracker->shownMessageId());
|
_pinnedTracker->shownMessageId());
|
||||||
rpl::combine(
|
rpl::combine(
|
||||||
rpl::duplicate(pinnedRefreshed),
|
rpl::duplicate(pinnedRefreshed),
|
||||||
rpl::duplicate(markupRefreshed)
|
rpl::duplicate(customButtonItem)
|
||||||
) | rpl::start_with_next([=](bool many, HistoryItem *item) {
|
) | rpl::start_with_next([=](bool many, HistoryItem *item) {
|
||||||
refreshPinnedBarButton(many, item);
|
refreshPinnedBarButton(many, item);
|
||||||
}, _pinnedBar->lifetime());
|
}, _pinnedBar->lifetime());
|
||||||
|
@ -1862,7 +1862,7 @@ void RepliesWidget::checkPinnedBarState() {
|
||||||
_pinnedTracker->shownMessageId(),
|
_pinnedTracker->shownMessageId(),
|
||||||
[bar = _pinnedBar.get()] { bar->customEmojiRepaint(); }),
|
[bar = _pinnedBar.get()] { bar->customEmojiRepaint(); }),
|
||||||
std::move(pinnedRefreshed),
|
std::move(pinnedRefreshed),
|
||||||
std::move(markupRefreshed),
|
std::move(customButtonItem),
|
||||||
_rootVisible.value()
|
_rootVisible.value()
|
||||||
) | rpl::map([=](Ui::MessageBarContent &&content, auto, auto, bool show) {
|
) | rpl::map([=](Ui::MessageBarContent &&content, auto, auto, bool show) {
|
||||||
const auto shown = !content.title.isEmpty() && !content.text.empty();
|
const auto shown = !content.title.isEmpty() && !content.text.empty();
|
||||||
|
@ -1943,43 +1943,29 @@ void RepliesWidget::refreshPinnedBarButton(bool many, HistoryItem *item) {
|
||||||
controller()->showSection(
|
controller()->showSection(
|
||||||
std::make_shared<PinnedMemento>(_topic, id.message.msg));
|
std::make_shared<PinnedMemento>(_topic, id.message.msg));
|
||||||
};
|
};
|
||||||
if (const auto replyMarkup = item ? item->inlineReplyMarkup() : nullptr) {
|
const auto context = [copy = _inner](FullMsgId itemId) {
|
||||||
const auto &rows = replyMarkup->data.rows;
|
if (const auto raw = copy.data()) {
|
||||||
if ((rows.size() == 1) && (rows.front().size() == 1)) {
|
return raw->prepareClickHandlerContext(itemId);
|
||||||
const auto text = rows.front().front().text;
|
|
||||||
if (!text.isEmpty()) {
|
|
||||||
auto button = object_ptr<Ui::RoundButton>(
|
|
||||||
this,
|
|
||||||
rpl::single(text),
|
|
||||||
st::historyPinnedBotButton);
|
|
||||||
button->setTextTransform(
|
|
||||||
Ui::RoundButton::TextTransform::NoTransform);
|
|
||||||
button->setFullRadius(true);
|
|
||||||
button->setClickedCallback([=] {
|
|
||||||
Api::ActivateBotCommand(
|
|
||||||
_inner->prepareClickHandlerContext(item->fullId()),
|
|
||||||
0,
|
|
||||||
0);
|
|
||||||
});
|
|
||||||
if (button->width() > st::historyPinnedBotButtonMaxWidth) {
|
|
||||||
button->setFullWidth(st::historyPinnedBotButtonMaxWidth);
|
|
||||||
}
|
|
||||||
struct State {
|
|
||||||
base::unique_qptr<Ui::PopupMenu> menu;
|
|
||||||
};
|
|
||||||
const auto state = button->lifetime().make_state<State>();
|
|
||||||
_pinnedBar->contextMenuRequested(
|
|
||||||
) | rpl::start_with_next([=, raw = button.data()] {
|
|
||||||
state->menu = base::make_unique_q<Ui::PopupMenu>(raw);
|
|
||||||
state->menu->addAction(
|
|
||||||
tr::lng_settings_events_pinned(tr::now),
|
|
||||||
openSection);
|
|
||||||
state->menu->popup(QCursor::pos());
|
|
||||||
}, button->lifetime());
|
|
||||||
_pinnedBar->setRightButton(std::move(button));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return ClickHandlerContext();
|
||||||
|
};
|
||||||
|
auto customButton = CreatePinnedBarCustomButton(this, item, context);
|
||||||
|
if (customButton) {
|
||||||
|
struct State {
|
||||||
|
base::unique_qptr<Ui::PopupMenu> menu;
|
||||||
|
};
|
||||||
|
const auto buttonRaw = customButton.data();
|
||||||
|
const auto state = buttonRaw->lifetime().make_state<State>();
|
||||||
|
_pinnedBar->contextMenuRequested(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
state->menu = base::make_unique_q<Ui::PopupMenu>(buttonRaw);
|
||||||
|
state->menu->addAction(
|
||||||
|
tr::lng_settings_events_pinned(tr::now),
|
||||||
|
openSection);
|
||||||
|
state->menu->popup(QCursor::pos());
|
||||||
|
}, buttonRaw->lifetime());
|
||||||
|
_pinnedBar->setRightButton(std::move(customButton));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const auto close = !many;
|
const auto close = !many;
|
||||||
auto button = object_ptr<Ui::IconButton>(
|
auto button = object_ptr<Ui::IconButton>(
|
||||||
|
|
|
@ -1624,7 +1624,7 @@ void TopBarWidget::refreshUnreadBadge() {
|
||||||
geometry.y() + st::titleUnreadCounterTop);
|
geometry.y() + st::titleUnreadCounterTop);
|
||||||
}, _unreadBadge->lifetime());
|
}, _unreadBadge->lifetime());
|
||||||
|
|
||||||
_unreadBadge->show();
|
_unreadBadge->setVisible(!rootChatsListBar());
|
||||||
_unreadBadge->setAttribute(Qt::WA_TransparentForMouseEvents);
|
_unreadBadge->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
_controller->session().data().unreadBadgeChanges(
|
_controller->session().data().unreadBadgeChanges(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
|
|
|
@ -372,10 +372,16 @@ void TranslateBar::setup(not_null<History*> history) {
|
||||||
const auto&,
|
const auto&,
|
||||||
const auto&) {
|
const auto&) {
|
||||||
using Flag = PeerData::TranslationFlag;
|
using Flag = PeerData::TranslationFlag;
|
||||||
|
const auto automatic = history->peer->autoTranslation();
|
||||||
return (history->peer->translationFlag() != Flag::Enabled)
|
return (history->peer->translationFlag() != Flag::Enabled)
|
||||||
? rpl::single(QString())
|
? rpl::single(QString())
|
||||||
: history->translatedTo()
|
: history->translatedTo()
|
||||||
? tr::lng_translate_show_original()
|
? (automatic
|
||||||
|
? tr::lng_translate_return_original(
|
||||||
|
lt_language,
|
||||||
|
rpl::single(Ui::LanguageName(
|
||||||
|
history->translateOfferedFrom())))
|
||||||
|
: tr::lng_translate_show_original())
|
||||||
: history->translateOfferedFrom()
|
: history->translateOfferedFrom()
|
||||||
? Ui::TranslateBarTo(to)
|
? Ui::TranslateBarTo(to)
|
||||||
: rpl::single(QString());
|
: rpl::single(QString());
|
||||||
|
|
|
@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/core_settings.h"
|
#include "core/core_settings.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
|
#include "data/data_channel.h"
|
||||||
|
#include "data/data_flags.h"
|
||||||
#include "data/data_peer_values.h" // Data::AmPremiumValue.
|
#include "data/data_peer_values.h" // Data::AmPremiumValue.
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
|
@ -51,14 +53,21 @@ void TranslateTracker::setup() {
|
||||||
const auto peer = _history->peer;
|
const auto peer = _history->peer;
|
||||||
peer->updateFull();
|
peer->updateFull();
|
||||||
|
|
||||||
|
const auto channel = peer->asChannel();
|
||||||
|
auto autoTranslationValue = (channel
|
||||||
|
? (channel->flagsValue() | rpl::type_erased())
|
||||||
|
: rpl::single(Data::Flags<ChannelDataFlags>::Change({}, {}))
|
||||||
|
) | rpl::map([=](Data::Flags<ChannelDataFlags>::Change data) {
|
||||||
|
return (data.value & ChannelDataFlag::AutoTranslation);
|
||||||
|
}) | rpl::distinct_until_changed();
|
||||||
|
|
||||||
using namespace rpl::mappers;
|
using namespace rpl::mappers;
|
||||||
_trackingLanguage = rpl::combine(
|
_trackingLanguage = rpl::combine(
|
||||||
Data::AmPremiumValue(&_history->session()),
|
|
||||||
Core::App().settings().translateChatEnabledValue(),
|
Core::App().settings().translateChatEnabledValue(),
|
||||||
_1 && _2);
|
Data::AmPremiumValue(&_history->session()),
|
||||||
|
std::move(autoTranslationValue),
|
||||||
_trackingLanguage.value(
|
_1 && (_2 || _3));
|
||||||
) | rpl::start_with_next([=](bool tracking) {
|
_trackingLanguage.value() | rpl::start_with_next([=](bool tracking) {
|
||||||
_trackingLifetime.destroy();
|
_trackingLifetime.destroy();
|
||||||
if (tracking) {
|
if (tracking) {
|
||||||
recognizeCollected();
|
recognizeCollected();
|
||||||
|
@ -101,7 +110,7 @@ bool TranslateTracker::add(
|
||||||
bool skipDependencies) {
|
bool skipDependencies) {
|
||||||
Expects(_addedInBunch >= 0);
|
Expects(_addedInBunch >= 0);
|
||||||
|
|
||||||
if (item->out()
|
if ((item->out() && !item->history()->peer->autoTranslation())
|
||||||
|| item->isService()
|
|| item->isService()
|
||||||
|| !item->isRegular()
|
|| !item->isRegular()
|
||||||
|| item->isOnlyEmojiAndSpaces()) {
|
|| item->isOnlyEmojiAndSpaces()) {
|
||||||
|
|
|
@ -172,7 +172,9 @@ QSize GroupedMedia::countOptimalSize() {
|
||||||
_parts[i].sides = item.sides;
|
_parts[i].sides = item.sides;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_mode == Mode::Column && _parts.back().item->emptyText()) {
|
if (_mode == Mode::Column
|
||||||
|
&& isBubbleBottom()
|
||||||
|
&& _parts.back().item->emptyText()) {
|
||||||
const auto item = _parent->data();
|
const auto item = _parent->data();
|
||||||
const auto msgsigned = item->Get<HistoryMessageSigned>();
|
const auto msgsigned = item->Get<HistoryMessageSigned>();
|
||||||
const auto views = item->Get<HistoryMessageViews>();
|
const auto views = item->Get<HistoryMessageViews>();
|
||||||
|
@ -236,7 +238,9 @@ QSize GroupedMedia::countCurrentSize(int newWidth) {
|
||||||
accumulate_max(newHeight, top + height);
|
accumulate_max(newHeight, top + height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_mode == Mode::Column && _parts.back().item->emptyText()) {
|
if (_mode == Mode::Column
|
||||||
|
&& isBubbleBottom()
|
||||||
|
&& _parts.back().item->emptyText()) {
|
||||||
const auto item = _parent->data();
|
const auto item = _parent->data();
|
||||||
const auto msgsigned = item->Get<HistoryMessageSigned>();
|
const auto msgsigned = item->Get<HistoryMessageSigned>();
|
||||||
const auto views = item->Get<HistoryMessageViews>();
|
const auto views = item->Get<HistoryMessageViews>();
|
||||||
|
|