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
|
||||
|
||||
## API Schema
|
||||
|
||||
The API definitions are described using [TL Language](https://core.telegram.org/mtproto/TL) in two main schema files:
|
||||
The API definitions are described using [TL Language]\(https:/core.telegram.org/mtproto/TL) in two main schema files:
|
||||
|
||||
1. **`Telegram/SourceFiles/mtproto/scheme/mtproto.tl`**
|
||||
* Defines the core MTProto protocol types and methods used for basic communication, encryption, authorization, service messages, etc.
|
||||
|
@ -39,7 +44,7 @@ api().request(MTPnamespace_MethodName(
|
|||
MTP_long(randomId),
|
||||
// ... other arguments matching the TL definition
|
||||
MTP_vector<MTPMessageEntity>() // Example for a vector argument
|
||||
)).done([=](const MTPResponseType &result) {
|
||||
)).done([=]\(const MTPResponseType &result) {
|
||||
// Handle the successful response (result).
|
||||
// 'result' will be of the C++ type corresponding to the TL type
|
||||
// specified after the '=' in the api.tl method definition.
|
||||
|
@ -47,9 +52,9 @@ api().request(MTPnamespace_MethodName(
|
|||
|
||||
// 1. Multiple Constructors (e.g., User = User | UserEmpty):
|
||||
// Use .match() with lambdas for each constructor:
|
||||
result.match([&](const MTPDuser &data) {
|
||||
result.match([&]\(const MTPDuser &data) {
|
||||
/* use data.vfirst_name().v, etc. */
|
||||
}, [&](const MTPDuserEmpty &data) {
|
||||
}, [&]\(const MTPDuserEmpty &data) {
|
||||
/* handle empty user */
|
||||
});
|
||||
|
||||
|
@ -64,7 +69,7 @@ api().request(MTPnamespace_MethodName(
|
|||
|
||||
// 2. Single Constructor (e.g., Messages = messages { msgs: vector<Message> }):
|
||||
// Use .match() with a single lambda:
|
||||
result.match([&](const MTPDmessages &data) { /* use data.vmessages().v */ });
|
||||
result.match([&]\(const MTPDmessages &data) { /* use data.vmessages().v */ });
|
||||
|
||||
// Or check the type explicitly and use the constructor getter:
|
||||
if (result.type() == mtpc_messages) {
|
||||
|
@ -76,7 +81,7 @@ api().request(MTPnamespace_MethodName(
|
|||
const auto &data = result.data(); // Only works for single-constructor types!
|
||||
// use data.vmessages().v
|
||||
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
}).fail([=]\(const MTP::Error &error) {
|
||||
// Handle the API error (error).
|
||||
// 'error' is an MTP::Error object containing the error code (error.type())
|
||||
// and description (error.description()). Check for specific error strings.
|
||||
|
@ -93,8 +98,8 @@ api().request(MTPnamespace_MethodName(
|
|||
* Always refer to `Telegram/SourceFiles/mtproto/scheme/api.tl` for the correct method names, parameters (names and types), and response types.
|
||||
* Use the generated `MTP...` types/classes for request parameters (e.g., `MTP_int`, `MTP_string`, `MTP_bool`, `MTP_vector`, `MTPInputUser`, etc.) and response handling.
|
||||
* The `.done()` lambda receives the specific C++ `MTP...` type corresponding to the TL return type.
|
||||
* For types with **multiple constructors** (e.g., `User = User | UserEmpty`), use `result.match([&](const MTPDuser &d){ ... }, [&](const MTPDuserEmpty &d){ ... })` to handle each case, or check `result.type() == mtpc_user` / `mtpc_userEmpty` and call the specific `result.c_user()` / `result.c_userEmpty()` getter (which asserts on type mismatch).
|
||||
* For types with a **single constructor** (e.g., `Messages = messages{...}`), you can use `result.match([&](const MTPDmessages &d){ ... })` with one lambda, or check `type()` and call `c_messages()`, or use the shortcut `result.data()` to access the fields directly.
|
||||
* For types with **multiple constructors** (e.g., `User = User | UserEmpty`), use `result.match([&]\(const MTPDuser &d){ ... }, [&]\(const MTPDuserEmpty &d){ ... })` to handle each case, or check `result.type() == mtpc_user` / `mtpc_userEmpty` and call the specific `result.c_user()` / `result.c_userEmpty()` getter (which asserts on type mismatch).
|
||||
* For types with a **single constructor** (e.g., `Messages = messages{...}`), you can use `result.match([&]\(const MTPDmessages &d){ ... })` with one lambda, or check `type()` and call `c_messages()`, or use the shortcut `result.data()` to access the fields directly.
|
||||
* The `.fail()` lambda receives an `MTP::Error` object. Check `error.type()` against known error strings (often defined as constants or using `u"..."_q` literals).
|
||||
* Directly construct the `MTPnamespace_MethodName(...)` object inside `request()`.
|
||||
* Include `.handleFloodErrors()` before `.send()` for standard flood wait handling.
|
||||
* 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
|
||||
|
||||
## Coding Style Note
|
||||
|
@ -156,4 +161,4 @@ auto messageProducer = tr::lng_user_posted_photo( // Type: rpl::producer<TextWit
|
|||
* Immediate: Pass `int` or `float64`.
|
||||
* 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.
|
||||
* 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
|
||||
|
||||
## Coding Style Note
|
||||
|
@ -64,7 +69,7 @@ rpl::lifetime lifetime;
|
|||
// Counter is consumed here, use std::move if it's an l-value.
|
||||
std::move(
|
||||
counter
|
||||
) | rpl::start_with_next([=](int nextValue) {
|
||||
) | rpl::start_with_next([=]\(int nextValue) {
|
||||
// Process the next integer value emitted by the producer.
|
||||
qDebug() << "Received: " << nextValue;
|
||||
}, lifetime); // Pass the lifetime to manage the subscription.
|
||||
|
@ -77,7 +82,7 @@ std::move(
|
|||
auto counter2 = /* ... */; // Type: rpl::producer<int>
|
||||
rpl::lifetime subscriptionLifetime = std::move(
|
||||
counter2
|
||||
) | rpl::start_with_next([=](int nextValue) { /* ... */ });
|
||||
) | rpl::start_with_next([=]\(int nextValue) { /* ... */ });
|
||||
// The returned lifetime MUST be stored. If it's discarded immediately,
|
||||
// the subscription stops instantly.
|
||||
// `counter2` is also moved-from here.
|
||||
|
@ -93,7 +98,7 @@ rpl::lifetime lifetime;
|
|||
// If it's the only use, std::move(dataStream) would be preferred.
|
||||
rpl::duplicate(
|
||||
dataStream
|
||||
) | rpl::start_with_error([=](Error &&error) {
|
||||
) | rpl::start_with_error([=]\(Error &&error) {
|
||||
// Handle the error signaled by the producer.
|
||||
qDebug() << "Error: " << error.text();
|
||||
}, lifetime);
|
||||
|
@ -101,7 +106,7 @@ rpl::duplicate(
|
|||
// Using dataStream again, perhaps duplicated again or moved if last use.
|
||||
rpl::duplicate(
|
||||
dataStream
|
||||
) | rpl::start_with_done([=]() {
|
||||
) | rpl::start_with_done([=] {
|
||||
// Execute when the producer signals it's finished emitting values.
|
||||
qDebug() << "Stream finished.";
|
||||
}, lifetime);
|
||||
|
@ -110,9 +115,9 @@ rpl::duplicate(
|
|||
std::move(
|
||||
dataStream
|
||||
) | rpl::start_with_next_error_done(
|
||||
[=](QString &&value) { /* handle next value */ },
|
||||
[=](Error &&error) { /* handle error */ },
|
||||
[=]() { /* handle done */ },
|
||||
[=]\(QString &&value) { /* handle next value */ },
|
||||
[=]\(Error &&error) { /* handle error */ },
|
||||
[=] { /* handle done */ },
|
||||
lifetime);
|
||||
```
|
||||
|
||||
|
@ -164,7 +169,7 @@ You can combine multiple producers into one:
|
|||
// The lambda receives unpacked arguments, not the tuple itself.
|
||||
std::move(
|
||||
combined
|
||||
) | rpl::start_with_next([=](int count, const QString &text) {
|
||||
) | rpl::start_with_next([=]\(int count, const QString &text) {
|
||||
// No need for std::get<0>(latest), etc.
|
||||
qDebug() << "Combined: Count=" << count << ", Text=" << text;
|
||||
}, lifetime);
|
||||
|
@ -172,11 +177,11 @@ You can combine multiple producers into one:
|
|||
// This also works with map, filter, etc.
|
||||
std::move(
|
||||
combined
|
||||
) | rpl::filter([=](int count, const QString &text) {
|
||||
) | rpl::filter([=]\(int count, const QString &text) {
|
||||
return count > 0 && !text.isEmpty();
|
||||
}) | rpl::map([=](int count, const QString &text) {
|
||||
}) | rpl::map([=]\(int count, const QString &text) {
|
||||
return text.repeated(count);
|
||||
}) | rpl::start_with_next([=](const QString &result) {
|
||||
}) | rpl::start_with_next([=]\(const QString &result) {
|
||||
qDebug() << "Mapped & Filtered: " << result;
|
||||
}, lifetime);
|
||||
```
|
||||
|
@ -192,7 +197,7 @@ You can combine multiple producers into one:
|
|||
// Starting the merged producer consumes it.
|
||||
std::move(
|
||||
merged
|
||||
) | rpl::start_with_next([=](QString &&value) {
|
||||
) | rpl::start_with_next([=]\(QString &&value) {
|
||||
// Receives values from either sourceA or sourceB as they arrive.
|
||||
qDebug() << "Merged value: " << value;
|
||||
}, lifetime);
|
||||
|
@ -208,4 +213,4 @@ You can combine multiple producers into one:
|
|||
* Use `rpl::combine` or `rpl::merge` to combine streams.
|
||||
* When starting a chain (`std::move(producer) | rpl::map(...)`), explicitly move the initial producer.
|
||||
* These functions typically duplicate their input producers internally.
|
||||
* Starting a pipeline consumes the producer; use `
|
||||
* 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
|
||||
|
||||
## Style Definition Files
|
||||
|
@ -146,4 +151,4 @@ void MyWidget::paintEvent(QPaintEvent *e) {
|
|||
* Icons are defined inline using `name: icon{{ "path_stem", color }};` or `name: icon{ { "path1", c1 }, ... };` syntax, with optional path modifiers.
|
||||
* Code generation creates `struct` definitions in the `style` namespace for custom types and objects/structs in the `st` namespace for defined variables/groups.
|
||||
* Generated headers are in `styles/` with a `style_` prefix and must be included.
|
||||
* Access style properties via the generated `st::` objects (e.g., `st::primaryButton.height`, `st::chatInput.backgroundColor`).
|
||||
* Access style properties via the generated `st::` objects (e.g., `st::primaryButton.height`, `st::chatInput.backgroundColor`).
|
|
@ -5,11 +5,9 @@
|
|||
"vscode": {
|
||||
"settings": {
|
||||
"C_Cpp.intelliSenseEngine": "disabled",
|
||||
"clangd.arguments": [
|
||||
"--compile-commands-dir=${workspaceFolder}/out"
|
||||
],
|
||||
"cmake.generator": "Ninja Multi-Config",
|
||||
"cmake.buildDirectory": "${workspaceFolder}/out"
|
||||
"cmake.buildDirectory": "${workspaceFolder}/out",
|
||||
"cmake.copyCompileCommands": "${workspaceFolder}/compile_commands.json"
|
||||
},
|
||||
"extensions": [
|
||||
"ms-vscode.cpptools-extension-pack",
|
||||
|
|
54
.github/workflows/linux.yml
vendored
|
@ -12,6 +12,7 @@ on:
|
|||
- '!.github/workflows/linux.yml'
|
||||
- 'snap/**'
|
||||
- 'Telegram/build/**'
|
||||
- '!Telegram/build/docker/centos_env/**'
|
||||
- 'Telegram/Resources/uwp/**'
|
||||
- 'Telegram/Resources/winrc/**'
|
||||
- 'Telegram/SourceFiles/platform/win/**'
|
||||
|
@ -30,6 +31,7 @@ on:
|
|||
- '!.github/workflows/linux.yml'
|
||||
- 'snap/**'
|
||||
- 'Telegram/build/**'
|
||||
- '!Telegram/build/docker/centos_env/**'
|
||||
- 'Telegram/Resources/uwp/**'
|
||||
- 'Telegram/Resources/winrc/**'
|
||||
- 'Telegram/SourceFiles/platform/win/**'
|
||||
|
@ -52,27 +54,44 @@ jobs:
|
|||
|
||||
env:
|
||||
UPLOAD_ARTIFACT: "true"
|
||||
ONLY_CACHE: "false"
|
||||
IMAGE_TAG: tdesktop:centos_env
|
||||
|
||||
steps:
|
||||
- name: Get repository name.
|
||||
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
path: ${{ env.REPO_NAME }}
|
||||
|
||||
- name: First set up.
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
|
||||
docker pull ghcr.io/$GITHUB_REPOSITORY/centos_env
|
||||
docker tag ghcr.io/$GITHUB_REPOSITORY/centos_env tdesktop:centos_env
|
||||
sudo apt update
|
||||
curl -sSL https://install.python-poetry.org | python3 -
|
||||
cd Telegram/build/docker/centos_env
|
||||
poetry install
|
||||
DOCKERFILE=$(DEBUG= LTO= poetry run gen_dockerfile)
|
||||
echo "$DOCKERFILE" > Dockerfile
|
||||
|
||||
- name: Free up some disk space.
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
with:
|
||||
tool-cache: true
|
||||
|
||||
- name: Set up Docker Buildx.
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Libraries.
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: Telegram/build/docker/centos_env
|
||||
load: ${{ env.ONLY_CACHE == 'false' }}
|
||||
tags: ${{ env.IMAGE_TAG }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Telegram Desktop build.
|
||||
if: env.ONLY_CACHE == 'false'
|
||||
run: |
|
||||
cd $REPO_NAME
|
||||
|
||||
DEFINE=""
|
||||
if [ -n "${{ matrix.defines }}" ]; then
|
||||
DEFINE="-D ${{ matrix.defines }}=ON"
|
||||
|
@ -86,21 +105,22 @@ jobs:
|
|||
-u $(id -u) \
|
||||
-v $PWD:/usr/src/tdesktop \
|
||||
-e CONFIG=Debug \
|
||||
tdesktop:centos_env \
|
||||
$IMAGE_TAG \
|
||||
/usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \
|
||||
-D CMAKE_C_FLAGS_DEBUG="" \
|
||||
-D CMAKE_CXX_FLAGS_DEBUG="" \
|
||||
-D CMAKE_C_FLAGS="-Werror" \
|
||||
-D CMAKE_CXX_FLAGS="-Werror" \
|
||||
-D CMAKE_CONFIGURATION_TYPES=Debug \
|
||||
-D CMAKE_C_FLAGS_DEBUG="-O0 -U_FORTIFY_SOURCE" \
|
||||
-D CMAKE_CXX_FLAGS_DEBUG="-O0 -U_FORTIFY_SOURCE" \
|
||||
-D CMAKE_EXE_LINKER_FLAGS="-s" \
|
||||
-D CMAKE_COMPILE_WARNING_AS_ERROR=ON \
|
||||
-D TDESKTOP_API_TEST=ON \
|
||||
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \
|
||||
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
|
||||
$DEFINE
|
||||
|
||||
- name: Check.
|
||||
if: env.ONLY_CACHE == 'false'
|
||||
run: |
|
||||
filePath="$REPO_NAME/out/Debug/Telegram"
|
||||
filePath="out/Debug/Telegram"
|
||||
if test -f "$filePath"; then
|
||||
echo "Build successfully done! :)"
|
||||
|
||||
|
@ -114,7 +134,7 @@ jobs:
|
|||
- name: Move artifact.
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
run: |
|
||||
cd $REPO_NAME/out/Debug
|
||||
cd out/Debug
|
||||
mkdir artifact
|
||||
mv {Telegram,Updater} artifact/
|
||||
- uses: actions/upload-artifact@v4
|
||||
|
@ -122,4 +142,4 @@ jobs:
|
|||
name: Upload artifact.
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_NAME }}
|
||||
path: ${{ env.REPO_NAME }}/out/Debug/artifact/
|
||||
path: out/Debug/artifact/
|
||||
|
|
6
.github/workflows/win.yml
vendored
|
@ -177,11 +177,11 @@ jobs:
|
|||
%TDESKTOP_BUILD_GENERATOR% ^
|
||||
%TDESKTOP_BUILD_ARCH% ^
|
||||
%TDESKTOP_BUILD_API% ^
|
||||
-D CMAKE_C_FLAGS="/WX" ^
|
||||
-D CMAKE_CXX_FLAGS="/WX" ^
|
||||
-D CMAKE_CONFIGURATION_TYPES=Debug ^
|
||||
-D CMAKE_COMPILE_WARNING_AS_ERROR=ON ^
|
||||
-D CMAKE_MSVC_DEBUG_INFORMATION_FORMAT= ^
|
||||
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^
|
||||
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
|
||||
-D DESKTOP_APP_NO_PDB=ON ^
|
||||
%TDESKTOP_BUILD_DEFINE%
|
||||
|
||||
cmake --build ..\out --config Debug --parallel
|
||||
|
|
1
.gitignore
vendored
|
@ -20,6 +20,7 @@ ipch/
|
|||
.vs/
|
||||
.vscode/
|
||||
.cache/
|
||||
compile_commands.json
|
||||
|
||||
/Telegram/log.txt
|
||||
/Telegram/data
|
||||
|
|
|
@ -4,15 +4,7 @@
|
|||
# For license and copyright information please follow this link:
|
||||
# https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
if (APPLE)
|
||||
# target_precompile_headers with COMPILE_LANGUAGE restriction.
|
||||
cmake_minimum_required(VERSION 3.23)
|
||||
else()
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
endif()
|
||||
if (POLICY CMP0149)
|
||||
cmake_policy(SET CMP0149 NEW)
|
||||
endif()
|
||||
cmake_minimum_required(VERSION 3.25...3.31)
|
||||
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
|
||||
|
|
|
@ -296,8 +296,8 @@ PRIVATE
|
|||
boxes/peers/edit_contact_box.h
|
||||
boxes/peers/edit_forum_topic_box.cpp
|
||||
boxes/peers/edit_forum_topic_box.h
|
||||
boxes/peers/edit_linked_chat_box.cpp
|
||||
boxes/peers/edit_linked_chat_box.h
|
||||
boxes/peers/edit_discussion_link_box.cpp
|
||||
boxes/peers/edit_discussion_link_box.h
|
||||
boxes/peers/edit_members_visible.cpp
|
||||
boxes/peers/edit_members_visible.h
|
||||
boxes/peers/edit_participant_box.cpp
|
||||
|
@ -583,6 +583,8 @@ PRIVATE
|
|||
data/components/factchecks.h
|
||||
data/components/location_pickers.cpp
|
||||
data/components/location_pickers.h
|
||||
data/components/promo_suggestions.cpp
|
||||
data/components/promo_suggestions.h
|
||||
data/components/recent_peers.cpp
|
||||
data/components/recent_peers.h
|
||||
data/components/scheduled_messages.cpp
|
||||
|
|
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_az" = "Azerbaijani";
|
||||
"cloud_lng_passport_in_bg" = "Bulgarian";
|
||||
"cloud_lng_passport_in_bn" = "Bangla";
|
||||
"cloud_lng_passport_in_bn" = "Bengali";
|
||||
"cloud_lng_passport_in_cs" = "Czech";
|
||||
"cloud_lng_passport_in_da" = "Danish";
|
||||
"cloud_lng_passport_in_de" = "German";
|
||||
|
@ -64,7 +64,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"cloud_lng_translate_to_ar" = "Arabic";
|
||||
"cloud_lng_translate_to_az" = "Azerbaijani";
|
||||
"cloud_lng_translate_to_bg" = "Bulgarian";
|
||||
// "cloud_lng_translate_to_bn" = "Bangla";
|
||||
// "cloud_lng_translate_to_bn" = "Bengali";
|
||||
"cloud_lng_translate_to_cs" = "Czech";
|
||||
"cloud_lng_translate_to_da" = "Danish";
|
||||
"cloud_lng_translate_to_de" = "German";
|
||||
|
@ -109,50 +109,116 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"cloud_lng_translate_to_uz" = "Uzbek";
|
||||
"cloud_lng_translate_to_vi" = "Vietnamese";
|
||||
|
||||
"cloud_lng_language_af" = "Afrikaans";
|
||||
"cloud_lng_language_am" = "Amharic";
|
||||
"cloud_lng_language_ar" = "Arabic";
|
||||
"cloud_lng_language_az" = "Azerbaijani";
|
||||
"cloud_lng_language_be" = "Belarusian";
|
||||
"cloud_lng_language_bg" = "Bulgarian";
|
||||
// "cloud_lng_language_bn" = "Bangla";
|
||||
"cloud_lng_language_bn" = "Bengali";
|
||||
"cloud_lng_language_bs" = "Bosnian";
|
||||
"cloud_lng_language_ca" = "Catalan";
|
||||
// "cloud_lng_language_ceb" = "Cebuano";
|
||||
"cloud_lng_language_co" = "Corsican";
|
||||
"cloud_lng_language_cs" = "Czech";
|
||||
"cloud_lng_language_cy" = "Welsh";
|
||||
"cloud_lng_language_da" = "Danish";
|
||||
"cloud_lng_language_de" = "German";
|
||||
// "cloud_lng_language_dv" = "Divehi";
|
||||
// "cloud_lng_language_dz" = "Dzongkha";
|
||||
"cloud_lng_language_dv" = "Divehi";
|
||||
"cloud_lng_language_dz" = "Dzongkha";
|
||||
"cloud_lng_language_el" = "Greek";
|
||||
"cloud_lng_language_en" = "English";
|
||||
"cloud_lng_language_eo" = "Esperanto";
|
||||
"cloud_lng_language_es" = "Spanish";
|
||||
"cloud_lng_language_et" = "Estonian";
|
||||
"cloud_lng_language_eu" = "Basque";
|
||||
"cloud_lng_language_fa" = "Persian";
|
||||
"cloud_lng_language_fi" = "Finnish";
|
||||
"cloud_lng_language_fr" = "French";
|
||||
"cloud_lng_language_fy" = "Frisian";
|
||||
"cloud_lng_language_ga" = "Irish";
|
||||
"cloud_lng_language_gd" = "Scots Gaelic";
|
||||
"cloud_lng_language_gl" = "Galician";
|
||||
"cloud_lng_language_gu" = "Gujarati";
|
||||
"cloud_lng_language_ha" = "Hausa";
|
||||
"cloud_lng_language_haw" = "Hawaiian";
|
||||
"cloud_lng_language_he" = "Hebrew";
|
||||
"cloud_lng_language_hi" = "Hindi";
|
||||
// "cloud_lng_language_hmn" = "Hmong";
|
||||
"cloud_lng_language_hr" = "Croatian";
|
||||
"cloud_lng_language_ht" = "Haitian Creole";
|
||||
"cloud_lng_language_hu" = "Hungarian";
|
||||
"cloud_lng_language_hy" = "Armenian";
|
||||
"cloud_lng_language_id" = "Indonesian";
|
||||
"cloud_lng_language_ig" = "Igbo";
|
||||
"cloud_lng_language_is" = "Icelandic";
|
||||
"cloud_lng_language_it" = "Italian";
|
||||
"cloud_lng_language_iw" = "Hebrew (Obsolete code)";
|
||||
"cloud_lng_language_ja" = "Japanese";
|
||||
"cloud_lng_language_jv" = "Javanese";
|
||||
"cloud_lng_language_ka" = "Georgian";
|
||||
// "cloud_lng_language_km" = "Khmer";
|
||||
"cloud_lng_language_kk" = "Kazakh";
|
||||
"cloud_lng_language_km" = "Khmer";
|
||||
"cloud_lng_language_kn" = "Kannada";
|
||||
"cloud_lng_language_ko" = "Korean";
|
||||
"cloud_lng_language_ku" = "Kurdish";
|
||||
"cloud_lng_language_ky" = "Kyrgyz";
|
||||
"cloud_lng_language_la" = "Latin";
|
||||
"cloud_lng_language_lb" = "Luxembourgish";
|
||||
"cloud_lng_language_lo" = "Lao";
|
||||
"cloud_lng_language_lt" = "Lithuanian";
|
||||
"cloud_lng_language_lv" = "Latvian";
|
||||
"cloud_lng_language_mg" = "Malagasy";
|
||||
"cloud_lng_language_mi" = "Maori";
|
||||
"cloud_lng_language_mk" = "Macedonian";
|
||||
"cloud_lng_language_ml" = "Malayalam";
|
||||
"cloud_lng_language_mn" = "Mongolian";
|
||||
"cloud_lng_language_mr" = "Marathi";
|
||||
"cloud_lng_language_ms" = "Malay";
|
||||
"cloud_lng_language_mt" = "Maltese";
|
||||
"cloud_lng_language_my" = "Burmese";
|
||||
"cloud_lng_language_ne" = "Nepali";
|
||||
"cloud_lng_language_nl" = "Dutch";
|
||||
"cloud_lng_language_no" = "Norwegian";
|
||||
"cloud_lng_language_ny" = "Nyanja";
|
||||
"cloud_lng_language_or" = "Odia (Oriya)";
|
||||
"cloud_lng_language_pa" = "Punjabi";
|
||||
"cloud_lng_language_pl" = "Polish";
|
||||
"cloud_lng_language_ps" = "Pashto";
|
||||
"cloud_lng_language_pt" = "Portuguese";
|
||||
"cloud_lng_language_ro" = "Romanian";
|
||||
"cloud_lng_language_ru" = "Russian";
|
||||
"cloud_lng_language_rw" = "Kinyarwanda";
|
||||
"cloud_lng_language_sd" = "Sindhi";
|
||||
"cloud_lng_language_si" = "Sinhala";
|
||||
"cloud_lng_language_sk" = "Slovak";
|
||||
"cloud_lng_language_sl" = "Slovenian";
|
||||
"cloud_lng_language_sm" = "Samoan";
|
||||
"cloud_lng_language_sn" = "Shona";
|
||||
"cloud_lng_language_so" = "Somali";
|
||||
"cloud_lng_language_sq" = "Albanian";
|
||||
"cloud_lng_language_sr" = "Serbian";
|
||||
"cloud_lng_language_st" = "Sesotho";
|
||||
"cloud_lng_language_su" = "Sundanese";
|
||||
"cloud_lng_language_sv" = "Swedish";
|
||||
"cloud_lng_language_sw" = "Swahili";
|
||||
"cloud_lng_language_ta" = "Tamil";
|
||||
"cloud_lng_language_te" = "Telugu";
|
||||
"cloud_lng_language_tg" = "Tajik";
|
||||
"cloud_lng_language_th" = "Thai";
|
||||
"cloud_lng_language_tk" = "Turkmen";
|
||||
"cloud_lng_language_tl" = "Tagalog";
|
||||
"cloud_lng_language_tr" = "Turkish";
|
||||
"cloud_lng_language_tt" = "Tatar";
|
||||
"cloud_lng_language_ug" = "Uyghur";
|
||||
"cloud_lng_language_uk" = "Ukrainian";
|
||||
"cloud_lng_language_ur" = "Urdu";
|
||||
"cloud_lng_language_uz" = "Uzbek";
|
||||
"cloud_lng_language_vi" = "Vietnamese";
|
||||
"cloud_lng_language_xh" = "Xhosa";
|
||||
"cloud_lng_language_yi" = "Yiddish";
|
||||
"cloud_lng_language_yo" = "Yoruba";
|
||||
"cloud_lng_language_zh" = "Chinese";
|
||||
// "cloud_lng_language_zh-CN" = "Chinese (Simplified)";
|
||||
// "cloud_lng_language_zh-TW" = "Chinese (Traditional)";
|
||||
"cloud_lng_language_zu" = "Zulu";
|
||||
|
|
|
@ -836,6 +836,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_settings_disconnect" = "Disconnect";
|
||||
"lng_settings_connected_title" = "Connected websites";
|
||||
|
||||
"lng_settings_suggestion_phone_number_title" = "Is {phone} still your number?";
|
||||
"lng_settings_suggestion_phone_number_about" = "Keep your number up to date to ensure you can always log into Telegram. {link}";
|
||||
"lng_settings_suggestion_phone_number_about_link" = "https://telegram.org/faq#q-i-have-a-new-phone-number-what-do-i-do";
|
||||
"lng_settings_suggestion_phone_number_change" = "Please change your phone number in the official Telegram app on your phone as soon as possible. {emoji}";
|
||||
|
||||
"lng_settings_power_menu" = "Battery and Animations";
|
||||
"lng_settings_power_title" = "Power Usage";
|
||||
"lng_settings_power_subtitle" = "Power saving options";
|
||||
|
@ -2251,6 +2256,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_peer_gifts_title" = "Gifts";
|
||||
"lng_peer_gifts_about" = "These gifts were sent to {user} by other users.";
|
||||
"lng_peer_gifts_about_mine" = "These gifts were sent to you by other users. Click on a gift to convert it to Stars or change its privacy settings.";
|
||||
"lng_peer_gifts_empty_search" = "No matching gifts";
|
||||
"lng_peer_gifts_view_all" = "View All Gifts";
|
||||
"lng_peer_gifts_notify" = "Notify About New Gifts";
|
||||
"lng_peer_gifts_notify_enabled" = "You will receive a message from Telegram when your channel receives a gift.";
|
||||
"lng_peer_gifts_filter_by_value" = "Sort by Value";
|
||||
|
@ -2823,6 +2830,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_credits_box_history_entry_reaction_name" = "Star Reaction";
|
||||
"lng_credits_box_history_entry_subscription" = "Monthly subscription fee";
|
||||
"lng_credits_box_history_entry_gift_upgrade" = "Collectible Upgrade";
|
||||
"lng_credits_box_history_entry_gift_sold" = "Gift Sale";
|
||||
"lng_credits_box_history_entry_gift_bought" = "Gift Purchase";
|
||||
"lng_credits_box_history_entry_gift_sold_to" = "To";
|
||||
"lng_credits_box_history_entry_gift_full_price" = "Full Price";
|
||||
"lng_credits_box_history_entry_gift_bought_from" = "From";
|
||||
|
||||
"lng_credits_subscription_section" = "My subscriptions";
|
||||
"lng_credits_box_subscription_title" = "Subscription";
|
||||
|
@ -2965,6 +2977,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_chatbots_include_button" = "Select Chats";
|
||||
"lng_chatbots_exclude_about" = "Select chats or entire chat categories which the bot will not have access to.";
|
||||
"lng_chatbots_permissions_title" = "Bot permissions";
|
||||
"lng_chatbots_warning_title" = "Warning";
|
||||
"lng_chatbots_warning_both_text" = "The bot {bot} will be able to **manage your gifts and stars**, including giving them away to other users.";
|
||||
"lng_chatbots_warning_gifts_text" = "The bot {bot} will be able to **manage your gifts**, including giving them away to other users.";
|
||||
"lng_chatbots_warning_stars_text" = "The bot {bot} will be able to **transfer your stars**.";
|
||||
"lng_chatbots_warning_username_text" = "The bot {bot} will be able to **set and remove usernames** for your account, which may result in the loss of your current username.";
|
||||
|
||||
"lng_chatbots_manage_messages" = "Manage Messages";
|
||||
"lng_chatbots_read" = "Read Messages";
|
||||
|
@ -3155,6 +3172,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_boost_group_ask" = "Ask your **Premium** members to boost your group with this link:";
|
||||
//"lng_boost_group_gifting" = "Boost your group by gifting your members Telegram Premium. {link}";
|
||||
|
||||
"lng_boost_channel_title_autotranslate" = "Autotranslation of Messages";
|
||||
"lng_boost_channel_needs_level_autotranslate#one" = "Your channel needs to reach **Level {count}** to enable autotranslation of messages.";
|
||||
"lng_boost_channel_needs_level_autotranslate#other" = "Your channel needs to reach **Level {count}** to enable autotranslation of messages.";
|
||||
|
||||
"lng_feature_stories#one" = "**{count}** Story Per Day";
|
||||
"lng_feature_stories#other" = "**{count}** Stories Per Day";
|
||||
"lng_feature_reactions#one" = "**{count}** Custom Reaction";
|
||||
|
@ -3173,6 +3194,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_feature_custom_background_group" = "Custom Group Background";
|
||||
"lng_feature_custom_emoji_pack" = "Custom Emoji Pack";
|
||||
"lng_feature_transcribe" = "Voice-to-Text Conversion";
|
||||
"lng_feature_autotranslate" = "Autotranslation of Messages";
|
||||
|
||||
"lng_giveaway_new_title" = "Boosts via Gifts";
|
||||
"lng_giveaway_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers.";
|
||||
|
@ -3421,10 +3443,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_stars_link" = "What are Stars >";
|
||||
"lng_gift_stars_limited" = "limited";
|
||||
"lng_gift_stars_sold_out" = "sold out";
|
||||
"lng_gift_stars_resale" = "resale";
|
||||
"lng_gift_stars_on_sale" = "on sale";
|
||||
"lng_gift_stars_tabs_all" = "All Gifts";
|
||||
"lng_gift_stars_tabs_my" = "My Gifts";
|
||||
"lng_gift_stars_tabs_limited" = "Limited";
|
||||
"lng_gift_stars_tabs_in_stock" = "In Stock";
|
||||
"lng_gift_stars_tabs_resale" = "Resale";
|
||||
"lng_gift_send_title" = "Send a Gift";
|
||||
"lng_gift_send_message" = "Enter Message";
|
||||
"lng_gift_send_anonymous" = "Hide My Name";
|
||||
|
@ -3446,7 +3471,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_send_limited_left#other" = "{count} left";
|
||||
"lng_gift_send_button" = "Send a Gift for {cost}";
|
||||
"lng_gift_send_button_self" = "Buy a Gift for {cost}";
|
||||
"lng_gift_buy_resale_title" = "Buy {name}";
|
||||
"lng_gift_buy_resale_button" = "Buy for {cost}";
|
||||
"lng_gift_buy_resale_confirm" = "Do you want to buy {name} for {price} and gift it to {user}?";
|
||||
"lng_gift_buy_resale_confirm_self" = "Do you want to buy {name} for {price}?";
|
||||
"lng_gift_buy_price_change_title" = "Price change!";
|
||||
"lng_gift_buy_price_change_text" = "This gift price was changed and now is {price}. Do you still want to buy?";
|
||||
"lng_gift_sent_title" = "Gift Sent!";
|
||||
"lng_gift_sent_resale_done" = "{user} has been notified about your gift.";
|
||||
"lng_gift_sent_resale_done_self" = "{gift} is now yours.";
|
||||
"lng_gift_sent_about#one" = "You spent **{count}** Star from your balance.";
|
||||
"lng_gift_sent_about#other" = "You spent **{count}** Stars from your balance.";
|
||||
"lng_gift_limited_of_one" = "unique";
|
||||
|
@ -3573,6 +3606,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_transfer_button_for" = "Transfer for {price}";
|
||||
"lng_gift_transfer_wear" = "Wear";
|
||||
"lng_gift_transfer_take_off" = "Take Off";
|
||||
"lng_gift_transfer_sell" = "Sell";
|
||||
"lng_gift_transfer_update" = "Change Price";
|
||||
"lng_gift_transfer_unlist" = "Unlist";
|
||||
"lng_gift_sell_unlist_title" = "Unlist {name}";
|
||||
"lng_gift_sell_unlist_sure" = "Are you sure you want to unlist your gift?";
|
||||
"lng_gift_sell_title" = "Price in Stars";
|
||||
"lng_gift_sell_placeholder" = "Enter price in Stars";
|
||||
"lng_gift_sell_about" = "You will receive {percent} of the selected amount.";
|
||||
"lng_gift_sell_amount#one" = "You will receive **{count}** Star.";
|
||||
"lng_gift_sell_amount#other" = "You will receive **{count}** Stars.";
|
||||
"lng_gift_sell_min_price#one" = "Minimum price is {count} Star.";
|
||||
"lng_gift_sell_min_price#other" = "Minimum price is {count} Stars.";
|
||||
"lng_gift_sell_put" = "Put for Sale";
|
||||
"lng_gift_sell_update" = "Update the Price";
|
||||
"lng_gift_sell_toast" = "{name} is now for sale!";
|
||||
"lng_gift_sell_updated" = "Sale price for {name} was updated.";
|
||||
"lng_gift_sell_removed" = "{name} is removed from sale.";
|
||||
"lng_gift_menu_show" = "Show";
|
||||
"lng_gift_menu_hide" = "Hide";
|
||||
"lng_gift_wear_title" = "Wear {name}";
|
||||
|
@ -3589,6 +3639,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_wear_end_toast" = "You took off {name}";
|
||||
"lng_gift_many_pinned_title" = "Too Many Pinned Gifts";
|
||||
"lng_gift_many_pinned_choose" = "Select a gift to unpin below";
|
||||
"lng_gift_resale_price" = "Price";
|
||||
"lng_gift_resale_number" = "Number";
|
||||
"lng_gift_resale_date" = "Date";
|
||||
"lng_gift_resale_count#one" = "{count} gift in resale";
|
||||
"lng_gift_resale_count#other" = "{count} gifts in resale";
|
||||
"lng_gift_resale_sort_price" = "Sort by Price";
|
||||
"lng_gift_resale_sort_date" = "Sort by Date";
|
||||
"lng_gift_resale_sort_number" = "Sort by Number";
|
||||
"lng_gift_resale_filter_all" = "Select All";
|
||||
"lng_gift_resale_model" = "Model";
|
||||
"lng_gift_resale_models#one" = "{count} Model";
|
||||
"lng_gift_resale_models#other" = "{count} Models";
|
||||
"lng_gift_resale_backdrop" = "Backdrop";
|
||||
"lng_gift_resale_backdrops#one" = "{count} Backdrop";
|
||||
"lng_gift_resale_backdrops#other" = "{count} Backdrops";
|
||||
"lng_gift_resale_symbol" = "Symbol";
|
||||
"lng_gift_resale_symbols#one" = "{count} Symbol";
|
||||
"lng_gift_resale_symbols#other" = "{count} Symbols";
|
||||
"lng_gift_resale_early" = "You will be able to resell this gift in {duration}.";
|
||||
"lng_gift_transfer_early" = "You will be able to transfer this gift in {duration}.";
|
||||
"lng_gift_resale_transfer_early_title" = "Try Later";
|
||||
|
||||
"lng_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
|
||||
|
@ -4242,6 +4313,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_sensitive_toast" = "You can update the visibility of sensitive media in **Settings > Chat Settings > Sensitive content**";
|
||||
|
||||
"lng_translate_show_original" = "Show Original";
|
||||
"lng_translate_return_original" = "View Original ({language})";
|
||||
"lng_translate_bar_to" = "Translate to {name}";
|
||||
"lng_translate_bar_to_other" = "Translate to {name}";
|
||||
"lng_translate_menu_to" = "Translate To";
|
||||
|
@ -4349,6 +4421,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_edit_contact_title" = "Edit contact";
|
||||
"lng_edit_channel_title" = "Edit channel";
|
||||
"lng_edit_bot_title" = "Edit bot";
|
||||
"lng_edit_autotranslate" = "Auto-translate messages";
|
||||
"lng_edit_sign_messages" = "Sign messages";
|
||||
"lng_edit_sign_messages_about" = "Add names of admins to the messages they post.";
|
||||
"lng_edit_sign_profiles" = "Show authors' profiles";
|
||||
|
@ -5205,10 +5278,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_admin_log_filter_all_actions" = "All actions";
|
||||
"lng_admin_log_filter_actions_type_subtitle" = "Filter actions by type";
|
||||
"lng_admin_log_filter_actions_member_section" = "Members And Admins";
|
||||
"lng_admin_log_filter_actions_subscriber_section" = "Subscribers And Admins";
|
||||
"lng_admin_log_filter_restrictions" = "New restrictions";
|
||||
"lng_admin_log_filter_admins_new" = "Admin rights";
|
||||
"lng_admin_log_filter_members_new" = "New members";
|
||||
"lng_admin_log_filter_subscribers_new" = "New subscribers";
|
||||
"lng_admin_log_filter_actions_settings_section" = "Group Settings";
|
||||
"lng_admin_log_filter_actions_channel_settings_section" = "Channel Settings";
|
||||
"lng_admin_log_filter_info_group" = "Group info";
|
||||
"lng_admin_log_filter_info_channel" = "Channel info";
|
||||
"lng_admin_log_filter_actions_messages_section" = "Messages";
|
||||
|
@ -5219,6 +5295,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_admin_log_filter_voice_chats_channel" = "Live stream";
|
||||
"lng_admin_log_filter_invite_links" = "Invite links";
|
||||
"lng_admin_log_filter_members_removed" = "Members leaving";
|
||||
"lng_admin_log_filter_subscribers_removed" = "Subscribers leaving";
|
||||
"lng_admin_log_filter_topics" = "Topics";
|
||||
"lng_admin_log_filter_sub_extend" = "Subscription Renewals";
|
||||
"lng_admin_log_filter_all_admins" = "All users and admins";
|
||||
|
@ -5329,6 +5406,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_admin_log_participant_volume_channel" = "{from} changed live stream volume for {user} to {percent}";
|
||||
"lng_admin_log_antispam_enabled" = "{from} enabled aggressive anti-spam";
|
||||
"lng_admin_log_antispam_disabled" = "{from} disabled aggressive anti-spam";
|
||||
"lng_admin_log_autotranslate_enabled" = "{from} enabled automatic translation";
|
||||
"lng_admin_log_autotranslate_disabled" = "{from} disabled automatic translation";
|
||||
"lng_admin_log_change_color" = "{from} changed channel color from {previous} to {color}";
|
||||
"lng_admin_log_set_background_emoji" = "{from} set channel background emoji to {emoji}";
|
||||
"lng_admin_log_change_background_emoji" = "{from} changed channel background emoji from {previous} to {emoji}";
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<RCC>
|
||||
<qresource prefix="/animations">
|
||||
<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="cloud_filters.tgs">../../animations/cloud_filters.tgs</file>
|
||||
<file alias="local_passcode_enter.tgs">../../animations/local_passcode_enter.tgs</file>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.14.1.0" />
|
||||
Version="5.14.3.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
|
|
@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,14,1,0
|
||||
PRODUCTVERSION 5,14,1,0
|
||||
FILEVERSION 5,14,3,0
|
||||
PRODUCTVERSION 5,14,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -62,10 +62,10 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop"
|
||||
VALUE "FileVersion", "5.14.1.0"
|
||||
VALUE "FileVersion", "5.14.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.14.1.0"
|
||||
VALUE "ProductVersion", "5.14.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,14,1,0
|
||||
PRODUCTVERSION 5,14,1,0
|
||||
FILEVERSION 5,14,3,0
|
||||
PRODUCTVERSION 5,14,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -53,10 +53,10 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||
VALUE "FileVersion", "5.14.1.0"
|
||||
VALUE "FileVersion", "5.14.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.14.1.0"
|
||||
VALUE "ProductVersion", "5.14.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -157,6 +157,7 @@ constexpr auto kTransactionsLimit = 100;
|
|||
.converted = stargift && incoming,
|
||||
.stargift = stargift.has_value(),
|
||||
.giftUpgraded = tl.data().is_stargift_upgrade(),
|
||||
.giftResale = tl.data().is_stargift_resale(),
|
||||
.reaction = tl.data().is_reaction(),
|
||||
.refunded = tl.data().is_refund(),
|
||||
.pending = tl.data().is_pending(),
|
||||
|
@ -528,8 +529,12 @@ void EditCreditsSubscription(
|
|||
)).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send();
|
||||
}
|
||||
|
||||
MTPInputSavedStarGift InputSavedStarGiftId(const Data::SavedStarGiftId &id) {
|
||||
return id.isUser()
|
||||
MTPInputSavedStarGift InputSavedStarGiftId(
|
||||
const Data::SavedStarGiftId &id,
|
||||
const std::shared_ptr<Data::UniqueGift> &unique) {
|
||||
return (!id && unique)
|
||||
? MTP_inputSavedStarGiftSlug(MTP_string(unique->slug))
|
||||
: id.isUser()
|
||||
? MTP_inputSavedStarGiftUser(MTP_int(id.userMessageId().bare))
|
||||
: MTP_inputSavedStarGiftChat(
|
||||
id.chat()->input,
|
||||
|
|
|
@ -122,6 +122,7 @@ void EditCreditsSubscription(
|
|||
Fn<void(QString)> fail);
|
||||
|
||||
[[nodiscard]] MTPInputSavedStarGift InputSavedStarGiftId(
|
||||
const Data::SavedStarGiftId &id);
|
||||
const Data::SavedStarGiftId &id,
|
||||
const std::shared_ptr<Data::UniqueGift> &unique = nullptr);
|
||||
|
||||
} // namespace Api
|
||||
|
|
|
@ -46,11 +46,15 @@ void HandleWithdrawalButton(
|
|||
bool loading = false;
|
||||
};
|
||||
|
||||
const auto channel = receiver.currencyReceiver;
|
||||
const auto peer = receiver.creditsReceiver;
|
||||
const auto currencyReceiver = receiver.currencyReceiver;
|
||||
const auto creditsReceiver = receiver.creditsReceiver;
|
||||
const auto isChannel = receiver.currencyReceiver
|
||||
&& receiver.currencyReceiver->isChannel();
|
||||
|
||||
const auto state = button->lifetime().make_state<State>();
|
||||
const auto session = (channel ? &channel->session() : &peer->session());
|
||||
const auto session = (currencyReceiver
|
||||
? ¤cyReceiver->session()
|
||||
: &creditsReceiver->session());
|
||||
|
||||
using ChannelOutUrl = MTPstats_BroadcastRevenueWithdrawalUrl;
|
||||
using CreditsOutUrl = MTPpayments_StarsRevenueWithdrawalUrl;
|
||||
|
@ -59,7 +63,7 @@ void HandleWithdrawalButton(
|
|||
const auto processOut = [=] {
|
||||
if (state->loading) {
|
||||
return;
|
||||
} else if (peer && !receiver.creditsAmount()) {
|
||||
} else if (creditsReceiver && !receiver.creditsAmount()) {
|
||||
return;
|
||||
}
|
||||
state->loading = true;
|
||||
|
@ -70,10 +74,10 @@ void HandleWithdrawalButton(
|
|||
state->loading = false;
|
||||
|
||||
auto fields = PasscodeBox::CloudFields::From(pass);
|
||||
fields.customTitle = channel
|
||||
fields.customTitle = isChannel
|
||||
? tr::lng_channel_earn_balance_password_title()
|
||||
: tr::lng_bot_earn_balance_password_title();
|
||||
fields.customDescription = channel
|
||||
fields.customDescription = isChannel
|
||||
? tr::lng_channel_earn_balance_password_description(tr::now)
|
||||
: tr::lng_bot_earn_balance_password_description(tr::now);
|
||||
fields.customSubmitButton = tr::lng_passcode_submit();
|
||||
|
@ -94,18 +98,18 @@ void HandleWithdrawalButton(
|
|||
show->showToast(message);
|
||||
}
|
||||
};
|
||||
if (channel) {
|
||||
if (currencyReceiver) {
|
||||
session->api().request(
|
||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||
channel->input,
|
||||
currencyReceiver->input,
|
||||
result.result
|
||||
)).done([=](const ChannelOutUrl &r) {
|
||||
done(qs(r.data().vurl()));
|
||||
}).fail(fail).send();
|
||||
} else if (peer) {
|
||||
} else if (creditsReceiver) {
|
||||
session->api().request(
|
||||
MTPpayments_GetStarsRevenueWithdrawalUrl(
|
||||
peer->input,
|
||||
creditsReceiver->input,
|
||||
MTP_long(receiver.creditsAmount()),
|
||||
result.result
|
||||
)).done([=](const CreditsOutUrl &r) {
|
||||
|
@ -134,17 +138,17 @@ void HandleWithdrawalButton(
|
|||
processOut();
|
||||
}
|
||||
};
|
||||
if (channel) {
|
||||
if (currencyReceiver) {
|
||||
session->api().request(
|
||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||
channel->input,
|
||||
currencyReceiver->input,
|
||||
MTP_inputCheckPasswordEmpty()
|
||||
)).fail(fail).send();
|
||||
} else if (peer) {
|
||||
} else if (creditsReceiver) {
|
||||
session->api().request(
|
||||
MTPpayments_GetStarsRevenueWithdrawalUrl(
|
||||
peer->input,
|
||||
MTP_long(std::numeric_limits<int64_t>::max()),
|
||||
creditsReceiver->input,
|
||||
MTP_long(receiver.creditsAmount()),
|
||||
MTP_inputCheckPasswordEmpty()
|
||||
)).fail(fail).send();
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ void RestrictSponsored(
|
|||
Fn<void(QString)> failed);
|
||||
|
||||
struct RewardReceiver final {
|
||||
ChannelData *currencyReceiver = nullptr;
|
||||
PeerData *currencyReceiver = nullptr;
|
||||
PeerData *creditsReceiver = nullptr;
|
||||
Fn<uint64()> creditsAmount;
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "api/api_global_privacy.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/components/promo_suggestions.h"
|
||||
#include "data/data_user.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_app_config.h"
|
||||
|
@ -101,13 +102,11 @@ rpl::producer<bool> GlobalPrivacy::showArchiveAndMute() const {
|
|||
}
|
||||
|
||||
rpl::producer<> GlobalPrivacy::suggestArchiveAndMute() const {
|
||||
return _session->appConfig().suggestionRequested(
|
||||
u"AUTOARCHIVE_POPULAR"_q);
|
||||
return _session->promoSuggestions().requested(u"AUTOARCHIVE_POPULAR"_q);
|
||||
}
|
||||
|
||||
void GlobalPrivacy::dismissArchiveAndMuteSuggestion() {
|
||||
_session->appConfig().dismissSuggestion(
|
||||
u"AUTOARCHIVE_POPULAR"_q);
|
||||
_session->promoSuggestions().dismiss(u"AUTOARCHIVE_POPULAR"_q);
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updateHideReadTime(bool hide) {
|
||||
|
|
|
@ -799,6 +799,7 @@ std::optional<Data::StarGift> FromTL(
|
|||
return gift.match([&](const MTPDstarGift &data) {
|
||||
const auto document = session->data().processDocument(
|
||||
data.vsticker());
|
||||
const auto resellPrice = data.vresell_min_stars().value_or_empty();
|
||||
const auto remaining = data.vavailability_remains();
|
||||
const auto total = data.vavailability_total();
|
||||
if (!document->sticker()) {
|
||||
|
@ -809,7 +810,10 @@ std::optional<Data::StarGift> FromTL(
|
|||
.stars = int64(data.vstars().v),
|
||||
.starsConverted = int64(data.vconvert_stars().v),
|
||||
.starsToUpgrade = int64(data.vupgrade_stars().value_or_empty()),
|
||||
.starsResellMin = int64(resellPrice),
|
||||
.document = document,
|
||||
.resellTitle = qs(data.vtitle().value_or_empty()),
|
||||
.resellCount = int(data.vavailability_resale().value_or_empty()),
|
||||
.limitedLeft = remaining.value_or_empty(),
|
||||
.limitedCount = total.value_or_empty(),
|
||||
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
|
||||
|
@ -849,6 +853,7 @@ std::optional<Data::StarGift> FromTL(
|
|||
? peerFromMTP(*data.vowner_id())
|
||||
: PeerId()),
|
||||
.number = data.vnum().v,
|
||||
.starsForResale = int(data.vresell_stars().value_or_empty()),
|
||||
.model = *model,
|
||||
.pattern = *pattern,
|
||||
}),
|
||||
|
@ -881,6 +886,8 @@ std::optional<Data::SavedStarGift> FromTL(
|
|||
} else if (const auto unique = parsed->unique.get()) {
|
||||
unique->starsForTransfer = data.vtransfer_stars().value_or(-1);
|
||||
unique->exportAt = data.vcan_export_at().value_or_empty();
|
||||
unique->canTransferAt = data.vcan_transfer_at().value_or_empty();
|
||||
unique->canResellAt = data.vcan_resell_at().value_or_empty();
|
||||
}
|
||||
using Id = Data::SavedStarGiftId;
|
||||
const auto hasUnique = parsed->unique != nullptr;
|
||||
|
@ -936,7 +943,7 @@ Data::UniqueGiftPattern FromTL(
|
|||
}
|
||||
|
||||
Data::UniqueGiftBackdrop FromTL(const MTPDstarGiftAttributeBackdrop &data) {
|
||||
auto result = Data::UniqueGiftBackdrop();
|
||||
auto result = Data::UniqueGiftBackdrop{ .id = data.vbackdrop_id().v };
|
||||
result.name = qs(data.vname());
|
||||
result.rarityPermille = data.vrarity_permille().v;
|
||||
result.centerColor = Ui::ColorFromSerialized(
|
||||
|
|
|
@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "mtproto/mtproto_dc_options.h"
|
||||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/components/promo_suggestions.h"
|
||||
#include "data/components/scheduled_messages.h"
|
||||
#include "data/components/top_peers.h"
|
||||
#include "data/notify/data_notify_settings.h"
|
||||
|
@ -2076,6 +2077,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
|||
|
||||
case mtpc_updateConfig: {
|
||||
session().mtp().requestConfig();
|
||||
session().promoSuggestions().invalidate();
|
||||
} break;
|
||||
|
||||
case mtpc_updateUserPhone: {
|
||||
|
|
|
@ -97,8 +97,6 @@ namespace {
|
|||
// Save draft to the cloud with 1 sec extra delay.
|
||||
constexpr auto kSaveCloudDraftTimeout = 1000;
|
||||
|
||||
constexpr auto kTopPromotionInterval = TimeId(60 * 60);
|
||||
constexpr auto kTopPromotionMinDelay = TimeId(10);
|
||||
constexpr auto kSmallDelayMs = 5;
|
||||
constexpr auto kReadFeaturedSetsTimeout = crl::time(1000);
|
||||
constexpr auto kFileLoaderQueueStopTimeout = crl::time(5000);
|
||||
|
@ -169,7 +167,6 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
|||
, _featuredSetsReadTimer([=] { readFeaturedSets(); })
|
||||
, _dialogsLoadState(std::make_unique<DialogsLoadState>())
|
||||
, _fileLoader(std::make_unique<TaskQueue>(kFileLoaderQueueStopTimeout))
|
||||
, _topPromotionTimer([=] { refreshTopPromotion(); })
|
||||
, _updateNotifyTimer([=] { sendNotifySettingsUpdates(); })
|
||||
, _statsSessionKillTimer([=] { checkStatsSessions(); })
|
||||
, _authorizations(std::make_unique<Api::Authorizations>(this))
|
||||
|
@ -205,11 +202,6 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
|||
}, _session->lifetime());
|
||||
|
||||
setupSupportMode();
|
||||
|
||||
Core::App().settings().proxy().connectionTypeValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshTopPromotion();
|
||||
}, _session->lifetime());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -249,79 +241,6 @@ void ApiWrap::requestChangelog(
|
|||
//).send();
|
||||
}
|
||||
|
||||
void ApiWrap::refreshTopPromotion() {
|
||||
const auto now = base::unixtime::now();
|
||||
const auto next = (_topPromotionNextRequestTime != 0)
|
||||
? _topPromotionNextRequestTime
|
||||
: now;
|
||||
if (_topPromotionRequestId) {
|
||||
getTopPromotionDelayed(now, next);
|
||||
return;
|
||||
}
|
||||
const auto key = [&]() -> std::pair<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(
|
||||
const QString &path,
|
||||
Fn<void(TextWithEntities message, bool updateRequired)> callback) {
|
||||
|
|
|
@ -200,7 +200,6 @@ public:
|
|||
void requestChangelog(
|
||||
const QString &sinceVersion,
|
||||
Fn<void(const MTPUpdates &result)> callback);
|
||||
void refreshTopPromotion();
|
||||
void requestDeepLinkInfo(
|
||||
const QString &path,
|
||||
Fn<void(TextWithEntities message, bool updateRequired)> callback);
|
||||
|
@ -569,9 +568,6 @@ private:
|
|||
not_null<SendingAlbum*> album,
|
||||
Fn<void(bool)> done = nullptr);
|
||||
|
||||
void getTopPromotionDelayed(TimeId now, TimeId next);
|
||||
void topPromotionDone(const MTPhelp_PromoData &proxy);
|
||||
|
||||
void sendNotifySettingsUpdates();
|
||||
|
||||
template <typename Request>
|
||||
|
@ -709,11 +705,6 @@ private:
|
|||
std::unique_ptr<TaskQueue> _fileLoader;
|
||||
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 PeerData*>> _updateNotifyPeers;
|
||||
base::flat_set<Data::DefaultNotify> _updateNotifyDefaults;
|
||||
|
|
|
@ -316,16 +316,23 @@ void FillChooseFilterMenu(
|
|||
return;
|
||||
}
|
||||
const auto session = &strong->session();
|
||||
const auto count = session->data().chatsFilters().list().size();
|
||||
if ((count - 1) >= limit()) {
|
||||
const auto &list = session->data().chatsFilters().list();
|
||||
if ((list.size() - 1) >= limit()) {
|
||||
return;
|
||||
}
|
||||
const auto chooseNextId = [&] {
|
||||
auto id = 2;
|
||||
while (ranges::contains(list, id, &Data::ChatFilter::id)) {
|
||||
++id;
|
||||
}
|
||||
return id;
|
||||
};
|
||||
auto filter =
|
||||
Data::ChatFilter({}, {}, {}, {}, {}, { history }, {}, {});
|
||||
const auto send = [=](const Data::ChatFilter &filter) {
|
||||
session->api().request(MTPmessages_UpdateDialogFilter(
|
||||
MTP_flags(MTPmessages_UpdateDialogFilter::Flag::f_filter),
|
||||
MTP_int(count),
|
||||
MTP_int(chooseNextId()),
|
||||
filter.tl()
|
||||
)).done([=] {
|
||||
session->data().chatsFilters().reload();
|
||||
|
|
|
@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/peers/replace_boost_box.h" // BoostsForGift.
|
||||
#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
|
||||
#include "boxes/star_gift_box.h" // ShowStarGiftBox.
|
||||
#include "boxes/transfer_gift_box.h" // ShowTransferGiftBox.
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_boosts.h"
|
||||
#include "data/data_changes.h"
|
||||
|
@ -1336,7 +1335,13 @@ void AddStarGiftTable(
|
|||
}, tooltip->lifetime());
|
||||
};
|
||||
|
||||
if (unique && entry.bareGiftOwnerId) {
|
||||
if (unique && entry.bareGiftResaleRecipientId) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer(),
|
||||
MakePeerTableValue(table, show, PeerId(entry.bareGiftResaleRecipientId)),
|
||||
st::giveawayGiftCodePeerMargin);
|
||||
} else if (unique && entry.bareGiftOwnerId) {
|
||||
const auto ownerId = PeerId(entry.bareGiftOwnerId);
|
||||
const auto was = std::make_shared<std::optional<CollectibleId>>();
|
||||
const auto handleChange = [=](
|
||||
|
@ -1621,7 +1626,17 @@ void AddCreditsHistoryEntryTable(
|
|||
const auto starrefRecipientId = PeerId(entry.starrefRecipientId);
|
||||
const auto session = &show->session();
|
||||
if (entry.starrefCommission) {
|
||||
if (entry.starrefAmount) {
|
||||
if (entry.giftResale && entry.starrefCommission < 1000) {
|
||||
const auto full = int(base::SafeRound(entry.credits.value()
|
||||
/ (1. - (entry.starrefCommission / 1000.))));
|
||||
auto value = Ui::Text::IconEmoji(&st::starIconEmojiColored);
|
||||
const auto starsText = Lang::FormatStarsAmountDecimal(
|
||||
StarsAmount{ full });
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_gift_full_price(),
|
||||
rpl::single(value.append(' ' + starsText)));
|
||||
} else if (entry.starrefAmount) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_star_ref_commission_title(),
|
||||
|
@ -1635,7 +1650,7 @@ void AddCreditsHistoryEntryTable(
|
|||
Ui::Text::WithEntities));
|
||||
}
|
||||
}
|
||||
if (starrefRecipientId && entry.starrefAmount) {
|
||||
if (starrefRecipientId && entry.starrefAmount && !entry.giftResale) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_affiliate(),
|
||||
|
@ -1645,7 +1660,9 @@ void AddCreditsHistoryEntryTable(
|
|||
if (peerId && entry.starrefCommission) {
|
||||
AddTableRow(
|
||||
table,
|
||||
(entry.starrefAmount
|
||||
(entry.giftResale
|
||||
? tr::lng_credits_box_history_entry_gift_sold_to
|
||||
: entry.starrefAmount
|
||||
? tr::lng_credits_box_history_entry_referred
|
||||
: tr::lng_credits_box_history_entry_miniapp)(),
|
||||
show,
|
||||
|
@ -1656,6 +1673,8 @@ void AddCreditsHistoryEntryTable(
|
|||
? tr::lng_credits_box_history_entry_referred()
|
||||
: entry.in
|
||||
? tr::lng_credits_box_history_entry_peer_in()
|
||||
: entry.giftResale
|
||||
? tr::lng_credits_box_history_entry_gift_bought_from()
|
||||
: entry.giftUpgraded
|
||||
? tr::lng_credits_box_history_entry_gift_from()
|
||||
: tr::lng_credits_box_history_entry_peer();
|
||||
|
|
|
@ -77,7 +77,7 @@ ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) {
|
|||
if (author == peer) {
|
||||
return {};
|
||||
} else if (const auto channel = author->asChannel()) {
|
||||
if (channel->linkedChat() == peer) {
|
||||
if (channel->discussionLink() == peer) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ the official desktop application for the Telegram messaging service.
|
|||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/peers/edit_linked_chat_box.h"
|
||||
#include "boxes/peers/edit_discussion_link_box.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_channel.h"
|
||||
|
@ -224,7 +224,7 @@ void Controller::choose(not_null<ChatData*> chat) {
|
|||
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<ChannelData*> channel,
|
||||
ChannelData *chat,
|
||||
|
@ -272,7 +272,7 @@ void Controller::choose(not_null<ChatData*> chat) {
|
|||
Settings::AddButtonWithIcon(
|
||||
above,
|
||||
tr::lng_manage_discussion_group_create(),
|
||||
st::infoCreateLinkedChatButton,
|
||||
st::infoCreateDiscussionLinkButton,
|
||||
{ &st::menuIconGroupCreate }
|
||||
)->addClickHandler([=, parent = above.data()] {
|
||||
const auto guarded = crl::guard(parent, callback);
|
||||
|
@ -292,7 +292,7 @@ void Controller::choose(not_null<ChatData*> chat) {
|
|||
(channel->isBroadcast()
|
||||
? tr::lng_manage_discussion_group_unlink
|
||||
: tr::lng_manage_linked_channel_unlink)(),
|
||||
st::infoUnlinkChatButton,
|
||||
st::infoUnlinkDiscussionLinkButton,
|
||||
{ &st::menuIconRemove }
|
||||
)->addClickHandler([=] { callback(nullptr); });
|
||||
Ui::AddSkip(below);
|
||||
|
@ -327,12 +327,12 @@ void Controller::choose(not_null<ChatData*> chat) {
|
|||
|
||||
} // namespace
|
||||
|
||||
object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
object_ptr<Ui::BoxContent> EditDiscussionLinkBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<ChannelData*> channel,
|
||||
std::vector<not_null<PeerData*>> &&chats,
|
||||
Fn<void(ChannelData*)> callback) {
|
||||
return EditLinkedChatBox(
|
||||
return EditDiscussionLinkBox(
|
||||
navigation,
|
||||
channel,
|
||||
nullptr,
|
||||
|
@ -341,13 +341,13 @@ object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
|||
callback);
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
object_ptr<Ui::BoxContent> EditDiscussionLinkBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<ChannelData*> chat,
|
||||
bool canEdit,
|
||||
Fn<void(ChannelData*)> callback) {
|
||||
return EditLinkedChatBox(
|
||||
return EditDiscussionLinkBox(
|
||||
navigation,
|
||||
channel,
|
||||
chat,
|
|
@ -17,14 +17,14 @@ namespace Window {
|
|||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> EditDiscussionLinkBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<ChannelData*> chat,
|
||||
bool canEdit,
|
||||
Fn<void(ChannelData*)> callback);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> EditDiscussionLinkBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<ChannelData*> channel,
|
||||
std::vector<not_null<PeerData*>> &&chats,
|
|
@ -1482,7 +1482,11 @@ void CheckBoostLevel(
|
|||
show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
|
||||
.link = qs(data.vboost_url()),
|
||||
.boost = counters,
|
||||
.features = (peer->isChannel()
|
||||
? LookupBoostFeatures(peer->asChannel())
|
||||
: Ui::BoostFeatures()),
|
||||
.reason = *reason,
|
||||
.group = !peer->isBroadcast(),
|
||||
}, openStatistics, nullptr));
|
||||
cancel();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
|
|
|
@ -22,7 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/peers/edit_peer_history_visibility_box.h"
|
||||
#include "boxes/peers/edit_peer_permissions_box.h"
|
||||
#include "boxes/peers/edit_peer_invite_links.h"
|
||||
#include "boxes/peers/edit_linked_chat_box.h"
|
||||
#include "boxes/peers/edit_discussion_link_box.h"
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
#include "boxes/peers/edit_peer_reactions.h"
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
|
@ -358,12 +358,13 @@ private:
|
|||
std::optional<QString> description;
|
||||
std::optional<bool> hiddenPreHistory;
|
||||
std::optional<bool> forum;
|
||||
std::optional<bool> autotranslate;
|
||||
std::optional<bool> signatures;
|
||||
std::optional<bool> signatureProfiles;
|
||||
std::optional<bool> noForwards;
|
||||
std::optional<bool> joinToWrite;
|
||||
std::optional<bool> requestToJoin;
|
||||
std::optional<ChannelData*> linkedChat;
|
||||
std::optional<ChannelData*> discussionLink;
|
||||
};
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> createPhotoAndTitleEdit();
|
||||
|
@ -379,12 +380,13 @@ private:
|
|||
void refreshForumToggleLocked();
|
||||
void showEditPeerTypeBox(
|
||||
std::optional<rpl::producer<QString>> error = {});
|
||||
void showEditLinkedChatBox();
|
||||
void showEditDiscussionLinkBox();
|
||||
void fillPrivacyTypeButton();
|
||||
void fillLinkedChatButton();
|
||||
void fillDiscussionLinkButton();
|
||||
//void fillInviteLinkButton();
|
||||
void fillForumButton();
|
||||
void fillColorIndexButton();
|
||||
void fillAutoTranslateButton();
|
||||
void fillSignaturesButton();
|
||||
void fillHistoryVisibilityButton();
|
||||
void fillManageSection();
|
||||
|
@ -408,11 +410,12 @@ private:
|
|||
[[nodiscard]] std::optional<Saving> validate() const;
|
||||
[[nodiscard]] bool validateUsernamesOrder(Saving &to) const;
|
||||
[[nodiscard]] bool validateUsername(Saving &to) const;
|
||||
[[nodiscard]] bool validateLinkedChat(Saving &to) const;
|
||||
[[nodiscard]] bool validateDiscussionLink(Saving &to) const;
|
||||
[[nodiscard]] bool validateTitle(Saving &to) const;
|
||||
[[nodiscard]] bool validateDescription(Saving &to) const;
|
||||
[[nodiscard]] bool validateHistoryVisibility(Saving &to) const;
|
||||
[[nodiscard]] bool validateForum(Saving &to) const;
|
||||
[[nodiscard]] bool validateAutotranslate(Saving &to) const;
|
||||
[[nodiscard]] bool validateSignatures(Saving &to) const;
|
||||
[[nodiscard]] bool validateForwards(Saving &to) const;
|
||||
[[nodiscard]] bool validateJoinToWrite(Saving &to) const;
|
||||
|
@ -421,11 +424,12 @@ private:
|
|||
void save();
|
||||
void saveUsernamesOrder();
|
||||
void saveUsername();
|
||||
void saveLinkedChat();
|
||||
void saveDiscussionLink();
|
||||
void saveTitle();
|
||||
void saveDescription();
|
||||
void saveHistoryVisibility();
|
||||
void saveForum();
|
||||
void saveAutotranslate();
|
||||
void saveSignatures();
|
||||
void saveForwards();
|
||||
void saveJoinToWrite();
|
||||
|
@ -446,12 +450,13 @@ private:
|
|||
void subscribeToMigration();
|
||||
void migrate(not_null<ChannelData*> channel);
|
||||
|
||||
std::optional<ChannelData*> _linkedChatSavedValue;
|
||||
ChannelData *_linkedChatOriginalValue = nullptr;
|
||||
std::optional<ChannelData*> _discussionLinkSavedValue;
|
||||
ChannelData *_discussionLinkOriginalValue = nullptr;
|
||||
bool _channelHasLocationOriginalValue = false;
|
||||
std::optional<HistoryVisibility> _historyVisibilitySavedValue;
|
||||
std::optional<EditPeerTypeData> _typeDataSavedValue;
|
||||
std::optional<bool> _forumSavedValue;
|
||||
std::optional<bool> _autotranslateSavedValue;
|
||||
std::optional<bool> _signaturesSavedValue;
|
||||
std::optional<bool> _signatureProfilesSavedValue;
|
||||
|
||||
|
@ -474,8 +479,8 @@ private:
|
|||
};
|
||||
|
||||
const rpl::event_stream<PrivacyAndForwards> _privacyTypeUpdates;
|
||||
const rpl::event_stream<ChannelData*> _linkedChatUpdates;
|
||||
mtpRequestId _linkedChatsRequestId = 0;
|
||||
const rpl::event_stream<ChannelData*> _discussionLinkUpdates;
|
||||
mtpRequestId _discussionLinksRequestId = 0;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
|
@ -813,7 +818,7 @@ void Controller::refreshHistoryVisibility() {
|
|||
_controls.historyVisibilityWrap->toggle(
|
||||
(!withUsername
|
||||
&& !_channelHasLocationOriginalValue
|
||||
&& (!_linkedChatSavedValue || !*_linkedChatSavedValue)
|
||||
&& (!_discussionLinkSavedValue || !*_discussionLinkSavedValue)
|
||||
&& (!_forumSavedValue || !*_forumSavedValue)),
|
||||
anim::type::instant);
|
||||
}
|
||||
|
@ -825,8 +830,8 @@ void Controller::showEditPeerTypeBox(
|
|||
_typeDataSavedValue = data;
|
||||
refreshHistoryVisibility();
|
||||
});
|
||||
_typeDataSavedValue->hasLinkedChat
|
||||
= (_linkedChatSavedValue.value_or(nullptr) != nullptr);
|
||||
_typeDataSavedValue->hasDiscussionLink
|
||||
= (_discussionLinkSavedValue.value_or(nullptr) != nullptr);
|
||||
const auto box = _navigation->parentController()->show(
|
||||
Box<EditPeerTypeBox>(
|
||||
_navigation,
|
||||
|
@ -841,7 +846,7 @@ void Controller::showEditPeerTypeBox(
|
|||
}, box->lifetime());
|
||||
}
|
||||
|
||||
void Controller::showEditLinkedChatBox() {
|
||||
void Controller::showEditDiscussionLinkBox() {
|
||||
Expects(_peer->isChannel());
|
||||
|
||||
if (_forumSavedValue && *_forumSavedValue) {
|
||||
|
@ -855,8 +860,8 @@ void Controller::showEditLinkedChatBox() {
|
|||
if (*box) {
|
||||
(*box)->closeBox();
|
||||
}
|
||||
*_linkedChatSavedValue = result;
|
||||
_linkedChatUpdates.fire_copy(result);
|
||||
*_discussionLinkSavedValue = result;
|
||||
_discussionLinkUpdates.fire_copy(result);
|
||||
refreshHistoryVisibility();
|
||||
refreshForumToggleLocked();
|
||||
};
|
||||
|
@ -867,31 +872,31 @@ void Controller::showEditLinkedChatBox() {
|
|||
&& (!channel->hiddenPreHistory()
|
||||
|| channel->canEditPreHistoryHidden()));
|
||||
|
||||
if (const auto chat = *_linkedChatSavedValue) {
|
||||
*box = _navigation->parentController()->show(EditLinkedChatBox(
|
||||
if (const auto chat = *_discussionLinkSavedValue) {
|
||||
*box = _navigation->parentController()->show(EditDiscussionLinkBox(
|
||||
_navigation,
|
||||
channel,
|
||||
chat,
|
||||
canEdit,
|
||||
callback));
|
||||
return;
|
||||
} else if (!canEdit || _linkedChatsRequestId) {
|
||||
} else if (!canEdit || _discussionLinksRequestId) {
|
||||
return;
|
||||
} else if (channel->isMegagroup()) {
|
||||
if (_forumSavedValue
|
||||
&& *_forumSavedValue
|
||||
&& _linkedChatOriginalValue) {
|
||||
&& _discussionLinkOriginalValue) {
|
||||
ShowForumForDiscussionError(_navigation);
|
||||
} else {
|
||||
// Restore original linked channel.
|
||||
callback(_linkedChatOriginalValue);
|
||||
// Restore original discussion link.
|
||||
callback(_discussionLinkOriginalValue);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_linkedChatsRequestId = _api.request(
|
||||
_discussionLinksRequestId = _api.request(
|
||||
MTPchannels_GetGroupsForDiscussion()
|
||||
).done([=](const MTPmessages_Chats &result) {
|
||||
_linkedChatsRequestId = 0;
|
||||
_discussionLinksRequestId = 0;
|
||||
const auto list = result.match([&](const auto &data) {
|
||||
return data.vchats().v;
|
||||
});
|
||||
|
@ -900,13 +905,13 @@ void Controller::showEditLinkedChatBox() {
|
|||
for (const auto &item : list) {
|
||||
chats.emplace_back(_peer->owner().processChat(item));
|
||||
}
|
||||
*box = _navigation->parentController()->show(EditLinkedChatBox(
|
||||
*box = _navigation->parentController()->show(EditDiscussionLinkBox(
|
||||
_navigation,
|
||||
channel,
|
||||
std::move(chats),
|
||||
callback));
|
||||
}).fail([=] {
|
||||
_linkedChatsRequestId = 0;
|
||||
_discussionLinksRequestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
|
@ -972,11 +977,11 @@ void Controller::fillPrivacyTypeButton() {
|
|||
});
|
||||
}
|
||||
|
||||
void Controller::fillLinkedChatButton() {
|
||||
void Controller::fillDiscussionLinkButton() {
|
||||
Expects(_controls.buttonsLayout != nullptr);
|
||||
|
||||
_linkedChatSavedValue = _linkedChatOriginalValue = _peer->isChannel()
|
||||
? _peer->asChannel()->linkedChat()
|
||||
_discussionLinkSavedValue = _discussionLinkOriginalValue = _peer->isChannel()
|
||||
? _peer->asChannel()->discussionLink()
|
||||
: nullptr;
|
||||
|
||||
const auto isGroup = (_peer->isChat() || _peer->isMegagroup());
|
||||
|
@ -985,7 +990,7 @@ void Controller::fillLinkedChatButton() {
|
|||
: rpl::combine(
|
||||
tr::lng_manage_linked_channel(),
|
||||
tr::lng_manage_linked_channel_restore(),
|
||||
_linkedChatUpdates.events()
|
||||
_discussionLinkUpdates.events()
|
||||
) | rpl::map([=](
|
||||
const QString &edit,
|
||||
const QString &restore,
|
||||
|
@ -993,13 +998,13 @@ void Controller::fillLinkedChatButton() {
|
|||
return chat ? edit : restore;
|
||||
});
|
||||
auto label = isGroup
|
||||
? _linkedChatUpdates.events(
|
||||
? _discussionLinkUpdates.events(
|
||||
) | rpl::map([](ChannelData *chat) {
|
||||
return chat ? chat->name() : QString();
|
||||
}) | rpl::type_erased()
|
||||
: rpl::combine(
|
||||
tr::lng_manage_discussion_group_add(),
|
||||
_linkedChatUpdates.events()
|
||||
_discussionLinkUpdates.events()
|
||||
) | rpl::map([=](const QString &add, ChannelData *chat) {
|
||||
return chat ? chat->name() : add;
|
||||
}) | rpl::type_erased();
|
||||
|
@ -1007,9 +1012,9 @@ void Controller::fillLinkedChatButton() {
|
|||
_controls.buttonsLayout,
|
||||
std::move(text),
|
||||
std::move(label),
|
||||
[=] { showEditLinkedChatBox(); },
|
||||
[=] { showEditDiscussionLinkBox(); },
|
||||
{ isGroup ? &st::menuIconChannel : &st::menuIconGroups });
|
||||
_linkedChatUpdates.fire_copy(*_linkedChatSavedValue);
|
||||
_discussionLinkUpdates.fire_copy(*_discussionLinkSavedValue);
|
||||
}
|
||||
//
|
||||
//void Controller::fillInviteLinkButton() {
|
||||
|
@ -1044,7 +1049,7 @@ void Controller::fillForumButton() {
|
|||
) | rpl::start_with_next([=](bool toggled) {
|
||||
if (_controls.forumToggleLocked && toggled) {
|
||||
unlocks->fire(false);
|
||||
if (_linkedChatSavedValue && *_linkedChatSavedValue) {
|
||||
if (_discussionLinkSavedValue && *_discussionLinkSavedValue) {
|
||||
ShowForumForDiscussionError(_navigation);
|
||||
} else {
|
||||
_navigation->showToast(
|
||||
|
@ -1074,8 +1079,8 @@ void Controller::refreshForumToggleLocked() {
|
|||
const auto channel = _peer->asChannel();
|
||||
const auto notenough = !_peer->isForum()
|
||||
&& ((chat ? chat->count : channel->membersCount()) < limit);
|
||||
const auto linked = _linkedChatSavedValue
|
||||
&& *_linkedChatSavedValue;
|
||||
const auto linked = _discussionLinkSavedValue
|
||||
&& *_discussionLinkSavedValue;
|
||||
const auto locked = _controls.forumToggleLocked = notenough || linked;
|
||||
_controls.forumToggle->setToggleLocked(locked);
|
||||
}
|
||||
|
@ -1091,6 +1096,69 @@ void Controller::fillColorIndexButton() {
|
|||
st::managePeerColorsButton);
|
||||
}
|
||||
|
||||
void Controller::fillAutoTranslateButton() {
|
||||
Expects(_controls.buttonsLayout != nullptr);
|
||||
|
||||
const auto channel = _peer->asBroadcast();
|
||||
if (!channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto requiredLevel = Data::LevelLimits(&channel->session())
|
||||
.channelAutoTranslateLevelMin();
|
||||
const auto autotranslate = _controls.buttonsLayout->add(
|
||||
EditPeerInfoBox::CreateButton(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_edit_autotranslate(),
|
||||
rpl::single(QString()),
|
||||
[] {},
|
||||
st::manageGroupTopicsButton,
|
||||
{ &st::menuIconTranslate }));
|
||||
struct State {
|
||||
rpl::event_stream<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() {
|
||||
Expects(_controls.buttonsLayout != nullptr);
|
||||
|
||||
|
@ -1253,6 +1321,8 @@ void Controller::fillManageSection() {
|
|||
const auto canEditSignatures = isChannel
|
||||
&& channel->canEditSignatures()
|
||||
&& !channel->isMegagroup();
|
||||
const auto canEditAutoTranslate = isChannel
|
||||
&& channel->canEditAutoTranslate();
|
||||
const auto canEditPreHistoryHidden = isChannel
|
||||
? channel->canEditPreHistoryHidden()
|
||||
: chat->canEditPreHistoryHidden();
|
||||
|
@ -1283,8 +1353,8 @@ void Controller::fillManageSection() {
|
|||
const auto canEditStickers = isChannel && channel->canEditStickers();
|
||||
const auto canDeleteChannel = isChannel && channel->canDelete();
|
||||
const auto canEditColorIndex = isChannel && channel->canEditEmoji();
|
||||
const auto canViewOrEditLinkedChat = isChannel
|
||||
&& (channel->linkedChat()
|
||||
const auto canViewOrEditDiscussionLink = isChannel
|
||||
&& (channel->discussionLink()
|
||||
|| (channel->isBroadcast() && channel->canEditInformation()));
|
||||
|
||||
::AddSkip(_controls.buttonsLayout, 0);
|
||||
|
@ -1294,8 +1364,8 @@ void Controller::fillManageSection() {
|
|||
//} else if (canEditInviteLinks) {
|
||||
// fillInviteLinkButton();
|
||||
}
|
||||
if (canViewOrEditLinkedChat) {
|
||||
fillLinkedChatButton();
|
||||
if (canViewOrEditDiscussionLink) {
|
||||
fillDiscussionLinkButton();
|
||||
}
|
||||
if (canEditPreHistoryHidden) {
|
||||
fillHistoryVisibilityButton();
|
||||
|
@ -1306,13 +1376,16 @@ void Controller::fillManageSection() {
|
|||
if (canEditColorIndex) {
|
||||
fillColorIndexButton();
|
||||
}
|
||||
if (canEditAutoTranslate) {
|
||||
fillAutoTranslateButton();
|
||||
}
|
||||
if (canEditSignatures) {
|
||||
fillSignaturesButton();
|
||||
} else if (canEditPreHistoryHidden
|
||||
|| canEditForum
|
||||
|| canEditColorIndex
|
||||
//|| canEditInviteLinks
|
||||
|| canViewOrEditLinkedChat
|
||||
|| canViewOrEditDiscussionLink
|
||||
|| canEditType) {
|
||||
::AddSkip(_controls.buttonsLayout);
|
||||
}
|
||||
|
@ -1336,6 +1409,8 @@ void Controller::fillManageSection() {
|
|||
? tr::lng_manage_peer_reactions_on(tr::now)
|
||||
: some
|
||||
? QString::number(some)
|
||||
: allowed.paidEnabled
|
||||
? QString::number(1)
|
||||
: tr::lng_manage_peer_reactions_off(tr::now);
|
||||
});
|
||||
AddButtonWithCount(
|
||||
|
@ -1538,7 +1613,11 @@ void Controller::editReactions() {
|
|||
strong->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
|
||||
.link = link,
|
||||
.boost = counters,
|
||||
.features = (_peer->isChannel()
|
||||
? LookupBoostFeatures(_peer->asChannel())
|
||||
: Ui::BoostFeatures()),
|
||||
.reason = { Ui::AskBoostCustomReactions{ required } },
|
||||
.group = !_peer->isBroadcast(),
|
||||
}, openStatistics, nullptr));
|
||||
}
|
||||
};
|
||||
|
@ -1887,11 +1966,12 @@ std::optional<Controller::Saving> Controller::validate() const {
|
|||
auto result = Saving();
|
||||
if (validateUsernamesOrder(result)
|
||||
&& validateUsername(result)
|
||||
&& validateLinkedChat(result)
|
||||
&& validateDiscussionLink(result)
|
||||
&& validateTitle(result)
|
||||
&& validateDescription(result)
|
||||
&& validateHistoryVisibility(result)
|
||||
&& validateForum(result)
|
||||
&& validateAutotranslate(result)
|
||||
&& validateSignatures(result)
|
||||
&& validateForwards(result)
|
||||
&& validateJoinToWrite(result)
|
||||
|
@ -1928,11 +2008,11 @@ bool Controller::validateUsername(Saving &to) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Controller::validateLinkedChat(Saving &to) const {
|
||||
if (!_linkedChatSavedValue) {
|
||||
bool Controller::validateDiscussionLink(Saving &to) const {
|
||||
if (!_discussionLinkSavedValue) {
|
||||
return true;
|
||||
}
|
||||
to.linkedChat = *_linkedChatSavedValue;
|
||||
to.discussionLink = *_discussionLinkSavedValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1979,6 +2059,14 @@ bool Controller::validateForum(Saving &to) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Controller::validateAutotranslate(Saving &to) const {
|
||||
if (!_autotranslateSavedValue.has_value()) {
|
||||
return true;
|
||||
}
|
||||
to.autotranslate = _autotranslateSavedValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Controller::validateSignatures(Saving &to) const {
|
||||
Expects(_signaturesSavedValue.has_value()
|
||||
== _signatureProfilesSavedValue.has_value());
|
||||
|
@ -2025,11 +2113,12 @@ void Controller::save() {
|
|||
_savingData = *saving;
|
||||
pushSaveStage([=] { saveUsernamesOrder(); });
|
||||
pushSaveStage([=] { saveUsername(); });
|
||||
pushSaveStage([=] { saveLinkedChat(); });
|
||||
pushSaveStage([=] { saveDiscussionLink(); });
|
||||
pushSaveStage([=] { saveTitle(); });
|
||||
pushSaveStage([=] { saveDescription(); });
|
||||
pushSaveStage([=] { saveHistoryVisibility(); });
|
||||
pushSaveStage([=] { saveForum(); });
|
||||
pushSaveStage([=] { saveAutotranslate(); });
|
||||
pushSaveStage([=] { saveSignatures(); });
|
||||
pushSaveStage([=] { saveForwards(); });
|
||||
pushSaveStage([=] { saveJoinToWrite(); });
|
||||
|
@ -2147,36 +2236,37 @@ void Controller::saveUsername() {
|
|||
}).send();
|
||||
}
|
||||
|
||||
void Controller::saveLinkedChat() {
|
||||
void Controller::saveDiscussionLink() {
|
||||
const auto channel = _peer->asChannel();
|
||||
if (!channel) {
|
||||
return continueSave();
|
||||
}
|
||||
if (!_savingData.linkedChat
|
||||
|| *_savingData.linkedChat == channel->linkedChat()) {
|
||||
if (!_savingData.discussionLink
|
||||
|| *_savingData.discussionLink == channel->discussionLink()) {
|
||||
return continueSave();
|
||||
}
|
||||
|
||||
const auto chat = *_savingData.linkedChat;
|
||||
const auto chat = *_savingData.discussionLink;
|
||||
if (channel->isBroadcast() && chat && chat->hiddenPreHistory()) {
|
||||
togglePreHistoryHidden(
|
||||
chat,
|
||||
false,
|
||||
[=] { saveLinkedChat(); },
|
||||
[=] { saveDiscussionLink(); },
|
||||
[=] { cancelSave(); });
|
||||
return;
|
||||
}
|
||||
|
||||
const auto input = *_savingData.linkedChat
|
||||
? (*_savingData.linkedChat)->inputChannel
|
||||
const auto input = *_savingData.discussionLink
|
||||
? (*_savingData.discussionLink)->inputChannel
|
||||
: MTP_inputChannelEmpty();
|
||||
_api.request(MTPchannels_SetDiscussionGroup(
|
||||
(channel->isBroadcast() ? channel->inputChannel : input),
|
||||
(channel->isBroadcast() ? input : channel->inputChannel)
|
||||
)).done([=] {
|
||||
channel->setLinkedChat(*_savingData.linkedChat);
|
||||
channel->setDiscussionLink(*_savingData.discussionLink);
|
||||
continueSave();
|
||||
}).fail([=] {
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_navigation->showToast(error.type());
|
||||
cancelSave();
|
||||
}).send();
|
||||
}
|
||||
|
@ -2416,6 +2506,30 @@ void Controller::saveForum() {
|
|||
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
||||
continueSave();
|
||||
} else {
|
||||
_navigation->showToast(error.type());
|
||||
cancelSave();
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
void Controller::saveAutotranslate() {
|
||||
const auto channel = _peer->asBroadcast();
|
||||
if (!_savingData.autotranslate
|
||||
|| !channel
|
||||
|| (*_savingData.autotranslate == channel->autoTranslation())) {
|
||||
return continueSave();
|
||||
}
|
||||
_api.request(MTPchannels_ToggleAutotranslation(
|
||||
channel->inputChannel,
|
||||
MTP_bool(*_savingData.autotranslate)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
channel->session().api().applyUpdates(result);
|
||||
continueSave();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
||||
continueSave();
|
||||
} else {
|
||||
_navigation->showToast(error.type());
|
||||
cancelSave();
|
||||
}
|
||||
}).send();
|
||||
|
@ -2450,6 +2564,7 @@ void Controller::saveSignatures() {
|
|||
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
||||
continueSave();
|
||||
} else {
|
||||
_navigation->showToast(error.type());
|
||||
cancelSave();
|
||||
}
|
||||
}).send();
|
||||
|
@ -2470,6 +2585,7 @@ void Controller::saveForwards() {
|
|||
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
||||
continueSave();
|
||||
} else {
|
||||
_navigation->showToast(error.type());
|
||||
cancelSave();
|
||||
}
|
||||
}).send();
|
||||
|
@ -2492,6 +2608,7 @@ void Controller::saveJoinToWrite() {
|
|||
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
||||
continueSave();
|
||||
} else {
|
||||
_navigation->showToast(error.type());
|
||||
cancelSave();
|
||||
}
|
||||
}).send();
|
||||
|
@ -2514,6 +2631,7 @@ void Controller::saveRequestToJoin() {
|
|||
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
||||
continueSave();
|
||||
} else {
|
||||
_navigation->showToast(error.type());
|
||||
cancelSave();
|
||||
}
|
||||
}).send();
|
||||
|
|
|
@ -1140,7 +1140,7 @@ void ShowEditPeerPermissionsBox(
|
|||
result.emplace(
|
||||
Flag::ChangeInfo | Flag::PinMessages,
|
||||
tr::lng_rights_permission_unavailable(tr::now));
|
||||
} else if (channel->isMegagroup() && channel->linkedChat()) {
|
||||
} else if (channel->isMegagroup() && channel->discussionLink()) {
|
||||
result.emplace(
|
||||
Flag::ChangeInfo | Flag::PinMessages,
|
||||
tr::lng_rights_permission_in_discuss(tr::now));
|
||||
|
|
|
@ -220,7 +220,7 @@ void Controller::createContent() {
|
|||
const auto wrap = _controls.whoSendWrap->entity();
|
||||
|
||||
Ui::AddSkip(wrap);
|
||||
if (_dataSavedValue->hasLinkedChat) {
|
||||
if (_dataSavedValue->hasDiscussionLink) {
|
||||
Ui::AddSubsectionTitle(wrap, tr::lng_manage_peer_send_title());
|
||||
|
||||
_controls.joinToWrite = wrap->add(EditPeerInfoBox::CreateButton(
|
||||
|
@ -498,7 +498,7 @@ void Controller::privacyChanged(Privacy value) {
|
|||
}
|
||||
_controls.whoSendWrap->toggle(
|
||||
(value == Privacy::HasUsername
|
||||
|| (_dataSavedValue && _dataSavedValue->hasLinkedChat)),
|
||||
|| (_dataSavedValue && _dataSavedValue->hasDiscussionLink)),
|
||||
anim::type::instant);
|
||||
};
|
||||
const auto refreshVisibilities = [&] {
|
||||
|
|
|
@ -37,7 +37,7 @@ struct EditPeerTypeData {
|
|||
Privacy privacy = Privacy::NoUsername;
|
||||
QString username;
|
||||
std::vector<QString> usernamesOrder;
|
||||
bool hasLinkedChat = false;
|
||||
bool hasDiscussionLink = false;
|
||||
bool noForwards = false;
|
||||
bool joinToWrite = false;
|
||||
bool requestToJoin = false;
|
||||
|
|
|
@ -449,6 +449,7 @@ Ui::BoostFeatures LookupBoostFeatures(not_null<ChannelData*> channel) {
|
|||
.nameColorsByLevel = std::move(nameColorsByLevel),
|
||||
.linkStylesByLevel = std::move(linkStylesByLevel),
|
||||
.linkLogoLevel = group ? 0 : levelLimits.channelBgIconLevelMin(),
|
||||
.autotranslateLevel = group ? 0 : levelLimits.channelAutoTranslateLevelMin(),
|
||||
.transcribeLevel = group ? levelLimits.groupTranscribeLevelMin() : 0,
|
||||
.emojiPackLevel = group ? levelLimits.groupEmojiStickersLevelMin() : 0,
|
||||
.emojiStatusLevel = group
|
||||
|
|
|
@ -153,6 +153,10 @@ void EditPriceBox(
|
|||
field->resize(width, field->height());
|
||||
wrap->resize(width, field->height());
|
||||
}, wrap->lifetime());
|
||||
field->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
||||
auto p = QPainter(field);
|
||||
st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width());
|
||||
}, field->lifetime());
|
||||
field->selectAll();
|
||||
box->setFocusCallback([=] {
|
||||
field->setFocusFast();
|
||||
|
@ -173,11 +177,6 @@ void EditPriceBox(
|
|||
return false;
|
||||
});
|
||||
|
||||
field->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
||||
auto p = QPainter(field);
|
||||
st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width());
|
||||
}, field->lifetime());
|
||||
|
||||
const auto save = [=] {
|
||||
const auto now = field->getLastText().toULongLong();
|
||||
if (now > limit) {
|
||||
|
|
|
@ -17,8 +17,13 @@ namespace Data {
|
|||
struct UniqueGift;
|
||||
struct GiftCode;
|
||||
struct CreditsHistoryEntry;
|
||||
class SavedStarGiftId;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class SessionShow;
|
||||
} // namespace Main
|
||||
|
||||
namespace Payments {
|
||||
enum class CheckoutResult;
|
||||
} // namespace Payments
|
||||
|
@ -52,7 +57,9 @@ void ShowStarGiftBox(
|
|||
void AddUniqueGiftCover(
|
||||
not_null<VerticalLayout*> container,
|
||||
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(
|
||||
not_null<VerticalLayout*> container,
|
||||
const Data::UniqueGift &data,
|
||||
|
@ -64,6 +71,17 @@ void ShowUniqueGiftWearBox(
|
|||
const Data::UniqueGift &gift,
|
||||
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 {
|
||||
QPointF position;
|
||||
float64 scale = 1.;
|
||||
|
@ -100,13 +118,31 @@ void AddUniqueCloseButton(
|
|||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
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(
|
||||
not_null<Window::SessionController*> window,
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
MTPInputInvoice invoice,
|
||||
Fn<void(Payments::CheckoutResult, const MTPUpdates *)> done);
|
||||
|
||||
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,
|
||||
const Data::UniqueGift &gift);
|
||||
|
||||
|
|
|
@ -25,12 +25,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h" // peerListSingleRow.
|
||||
#include "styles/style_credits.h" // starIconEmoji.
|
||||
#include "styles/style_dialogs.h" // recentPeersSpecialName.
|
||||
#include "styles/style_layers.h" // boxLabel.
|
||||
|
||||
|
@ -421,7 +423,8 @@ void TransferGift(
|
|||
not_null<PeerData*> to,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
Data::SavedStarGiftId savedId,
|
||||
Fn<void(Payments::CheckoutResult)> done) {
|
||||
Fn<void(Payments::CheckoutResult)> done,
|
||||
bool skipPaymentForm = false) {
|
||||
Expects(to->isUser());
|
||||
|
||||
const auto session = &window->session();
|
||||
|
@ -429,38 +432,115 @@ void TransferGift(
|
|||
auto formDone = [=](
|
||||
Payments::CheckoutResult result,
|
||||
const MTPUpdates *updates) {
|
||||
if (result == Payments::CheckoutResult::Free) {
|
||||
Assert(!skipPaymentForm);
|
||||
TransferGift(window, to, gift, savedId, done, true);
|
||||
return;
|
||||
}
|
||||
done(result);
|
||||
if (result == Payments::CheckoutResult::Paid) {
|
||||
session->data().notifyGiftUpdate({
|
||||
.id = savedId,
|
||||
.action = Data::GiftUpdate::Action::Transfer,
|
||||
});
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->session().data().notifyGiftUpdate({
|
||||
.id = savedId,
|
||||
.action = Data::GiftUpdate::Action::Transfer,
|
||||
});
|
||||
Ui::ShowGiftTransferredToast(strong, to, *gift);
|
||||
Ui::ShowGiftTransferredToast(strong->uiShow(), 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(
|
||||
Api::InputSavedStarGiftId(savedId),
|
||||
Api::InputSavedStarGiftId(savedId, gift),
|
||||
to->input
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
session->api().applyUpdates(result);
|
||||
formDone(Payments::CheckoutResult::Paid, &result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
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());
|
||||
}
|
||||
}).send();
|
||||
return;
|
||||
} else {
|
||||
Ui::RequestStarsFormAndSubmit(
|
||||
window->uiShow(),
|
||||
MTP_inputInvoiceStarGiftTransfer(
|
||||
Api::InputSavedStarGiftId(savedId, gift),
|
||||
to->input),
|
||||
std::move(formDone));
|
||||
}
|
||||
Ui::RequestStarsFormAndSubmit(
|
||||
window,
|
||||
MTP_inputInvoiceStarGiftTransfer(
|
||||
Api::InputSavedStarGiftId(savedId),
|
||||
to->input),
|
||||
std::move(formDone));
|
||||
}
|
||||
|
||||
void BuyResaleGift(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> to,
|
||||
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
|
||||
|
@ -545,6 +625,9 @@ void ShowTransferGiftBox(
|
|||
not_null<Window::SessionController*> window,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
Data::SavedStarGiftId savedId) {
|
||||
if (ShowTransferGiftLater(window->uiShow(), gift)) {
|
||||
return;
|
||||
}
|
||||
auto controller = std::make_unique<Controller>(
|
||||
window,
|
||||
gift,
|
||||
|
@ -566,3 +649,116 @@ void ShowTransferGiftBox(
|
|||
Box<PeerListBox>(std::move(controller), std::move(initBox)),
|
||||
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;
|
||||
} // namespace Window
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Data {
|
||||
struct UniqueGift;
|
||||
class SavedStarGiftId;
|
||||
|
@ -27,3 +31,16 @@ void ShowTransferGiftBox(
|
|||
not_null<Window::SessionController*> window,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
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_peer.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "base/timer.h"
|
||||
|
@ -580,28 +581,33 @@ void TopBar::initBlobsUnder(
|
|||
|
||||
void TopBar::subscribeToMembersChanges(not_null<GroupCall*> call) {
|
||||
const auto peer = call->peer();
|
||||
peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::GroupCall
|
||||
) | rpl::map([=] {
|
||||
return peer->groupCall();
|
||||
}) | rpl::filter([=](Data::GroupCall *real) {
|
||||
const auto call = _groupCall.get();
|
||||
return call && real && (real->id() == call->id());
|
||||
}) | rpl::take(
|
||||
1
|
||||
const auto group = _groupCall.get();
|
||||
const auto conference = group && group->conference();
|
||||
auto realValue = conference
|
||||
? (rpl::single(group->conferenceCall().get()) | rpl::type_erased())
|
||||
: peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::GroupCall
|
||||
) | rpl::map([=] {
|
||||
return peer->groupCall();
|
||||
}) | 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) {
|
||||
real->titleValue() | rpl::start_with_next([=] {
|
||||
updateInfoLabels();
|
||||
}, lifetime());
|
||||
}) | rpl::map([=](not_null<Data::GroupCall*> real) {
|
||||
|
||||
return HistoryView::GroupCallBarContentByCall(
|
||||
real,
|
||||
st::groupCallTopBarUserpics.size);
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::filter([=](const Ui::GroupCallBarContent &content) {
|
||||
if (_users.size() != content.users.size()) {
|
||||
if (_users.size() != content.users.size()
|
||||
|| (conference && _usersCount != content.count)) {
|
||||
return true;
|
||||
}
|
||||
for (auto i = 0, count = int(_users.size()); i != count; ++i) {
|
||||
|
@ -613,10 +619,14 @@ void TopBar::subscribeToMembersChanges(not_null<GroupCall*> call) {
|
|||
return false;
|
||||
}) | rpl::start_with_next([=](const Ui::GroupCallBarContent &content) {
|
||||
_users = content.users;
|
||||
_usersCount = content.count;
|
||||
for (auto &user : _users) {
|
||||
user.speaking = false;
|
||||
}
|
||||
_userpics->update(_users, !isHidden());
|
||||
if (conference) {
|
||||
updateInfoLabels();
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
_userpics->widthValue(
|
||||
|
@ -655,14 +665,62 @@ void TopBar::setInfoLabels() {
|
|||
} else if (const auto group = _groupCall.get()) {
|
||||
const auto peer = group->peer();
|
||||
const auto real = peer->groupCall();
|
||||
const auto name = peer->name();
|
||||
const auto text = _isGroupConnecting.current()
|
||||
? tr::lng_group_call_connecting(tr::now)
|
||||
: (real && real->id() == group->id() && !real->title().isEmpty())
|
||||
? real->title()
|
||||
: name;
|
||||
_fullInfoLabel->setText(text);
|
||||
_shortInfoLabel->setText(text);
|
||||
const auto connecting = _isGroupConnecting.current();
|
||||
if (!group->conference()) {
|
||||
_shortInfoLabel.destroy();
|
||||
}
|
||||
if (!group->conference() || connecting) {
|
||||
const auto name = peer->name();
|
||||
const auto title = (real && real->id() == group->id())
|
||||
? 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());
|
||||
|
||||
auto fullWidth = _fullInfoLabel->textMaxWidth();
|
||||
auto showFull = (left + fullWidth + right <= width());
|
||||
_fullInfoLabel->setVisible(showFull);
|
||||
_shortInfoLabel->setVisible(!showFull);
|
||||
|
||||
auto showFull = !_shortInfoLabel
|
||||
|| (left + fullWidth + right <= width());
|
||||
auto setInfoLabelGeometry = [this, left, right](auto &&infoLabel) {
|
||||
auto minPadding = qMax(left, right);
|
||||
auto infoWidth = infoLabel->textMaxWidth();
|
||||
|
@ -746,8 +802,13 @@ void TopBar::updateControlsGeometry() {
|
|||
}
|
||||
infoLabel->setGeometryToLeft(infoLeft, st::callBarLabelTop, infoWidth, st::callBarInfoLabel.style.font->height);
|
||||
};
|
||||
|
||||
_fullInfoLabel->setVisible(showFull);
|
||||
setInfoLabelGeometry(_fullInfoLabel);
|
||||
setInfoLabelGeometry(_shortInfoLabel);
|
||||
if (_shortInfoLabel) {
|
||||
_shortInfoLabel->setVisible(!showFull);
|
||||
setInfoLabelGeometry(_shortInfoLabel);
|
||||
}
|
||||
|
||||
_gradients.set_points(
|
||||
QPointF(0, st::callBarHeight / 2),
|
||||
|
|
|
@ -85,6 +85,7 @@ private:
|
|||
|
||||
bool _muted = false;
|
||||
std::vector<Ui::GroupCallUser> _users;
|
||||
int _usersCount = 0;
|
||||
std::unique_ptr<Ui::GroupCallUserpics> _userpics;
|
||||
int _userpicsWidth = 0;
|
||||
object_ptr<Ui::LabelSimple> _durationLabel;
|
||||
|
@ -99,6 +100,9 @@ private:
|
|||
|
||||
rpl::variable<bool> _isGroupConnecting = false;
|
||||
|
||||
std::vector<not_null<PeerData*>> _conferenceFirstUsers;
|
||||
int _conferenceUsersCount = 0;
|
||||
|
||||
QBrush _groupBrush;
|
||||
anim::linear_gradients<BarState> _gradients;
|
||||
Ui::Animations::Simple _switchStateAnimation;
|
||||
|
|
|
@ -332,7 +332,7 @@ QString ImagesOrAllFilter() {
|
|||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -1932,7 +1932,7 @@ void ResolveAndShowUniqueGift(
|
|||
session->data().processUsers(data.vusers());
|
||||
if (const auto gift = Api::FromTL(session, data.vgift())) {
|
||||
using namespace ::Settings;
|
||||
show->show(Box(GlobalStarGiftBox, show, *gift, st));
|
||||
show->show(Box(GlobalStarGiftBox, show, *gift, PeerId(), st));
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
clear();
|
||||
|
|
|
@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
|
|||
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
||||
constexpr auto AppName = "AyuGram Desktop"_cs;
|
||||
constexpr auto AppFile = "AyuGram"_cs;
|
||||
constexpr auto AppVersion = 5014001;
|
||||
constexpr auto AppVersionStr = "5.14.1";
|
||||
constexpr auto AppVersion = 5014003;
|
||||
constexpr auto AppVersionStr = "5.14.3";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
|
|
@ -135,10 +135,17 @@ void Credits::apply(StarsAmount balance) {
|
|||
|
||||
void Credits::apply(PeerId peerId, StarsAmount balance) {
|
||||
_cachedPeerBalances[peerId] = balance;
|
||||
_refreshedByPeerId.fire_copy(peerId);
|
||||
}
|
||||
|
||||
void Credits::applyCurrency(PeerId peerId, uint64 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
|
||||
|
|
|
@ -37,6 +37,8 @@ public:
|
|||
[[nodiscard]] rpl::producer<float64> rateValue(
|
||||
not_null<PeerData*> ownedBotOrChannel);
|
||||
|
||||
[[nodiscard]] rpl::producer<> refreshedByPeerId(PeerId peerId);
|
||||
|
||||
void applyCurrency(PeerId peerId, uint64 balance);
|
||||
[[nodiscard]] uint64 balanceCurrency(PeerId peerId) const;
|
||||
|
||||
|
@ -64,6 +66,8 @@ private:
|
|||
crl::time _lastLoaded = 0;
|
||||
float64 _rate = 0.;
|
||||
|
||||
rpl::event_stream<PeerId> _refreshedByPeerId;
|
||||
|
||||
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),
|
||||
StickersSet = (1ULL << 46),
|
||||
EmojiSet = (1ULL << 47),
|
||||
ChannelLinkedChat = (1ULL << 48),
|
||||
DiscussionLink = (1ULL << 48),
|
||||
ChannelLocation = (1ULL << 49),
|
||||
Slowmode = (1ULL << 50),
|
||||
GroupCall = (1ULL << 51),
|
||||
|
|
|
@ -7,7 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "data/data_channel.h"
|
||||
|
||||
#include "api/api_credits.h"
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "api/api_statistics.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel_admins.h"
|
||||
|
@ -272,22 +275,22 @@ const ChannelLocation *ChannelData::getLocation() const {
|
|||
return mgInfo ? mgInfo->getLocation() : nullptr;
|
||||
}
|
||||
|
||||
void ChannelData::setLinkedChat(ChannelData *linked) {
|
||||
if (_linkedChat != linked) {
|
||||
_linkedChat = linked;
|
||||
void ChannelData::setDiscussionLink(ChannelData *linked) {
|
||||
if (_discussionLink != linked) {
|
||||
_discussionLink = linked;
|
||||
if (const auto history = owner().historyLoaded(this)) {
|
||||
history->forceFullResize();
|
||||
}
|
||||
session().changes().peerUpdated(this, UpdateFlag::ChannelLinkedChat);
|
||||
session().changes().peerUpdated(this, UpdateFlag::DiscussionLink);
|
||||
}
|
||||
}
|
||||
|
||||
ChannelData *ChannelData::linkedChat() const {
|
||||
return _linkedChat.value_or(nullptr);
|
||||
ChannelData *ChannelData::discussionLink() const {
|
||||
return _discussionLink.value_or(nullptr);
|
||||
}
|
||||
|
||||
bool ChannelData::linkedChatKnown() const {
|
||||
return _linkedChat.has_value();
|
||||
bool ChannelData::discussionLinkKnown() const {
|
||||
return _discussionLink.has_value();
|
||||
}
|
||||
|
||||
void ChannelData::setMembersCount(int newMembersCount) {
|
||||
|
@ -649,7 +652,11 @@ bool ChannelData::canEditPermissions() const {
|
|||
}
|
||||
|
||||
bool ChannelData::canEditSignatures() const {
|
||||
return isChannel() && canEditInformation();
|
||||
return isBroadcast() && canEditInformation();
|
||||
}
|
||||
|
||||
bool ChannelData::canEditAutoTranslate() const {
|
||||
return isBroadcast() && canEditInformation();
|
||||
}
|
||||
|
||||
bool ChannelData::canEditPreHistoryHidden() const {
|
||||
|
@ -1229,9 +1236,9 @@ void ApplyChannelUpdate(
|
|||
channel->setLocation(MTP_channelLocationEmpty());
|
||||
}
|
||||
if (const auto chat = update.vlinked_chat_id()) {
|
||||
channel->setLinkedChat(channel->owner().channelLoaded(chat->v));
|
||||
channel->setDiscussionLink(channel->owner().channelLoaded(chat->v));
|
||||
} else {
|
||||
channel->setLinkedChat(nullptr);
|
||||
channel->setDiscussionLink(nullptr);
|
||||
}
|
||||
if (const auto history = channel->owner().historyLoaded(channel)) {
|
||||
if (const auto available = update.vavailable_min_id()) {
|
||||
|
@ -1353,6 +1360,41 @@ void ApplyChannelUpdate(
|
|||
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.
|
||||
channel->owner().sendHistoryChangeNotifications();
|
||||
}
|
||||
|
|
|
@ -73,6 +73,7 @@ enum class ChannelDataFlag : uint64 {
|
|||
SignatureProfiles = (1ULL << 35),
|
||||
StargiftsAvailable = (1ULL << 36),
|
||||
PaidMessagesAvailable = (1ULL << 37),
|
||||
AutoTranslation = (1ULL << 38),
|
||||
};
|
||||
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
|
||||
using ChannelDataFlags = base::flags<ChannelDataFlag>;
|
||||
|
@ -321,6 +322,9 @@ public:
|
|||
[[nodiscard]] bool antiSpamMode() const {
|
||||
return flags() & Flag::AntiSpam;
|
||||
}
|
||||
[[nodiscard]] bool autoTranslation() const {
|
||||
return flags() & Flag::AutoTranslation;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto adminRights() const {
|
||||
return _adminRights.current();
|
||||
|
@ -382,6 +386,7 @@ public:
|
|||
[[nodiscard]] bool canViewAdmins() const;
|
||||
[[nodiscard]] bool canViewBanned() const;
|
||||
[[nodiscard]] bool canEditSignatures() const;
|
||||
[[nodiscard]] bool canEditAutoTranslate() const;
|
||||
[[nodiscard]] bool canEditStickers() const;
|
||||
[[nodiscard]] bool canEditEmoji() const;
|
||||
[[nodiscard]] bool canDelete() const;
|
||||
|
@ -404,9 +409,9 @@ public:
|
|||
void setLocation(const MTPChannelLocation &data);
|
||||
[[nodiscard]] const ChannelLocation *getLocation() const;
|
||||
|
||||
void setLinkedChat(ChannelData *linked);
|
||||
[[nodiscard]] ChannelData *linkedChat() const;
|
||||
[[nodiscard]] bool linkedChatKnown() const;
|
||||
void setDiscussionLink(ChannelData *link);
|
||||
[[nodiscard]] ChannelData *discussionLink() const;
|
||||
[[nodiscard]] bool discussionLinkKnown() const;
|
||||
|
||||
void ptsInit(int32 pts) {
|
||||
_ptsWaiter.init(pts);
|
||||
|
@ -565,7 +570,7 @@ private:
|
|||
std::vector<Data::UnavailableReason> _unavailableReasons;
|
||||
std::unique_ptr<InvitePeek> _invitePeek;
|
||||
QString _inviteLink;
|
||||
std::optional<ChannelData*> _linkedChat;
|
||||
std::optional<ChannelData*> _discussionLink;
|
||||
|
||||
Data::AllowedReactions _allowedReactions;
|
||||
|
||||
|
|
|
@ -65,6 +65,7 @@ struct CreditsHistoryEntry final {
|
|||
uint64 bareGiveawayMsgId = 0;
|
||||
uint64 bareGiftStickerId = 0;
|
||||
uint64 bareGiftOwnerId = 0;
|
||||
uint64 bareGiftResaleRecipientId = 0;
|
||||
uint64 bareActorId = 0;
|
||||
uint64 bareEntryOwnerId = 0;
|
||||
uint64 giftChannelSavedId = 0;
|
||||
|
@ -94,6 +95,7 @@ struct CreditsHistoryEntry final {
|
|||
bool giftTransferred : 1 = false;
|
||||
bool giftRefunded : 1 = false;
|
||||
bool giftUpgraded : 1 = false;
|
||||
bool giftResale : 1 = false;
|
||||
bool giftPinned : 1 = false;
|
||||
bool savedToProfile : 1 = false;
|
||||
bool fromGiftsList : 1 = false;
|
||||
|
|
|
@ -704,6 +704,13 @@ bool PeerData::canExportChatHistory() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool PeerData::autoTranslation() const {
|
||||
if (const auto channel = asChannel()) {
|
||||
return channel->autoTranslation();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PeerData::setAbout(const QString &newAbout) {
|
||||
if (_about == newAbout) {
|
||||
return false;
|
||||
|
|
|
@ -396,6 +396,7 @@ public:
|
|||
[[nodiscard]] bool canManageGifts() const;
|
||||
[[nodiscard]] bool canTransferGifts() const;
|
||||
[[nodiscard]] bool canExportChatHistory() const;
|
||||
[[nodiscard]] bool autoTranslation() const;
|
||||
|
||||
// Returns true if about text was changed.
|
||||
bool setAbout(const QString &newAbout);
|
||||
|
|
|
@ -262,6 +262,12 @@ int LevelLimits::channelRestrictSponsoredLevelMin() const {
|
|||
20);
|
||||
}
|
||||
|
||||
int LevelLimits::channelAutoTranslateLevelMin() const {
|
||||
return _session->appConfig().get<int>(
|
||||
u"channel_autotranslation_level_min"_q,
|
||||
3);
|
||||
}
|
||||
|
||||
int LevelLimits::groupTranscribeLevelMin() const {
|
||||
return _session->appConfig().get<int>(
|
||||
u"group_transcribe_level_min"_q,
|
||||
|
|
|
@ -102,6 +102,7 @@ public:
|
|||
[[nodiscard]] int channelWallpaperLevelMin() const;
|
||||
[[nodiscard]] int channelCustomWallpaperLevelMin() const;
|
||||
[[nodiscard]] int channelRestrictSponsoredLevelMin() const;
|
||||
[[nodiscard]] int channelAutoTranslateLevelMin() const;
|
||||
[[nodiscard]] int groupTranscribeLevelMin() const;
|
||||
[[nodiscard]] int groupEmojiStickersLevelMin() const;
|
||||
[[nodiscard]] int groupProfileBgIconLevelMin() const;
|
||||
|
|
|
@ -91,7 +91,6 @@ namespace Data {
|
|||
namespace {
|
||||
|
||||
using ViewElement = HistoryView::Element;
|
||||
using UserIds = std::vector<UserId>;
|
||||
|
||||
// s: box 100x100
|
||||
// m: box 320x320
|
||||
|
@ -977,7 +976,8 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
|||
| Flag::Forum
|
||||
| ((!minimal && !data.is_stories_hidden_min())
|
||||
? Flag::StoriesHidden
|
||||
: Flag());
|
||||
: Flag())
|
||||
| Flag::AutoTranslation;
|
||||
const auto storiesState = minimal
|
||||
? std::optional<Data::Stories::PeerSourceState>()
|
||||
: 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())
|
||||
? Flag::StoriesHidden
|
||||
: Flag());
|
||||
: Flag())
|
||||
| (data.is_autotranslation() ? Flag::AutoTranslation : Flag());
|
||||
channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
|
||||
channel->setBotVerifyDetailsIcon(
|
||||
data.vbot_verification_icon().value_or_empty());
|
||||
|
@ -4865,36 +4866,6 @@ MessageIdsList Session::takeMimeForwardIds() {
|
|||
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) {
|
||||
return data.match([&](const MTPDaccount_wallPapers &data) {
|
||||
setWallpapers(data.vwallpapers().v, data.vhash().v);
|
||||
|
@ -5046,69 +5017,4 @@ void Session::clearLocalStorage() {
|
|||
_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
|
||||
|
|
|
@ -89,9 +89,11 @@ struct GiftUpdate {
|
|||
Delete,
|
||||
Pin,
|
||||
Unpin,
|
||||
ResaleChange,
|
||||
};
|
||||
|
||||
Data::SavedStarGiftId id;
|
||||
QString slug;
|
||||
Action action = {};
|
||||
};
|
||||
|
||||
|
@ -799,11 +801,6 @@ public:
|
|||
void setMimeForwardIds(MessageIdsList &&list);
|
||||
MessageIdsList takeMimeForwardIds();
|
||||
|
||||
void setTopPromoted(
|
||||
History *promoted,
|
||||
const QString &type,
|
||||
const QString &message);
|
||||
|
||||
bool updateWallpapers(const MTPaccount_WallPapers &data);
|
||||
void removeWallpaper(const WallPaper &paper);
|
||||
const std::vector<WallPaper> &wallpapers() const;
|
||||
|
@ -833,13 +830,6 @@ public:
|
|||
void sentFromScheduled(SentFromScheduled value);
|
||||
[[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();
|
||||
|
||||
private:
|
||||
|
@ -1127,8 +1117,6 @@ private:
|
|||
ReactionId,
|
||||
base::flat_set<not_null<ViewElement*>>> _viewsByTag;
|
||||
|
||||
History *_topPromoted = nullptr;
|
||||
|
||||
std::unordered_map<PeerId, std::unique_ptr<PeerData>> _peers;
|
||||
|
||||
MessageIdsList _mimeForwardIds;
|
||||
|
@ -1155,11 +1143,6 @@ private:
|
|||
not_null<ChannelData*>,
|
||||
mtpRequestId> _viewAsMessagesRequests;
|
||||
|
||||
mtpRequestId _contactBirthdaysRequestId = 0;
|
||||
int _contactBirthdaysLastDayRequest = -1;
|
||||
std::vector<UserId> _contactBirthdays;
|
||||
std::vector<UserId> _contactBirthdaysToday;
|
||||
|
||||
Groups _groups;
|
||||
const std::unique_ptr<ChatFilters> _chatsFilters;
|
||||
const std::unique_ptr<CloudThemes> _cloudThemes;
|
||||
|
|
|
@ -27,6 +27,7 @@ struct UniqueGiftBackdrop : UniqueGiftAttribute {
|
|||
QColor edgeColor;
|
||||
QColor patternColor;
|
||||
QColor textColor;
|
||||
int id = 0;
|
||||
};
|
||||
|
||||
struct UniqueGiftOriginalDetails {
|
||||
|
@ -45,7 +46,10 @@ struct UniqueGift {
|
|||
PeerId ownerId = 0;
|
||||
int number = 0;
|
||||
int starsForTransfer = -1;
|
||||
int starsForResale = -1;
|
||||
TimeId exportAt = 0;
|
||||
TimeId canTransferAt = 0;
|
||||
TimeId canResellAt = 0;
|
||||
UniqueGiftModel model;
|
||||
UniqueGiftPattern pattern;
|
||||
UniqueGiftBackdrop backdrop;
|
||||
|
@ -62,7 +66,10 @@ struct StarGift {
|
|||
int64 stars = 0;
|
||||
int64 starsConverted = 0;
|
||||
int64 starsToUpgrade = 0;
|
||||
int64 starsResellMin = 0;
|
||||
not_null<DocumentData*> document;
|
||||
QString resellTitle;
|
||||
int resellCount = 0;
|
||||
int limitedLeft = 0;
|
||||
int limitedCount = 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_sensitive_content.h"
|
||||
#include "api/api_statistics.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "storage/storage_user_photos.h"
|
||||
|
@ -781,6 +782,7 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
|||
Data::PeerUpdate::Flag::Rights);
|
||||
}
|
||||
if (info->canEditInformation) {
|
||||
static constexpr auto kTimeout = crl::time(60000);
|
||||
const auto id = user->id;
|
||||
const auto weak = base::make_weak(&user->session());
|
||||
const auto creditsLoadLifetime
|
||||
|
@ -790,23 +792,28 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
|||
creditsLoad->request({}, [=](Data::CreditsStatusSlice slice) {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->credits().apply(id, slice.balance);
|
||||
creditsLoadLifetime->destroy();
|
||||
}
|
||||
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>(user);
|
||||
currencyLoad->request(
|
||||
) | rpl::start_with_error_done([=](const QString &error) {
|
||||
currencyLoadLifetime->destroy();
|
||||
}, [=] {
|
||||
const auto apply = [=](Data::EarnInt balance) {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->credits().applyCurrency(
|
||||
id,
|
||||
currencyLoad->data().currentBalance);
|
||||
currencyLoadLifetime->destroy();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3815,7 +3815,11 @@ void InnerWidget::searchReceived(
|
|||
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
|
||||
: Key(_openedForum->history());
|
||||
if (inject
|
||||
|
@ -4220,7 +4224,8 @@ void InnerWidget::repaintPreviewResult(int index) {
|
|||
|
||||
bool InnerWidget::computeSearchWithPostsPreview() const {
|
||||
return (_searchHashOrCashtag != HashOrCashtag::None)
|
||||
&& (_searchState.tab == ChatSearchTab::MyMessages);
|
||||
&& (_searchState.tab == ChatSearchTab::MyMessages)
|
||||
&& !_searchState.inChat;
|
||||
}
|
||||
|
||||
void InnerWidget::clearFilter() {
|
||||
|
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/star_gift_box.h" // ShowStarGiftBox.
|
||||
#include "core/application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_birthday.h"
|
||||
#include "data/data_changes.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 "info/profile/info_profile_values.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "data/components/promo_suggestions.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/credits_graphics.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/format_values.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 wrap = state->wrap;
|
||||
using RightIcon = TopBarSuggestionContent::RightIcon;
|
||||
const auto config = &session->appConfig();
|
||||
if (session->premiumCanBuy()
|
||||
&& config->suggestionCurrent(kSugPremiumGrace.utf8())) {
|
||||
const auto promo = &session->promoSuggestions();
|
||||
if (const auto custom = promo->custom()) {
|
||||
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([=] {
|
||||
const auto controller = FindSessionController(parent);
|
||||
UrlClickHandler::Open(
|
||||
|
@ -133,7 +159,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
|||
}));
|
||||
});
|
||||
content->setHideCallback([=] {
|
||||
config->dismissSuggestion(kSugPremiumGrace.utf8());
|
||||
promo->dismiss(kSugPremiumGrace.utf8());
|
||||
repeat(repeat);
|
||||
});
|
||||
content->setContent(
|
||||
|
@ -147,7 +173,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
|||
Toggle{ true, anim::type::normal });
|
||||
return;
|
||||
} else if (session->premiumCanBuy()
|
||||
&& config->suggestionCurrent(kSugLowCreditsSubs.utf8())) {
|
||||
&& promo->current(kSugLowCreditsSubs.utf8())) {
|
||||
state->creditsHistory = std::make_unique<Api::CreditsHistory>(
|
||||
session->user(),
|
||||
false,
|
||||
|
@ -156,7 +182,11 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
|||
const QString &peers,
|
||||
uint64 needed,
|
||||
uint64 whole) {
|
||||
if (whole > needed) {
|
||||
return;
|
||||
}
|
||||
content->setRightIcon(RightIcon::Close);
|
||||
content->setLeftPadding(0);
|
||||
content->setClickedCallback([=] {
|
||||
const auto controller = FindSessionController(parent);
|
||||
controller->uiShow()->show(Box(
|
||||
|
@ -165,15 +195,27 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
|||
needed,
|
||||
Settings::SmallBalanceSubscription{ peers },
|
||||
[=] {
|
||||
config->dismissSuggestion(
|
||||
kSugLowCreditsSubs.utf8());
|
||||
promo->dismiss(kSugLowCreditsSubs.utf8());
|
||||
repeat(repeat);
|
||||
}));
|
||||
});
|
||||
content->setHideCallback([=] {
|
||||
config->dismissSuggestion(kSugLowCreditsSubs.utf8());
|
||||
promo->dismiss(kSugLowCreditsSubs.utf8());
|
||||
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(
|
||||
tr::lng_dialogs_suggestions_credits_sub_low_title(
|
||||
tr::now,
|
||||
|
@ -187,7 +229,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
|||
tr::lng_dialogs_suggestions_credits_sub_low_about(
|
||||
tr::now,
|
||||
TextWithEntities::Simple),
|
||||
true);
|
||||
std::move(context));
|
||||
state->desiredWrapToggle.force_assign(
|
||||
Toggle{ true, anim::type::normal });
|
||||
};
|
||||
|
@ -219,12 +261,10 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
|||
|
||||
return;
|
||||
} else if (session->premiumCanBuy()
|
||||
&& config->suggestionCurrent(kSugBirthdayContacts.utf8())) {
|
||||
session->data().contactBirthdays(
|
||||
) | rpl::start_with_next(crl::guard(content, [=] {
|
||||
const auto users = session->data()
|
||||
.knownBirthdaysToday().value_or(
|
||||
std::vector<UserId>());
|
||||
&& promo->current(kSugBirthdayContacts.utf8())) {
|
||||
promo->requestContactBirthdays(crl::guard(content, [=] {
|
||||
const auto users = promo->knownBirthdaysToday().value_or(
|
||||
std::vector<UserId>());
|
||||
if (users.empty()) {
|
||||
repeat(repeat);
|
||||
return;
|
||||
|
@ -242,8 +282,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
|||
}
|
||||
});
|
||||
content->setHideCallback([=] {
|
||||
config->dismissSuggestion(
|
||||
kSugBirthdayContacts.utf8());
|
||||
promo->dismiss(kSugBirthdayContacts.utf8());
|
||||
controller->showToast(
|
||||
tr::lng_dialogs_suggestions_birthday_contact_dismiss(
|
||||
tr::now));
|
||||
|
@ -283,6 +322,8 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
|||
s->widget = base::make_unique_q<Ui::RpWidget>(
|
||||
content);
|
||||
const auto widget = s->widget.get();
|
||||
widget->setAttribute(
|
||||
Qt::WA_TransparentForMouseEvents);
|
||||
content->sizeValue() | rpl::filter_size(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
widget->resize(size);
|
||||
|
@ -343,17 +384,18 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
|||
fake->moveToLeft(
|
||||
leftPadding,
|
||||
(s.height() - fake->height()) / 2);
|
||||
}, content->lifetime());
|
||||
}, fake->lifetime());
|
||||
content->setLeftPadding(fake->width() + leftPadding);
|
||||
}
|
||||
|
||||
state->desiredWrapToggle.force_assign(
|
||||
Toggle{ true, anim::type::normal });
|
||||
}), state->giftsLifetime);
|
||||
}));
|
||||
return;
|
||||
} else if (config->suggestionCurrent(kSugSetBirthday.utf8())
|
||||
} else if (promo->current(kSugSetBirthday.utf8())
|
||||
&& !Data::IsBirthdayToday(session->user()->birthday())) {
|
||||
content->setRightIcon(RightIcon::Close);
|
||||
content->setLeftPadding(0);
|
||||
content->setClickedCallback([=] {
|
||||
const auto controller = FindSessionController(parent);
|
||||
Core::App().openInternalUrl(
|
||||
|
@ -373,7 +415,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
|||
});
|
||||
});
|
||||
content->setHideCallback([=] {
|
||||
config->dismissSuggestion(kSugSetBirthday.utf8());
|
||||
promo->dismiss(kSugSetBirthday.utf8());
|
||||
repeat(repeat);
|
||||
});
|
||||
content->setContent(
|
||||
|
@ -387,13 +429,13 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
|||
Toggle{ true, anim::type::normal });
|
||||
return;
|
||||
} else if (session->premiumPossible() && !session->premium()) {
|
||||
const auto isPremiumAnnual = config->suggestionCurrent(
|
||||
const auto isPremiumAnnual = promo->current(
|
||||
kSugPremiumAnnual.utf8());
|
||||
const auto isPremiumRestore = !isPremiumAnnual
|
||||
&& config->suggestionCurrent(kSugPremiumRestore.utf8());
|
||||
&& promo->current(kSugPremiumRestore.utf8());
|
||||
const auto isPremiumUpgrade = !isPremiumAnnual
|
||||
&& !isPremiumRestore
|
||||
&& config->suggestionCurrent(kSugPremiumUpgrade.utf8());
|
||||
&& promo->current(kSugPremiumUpgrade.utf8());
|
||||
const auto set = [=](QString discount) {
|
||||
constexpr auto kMinus = QChar(0x2212);
|
||||
const auto &title = isPremiumAnnual
|
||||
|
@ -416,7 +458,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
|||
content->setClickedCallback([=] {
|
||||
const auto controller = FindSessionController(parent);
|
||||
Settings::ShowPremium(controller, "dialogs_hint");
|
||||
config->dismissSuggestion(isPremiumAnnual
|
||||
promo->dismiss(isPremiumAnnual
|
||||
? kSugPremiumAnnual.utf8()
|
||||
: isPremiumRestore
|
||||
? kSugPremiumRestore.utf8()
|
||||
|
@ -428,6 +470,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
|||
};
|
||||
if (isPremiumAnnual || isPremiumRestore || isPremiumUpgrade) {
|
||||
content->setRightIcon(RightIcon::Arrow);
|
||||
content->setLeftPadding(0);
|
||||
const auto api = &session->api().premium();
|
||||
api->statusTextValue() | rpl::start_with_next([=] {
|
||||
for (const auto &o : api->subscriptionOptions()) {
|
||||
|
@ -442,7 +485,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (config->suggestionCurrent(kSugSetUserpic.utf8())
|
||||
if (promo->current(kSugSetUserpic.utf8())
|
||||
&& !session->user()->userpicPhotoId()) {
|
||||
const auto controller = FindSessionController(parent);
|
||||
content->setRightIcon(RightIcon::Close);
|
||||
|
@ -484,7 +527,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
|||
});
|
||||
|
||||
content->setHideCallback([=] {
|
||||
config->dismissSuggestion(kSugSetUserpic.utf8());
|
||||
promo->dismiss(kSugSetUserpic.utf8());
|
||||
repeat(repeat);
|
||||
});
|
||||
|
||||
|
@ -541,7 +584,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
|
|||
(was == now) ? toggle.type : anim::type::instant);
|
||||
}, lifetime);
|
||||
|
||||
session->appConfig().value() | rpl::start_with_next([=] {
|
||||
session->promoSuggestions().value() | rpl::start_with_next([=] {
|
||||
const auto was = state->wrap;
|
||||
processCurrentSuggestion(processCurrentSuggestion);
|
||||
if (was != state->wrap) {
|
||||
|
|
|
@ -109,8 +109,8 @@ constexpr auto kSearchRequestDelay = crl::time(900);
|
|||
|
||||
base::options::toggle OptionForumHideChatsList({
|
||||
.id = kOptionForumHideChatsList,
|
||||
.name = "Hide chats list in forums",
|
||||
.description = "Don't keep a narrow column of chats list.",
|
||||
.name = "Hide chat list in forums",
|
||||
.description = "Don't keep a narrow column of chat list.",
|
||||
});
|
||||
|
||||
[[nodiscard]] bool RedirectTextToSearch(const QString &text) {
|
||||
|
@ -2528,7 +2528,19 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
|
|||
};
|
||||
if (trimmed.isEmpty() && !fromPeer && inTags.empty()) {
|
||||
cancelSearchRequest();
|
||||
|
||||
// Otherwise inside first searchApplyEmpty we call searchMode(),
|
||||
// which tries to load migrated search results for empty query.
|
||||
_migratedProcess.full = true;
|
||||
|
||||
searchApplyEmpty(fromStartType, currentSearchProcess());
|
||||
if (_searchInMigrated) {
|
||||
const auto type = SearchRequestType{
|
||||
.migrated = true,
|
||||
.start = true,
|
||||
};
|
||||
searchApplyEmpty(type, &_migratedProcess);
|
||||
}
|
||||
if (_searchWithPostsPreview) {
|
||||
searchApplyEmpty(
|
||||
{ .posts = true, .start = true },
|
||||
|
@ -2950,7 +2962,8 @@ auto Widget::currentSearchProcess() -> not_null<SearchProcessState*> {
|
|||
|
||||
bool Widget::computeSearchWithPostsPreview() const {
|
||||
return (_searchHashOrCashtag != HashOrCashtag::None)
|
||||
&& (_searchState.tab == ChatSearchTab::MyMessages);
|
||||
&& (_searchState.tab == ChatSearchTab::MyMessages)
|
||||
&& !_searchState.inChat;
|
||||
}
|
||||
|
||||
void Widget::searchReceived(
|
||||
|
@ -3488,7 +3501,9 @@ bool Widget::applySearchState(SearchState state) {
|
|||
: ChatSearchTab::MyMessages;
|
||||
}
|
||||
|
||||
const auto migrateFrom = (peer && !topic)
|
||||
const auto migrateFrom = (peer
|
||||
&& !topic
|
||||
&& state.tab == ChatSearchTab::ThisPeer)
|
||||
? peer->migrateFrom()
|
||||
: nullptr;
|
||||
_searchInMigrated = migrateFrom
|
||||
|
|
|
@ -6,7 +6,7 @@ For license and copyright information please follow this link:
|
|||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#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/ui_rpl_filter.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
@ -141,28 +141,19 @@ void TopBarSuggestionContent::draw(QPainter &p) {
|
|||
void TopBarSuggestionContent::setContent(
|
||||
TextWithEntities title,
|
||||
TextWithEntities description,
|
||||
bool makeContext) {
|
||||
if (makeContext) {
|
||||
auto customEmojiFactory = [=, h = _contentTitleSt.font->height](
|
||||
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),
|
||||
};
|
||||
std::optional<Ui::Text::MarkedContext> context) {
|
||||
if (context) {
|
||||
context->repaint = [=] { update(); };
|
||||
_contentTitle.setMarkedText(
|
||||
_contentTitleSt,
|
||||
std::move(title),
|
||||
kMarkupTextOptions,
|
||||
context);
|
||||
*context);
|
||||
_contentText.setMarkedText(
|
||||
_contentTextSt,
|
||||
std::move(description),
|
||||
kMarkupTextOptions,
|
||||
context);
|
||||
base::take(*context));
|
||||
} else {
|
||||
_contentTitle.setMarkedText(_contentTitleSt, std::move(title));
|
||||
_contentText.setMarkedText(_contentTextSt, std::move(description));
|
||||
|
@ -194,6 +185,7 @@ rpl::producer<int> TopBarSuggestionContent::desiredHeightValue() const {
|
|||
}
|
||||
|
||||
void TopBarSuggestionContent::setHideCallback(Fn<void()> hideCallback) {
|
||||
Expects(_rightHide != nullptr);
|
||||
_rightHide->setClickedCallback(std::move(hideCallback));
|
||||
}
|
||||
|
||||
|
@ -202,4 +194,8 @@ void TopBarSuggestionContent::setLeftPadding(int value) {
|
|||
update();
|
||||
}
|
||||
|
||||
const style::TextStyle & TopBarSuggestionContent::contentTitleSt() const {
|
||||
return _contentTitleSt;
|
||||
}
|
||||
|
||||
} // namespace Dialogs
|
||||
|
|
|
@ -14,6 +14,10 @@ class DynamicImage;
|
|||
class IconButton;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::Text {
|
||||
struct MarkedContext;
|
||||
} // namespace Ui::Text
|
||||
|
||||
namespace Dialogs {
|
||||
|
||||
class TopBarSuggestionContent : public Ui::RippleButton {
|
||||
|
@ -29,7 +33,7 @@ public:
|
|||
void setContent(
|
||||
TextWithEntities title,
|
||||
TextWithEntities description,
|
||||
bool makeContext = false);
|
||||
std::optional<Ui::Text::MarkedContext> context = std::nullopt);
|
||||
|
||||
[[nodiscard]] rpl::producer<int> desiredHeightValue() const override;
|
||||
|
||||
|
@ -37,6 +41,8 @@ public:
|
|||
void setRightIcon(RightIcon);
|
||||
void setLeftPadding(int);
|
||||
|
||||
[[nodiscard]] const style::TextStyle &contentTitleSt() const;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
|
||||
|
|
|
@ -25,12 +25,18 @@ EditFlagsDescriptor<FilterValue::Flags> FilterValueLabels(bool isChannel) {
|
|||
| Flag::Unkick;
|
||||
const auto membersNew = Flag::Join | Flag::Invite;
|
||||
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>{
|
||||
{ adminRights, tr::lng_admin_log_filter_admins_new(tr::now) },
|
||||
{ restrictions, tr::lng_admin_log_filter_restrictions(tr::now) },
|
||||
{ membersNew, tr::lng_admin_log_filter_members_new(tr::now) },
|
||||
{ membersRemoved, tr::lng_admin_log_filter_members_removed(tr::now) },
|
||||
{ membersNew, std::move(membersNewText) },
|
||||
{ membersRemoved, std::move(membersRemovedText) },
|
||||
};
|
||||
|
||||
const auto info = Flag::Info | Flag::Settings;
|
||||
|
@ -76,11 +82,15 @@ EditFlagsDescriptor<FilterValue::Flags> FilterValueLabels(bool isChannel) {
|
|||
}
|
||||
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),
|
||||
},
|
||||
{
|
||||
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),
|
||||
},
|
||||
{
|
||||
|
|
|
@ -831,6 +831,7 @@ void GenerateItems(
|
|||
using LogChangeEmojiStatus = MTPDchannelAdminLogEventActionChangeEmojiStatus;
|
||||
using LogToggleSignatureProfiles = MTPDchannelAdminLogEventActionToggleSignatureProfiles;
|
||||
using LogParticipantSubExtend = MTPDchannelAdminLogEventActionParticipantSubExtend;
|
||||
using LogToggleAutotranslation = MTPDchannelAdminLogEventActionToggleAutotranslation;
|
||||
|
||||
const auto session = &history->session();
|
||||
const auto id = event.vid().v;
|
||||
|
@ -2115,6 +2116,18 @@ void GenerateItems(
|
|||
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(
|
||||
createChangeTitle,
|
||||
createChangeAbout,
|
||||
|
@ -2165,7 +2178,8 @@ void GenerateItems(
|
|||
createChangeWallpaper,
|
||||
createChangeEmojiStatus,
|
||||
createToggleSignatureProfiles,
|
||||
createParticipantSubExtend);
|
||||
createParticipantSubExtend,
|
||||
createToggleAutotranslation);
|
||||
}
|
||||
|
||||
} // namespace AdminLog
|
||||
|
|
|
@ -3696,6 +3696,11 @@ void History::translateOfferFrom(LanguageId id) {
|
|||
}
|
||||
} else if (!_translation) {
|
||||
_translation = std::make_unique<HistoryTranslation>(this, id);
|
||||
using Flag = PeerData::TranslationFlag;
|
||||
if (peer->autoTranslation()
|
||||
&& (peer->translationFlag() == Flag::Enabled)) {
|
||||
translateTo(Core::App().settings().translateTo());
|
||||
}
|
||||
} else {
|
||||
_translation->offerFrom(id);
|
||||
}
|
||||
|
|
|
@ -580,7 +580,8 @@ void HistoryInner::setupSwipeReplyAndBack() {
|
|||
}
|
||||
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 (!_swipeBackData.callback) {
|
||||
_swipeBackData = Ui::Controls::SetupSwipeBack(
|
||||
|
@ -616,9 +617,24 @@ void HistoryInner::setupSwipeReplyAndBack() {
|
|||
int cursorTop,
|
||||
Qt::LayoutDirection direction) {
|
||||
if (direction == Qt::RightToLeft) {
|
||||
return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] {
|
||||
_controller->showBackFromStack();
|
||||
auto good = true;
|
||||
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();
|
||||
if (inSelectionMode().inSelectionMode
|
||||
|
@ -632,6 +648,7 @@ void HistoryInner::setupSwipeReplyAndBack() {
|
|||
if ((cursorTop < itemtop)
|
||||
|| (cursorTop > itembottom)
|
||||
|| !view->data()->isRegular()
|
||||
|| view->data()->showSimilarChannels()
|
||||
|| view->data()->isService()) {
|
||||
return true;
|
||||
}
|
||||
|
@ -3873,9 +3890,7 @@ void HistoryInner::elementSendBotCommand(
|
|||
void HistoryInner::elementSearchInList(
|
||||
const QString &query,
|
||||
const FullMsgId &context) {
|
||||
const auto inChat = _history->peer->isUser()
|
||||
? Dialogs::Key()
|
||||
: Dialogs::Key(_history);
|
||||
const auto inChat = Dialogs::Key(_history);
|
||||
_controller->searchMessages(query, inChat);
|
||||
}
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ template <typename T>
|
|||
}
|
||||
|
||||
[[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);
|
||||
if (!m.hasMatch()) {
|
||||
return text;
|
||||
|
@ -1099,14 +1099,14 @@ void HistoryItem::setGroupId(MessageGroupId groupId) {
|
|||
_history->owner().groups().registerMessage(this);
|
||||
}
|
||||
|
||||
bool HistoryItem::checkCommentsLinkedChat(ChannelId id) const {
|
||||
bool HistoryItem::checkDiscussionLink(ChannelId id) const {
|
||||
if (!id) {
|
||||
return true;
|
||||
} else if (const auto channel = _history->peer->asChannel()) {
|
||||
if (channel->linkedChatKnown()
|
||||
if (channel->discussionLinkKnown()
|
||||
|| !(channel->flags() & ChannelDataFlag::HasLink)) {
|
||||
const auto linked = channel->linkedChat();
|
||||
if (!linked || peerToChannel(linked->id) != id) {
|
||||
const auto link = channel->discussionLink();
|
||||
if (!link || peerToChannel(link->id) != id) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1209,7 +1209,7 @@ bool HistoryItem::areCommentsUnread() const {
|
|||
const auto views = Get<HistoryMessageViews>();
|
||||
if (!views
|
||||
|| !views->commentsMegagroupId
|
||||
|| !checkCommentsLinkedChat(views->commentsMegagroupId)) {
|
||||
|| !checkDiscussionLink(views->commentsMegagroupId)) {
|
||||
return false;
|
||||
}
|
||||
const auto till = views->commentsInboxReadTillId;
|
||||
|
@ -3469,7 +3469,7 @@ int HistoryItem::viewsCount() const {
|
|||
|
||||
int HistoryItem::repliesCount() const {
|
||||
if (const auto views = Get<HistoryMessageViews>()) {
|
||||
if (!checkCommentsLinkedChat(views->commentsMegagroupId)) {
|
||||
if (!checkDiscussionLink(views->commentsMegagroupId)) {
|
||||
return 0;
|
||||
}
|
||||
return std::max(views->replies.count, 0);
|
||||
|
@ -3480,7 +3480,7 @@ int HistoryItem::repliesCount() const {
|
|||
bool HistoryItem::repliesAreComments() const {
|
||||
if (const auto views = Get<HistoryMessageViews>()) {
|
||||
return (views->commentsMegagroupId != 0)
|
||||
&& checkCommentsLinkedChat(views->commentsMegagroupId);
|
||||
&& checkDiscussionLink(views->commentsMegagroupId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -3977,9 +3977,9 @@ void HistoryItem::createComponents(CreateConfig &&config) {
|
|||
&& isSending()
|
||||
&& config.markup.isNull()) {
|
||||
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.channelId = peerToChannel(linked->id);
|
||||
config.replies.channelId = peerToChannel(link->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5783,6 +5783,11 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
|||
const MTPDmessageActionStarGiftUnique &action) {
|
||||
auto result = PreparedServiceText();
|
||||
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()
|
||||
? peerFromMTP(*action.vpeer())
|
||||
: PeerId();
|
||||
|
@ -5798,49 +5803,92 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
|||
peerToChannel(giftPeer));
|
||||
if (!from->isServiceUser() && !from->isSelf()) {
|
||||
result.links.push_back(from->createOpenLink());
|
||||
result.text = (action.is_upgrade()
|
||||
? tr::lng_action_gift_upgraded_channel
|
||||
: tr::lng_action_gift_transferred_channel)(
|
||||
if (resaleStars) {
|
||||
result.links.push_back(channel->createOpenLink());
|
||||
}
|
||||
result.text = resaleStars
|
||||
? tr::lng_action_gift_sent_channel(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Link(from->shortName(), 1),
|
||||
lt_channel,
|
||||
lt_name,
|
||||
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 {
|
||||
result.text = (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)(
|
||||
result.text = resaleStars
|
||||
? tr::lng_action_gift_sent_self_channel(
|
||||
tr::now,
|
||||
lt_channel,
|
||||
lt_name,
|
||||
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());
|
||||
} else {
|
||||
if (!from->isServiceUser() && !_history->peer->isSelf()) {
|
||||
result.links.push_back(from->createOpenLink());
|
||||
result.text = (action.is_upgrade()
|
||||
if (!resaleStars || !isSelf) {
|
||||
result.links.push_back(from->createOpenLink());
|
||||
}
|
||||
result.text = resaleStars
|
||||
? (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::lng_action_gift_sent(
|
||||
tr::now,
|
||||
lt_cost,
|
||||
resaleCost,
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_action_gift_received(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Link(from->shortName(), 1), // Link 1.
|
||||
Ui::Text::WithEntities);
|
||||
Ui::Text::Link(peer->shortName(), 1), // Link 1.
|
||||
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 {
|
||||
result.text = (from->isServiceUser()
|
||||
? tr::lng_action_gift_transferred_unknown
|
||||
: action.is_upgrade()
|
||||
? tr::lng_action_gift_upgraded_self
|
||||
: tr::lng_action_gift_transferred_self)(
|
||||
result.text = resaleStars
|
||||
? tr::lng_action_gift_self_bought(
|
||||
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;
|
||||
|
@ -6148,6 +6196,8 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
|
|||
unique->starsForTransfer
|
||||
= data.vtransfer_stars().value_or(-1);
|
||||
unique->exportAt = data.vcan_export_at().value_or_empty();
|
||||
unique->canTransferAt = data.vcan_transfer_at().value_or_empty();
|
||||
unique->canResellAt = data.vcan_resell_at().value_or_empty();
|
||||
}
|
||||
}
|
||||
_media = std::make_unique<Data::MediaGiftBox>(
|
||||
|
|
|
@ -323,7 +323,7 @@ public:
|
|||
return (_flags & MessageFlag::HideEdited);
|
||||
}
|
||||
[[nodiscard]] bool hideDisplayDate() const {
|
||||
return (_flags & MessageFlag::HideDisplayDate);
|
||||
return isEmpty() || (_flags & MessageFlag::HideDisplayDate);
|
||||
}
|
||||
[[nodiscard]] bool isLocal() const {
|
||||
return _flags & MessageFlag::Local;
|
||||
|
@ -605,7 +605,7 @@ private:
|
|||
return _flags & MessageFlag::Legacy;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool checkCommentsLinkedChat(ChannelId id) const;
|
||||
[[nodiscard]] bool checkDiscussionLink(ChannelId id) const;
|
||||
|
||||
void setReplyMarkup(HistoryMessageMarkupData &&markup);
|
||||
|
||||
|
|
|
@ -872,7 +872,7 @@ HistoryWidget::HistoryWidget(
|
|||
| PeerUpdateFlag::OnlineStatus
|
||||
| PeerUpdateFlag::Notifications
|
||||
| PeerUpdateFlag::ChannelAmIn
|
||||
| PeerUpdateFlag::ChannelLinkedChat
|
||||
| PeerUpdateFlag::DiscussionLink
|
||||
| PeerUpdateFlag::Slowmode
|
||||
| PeerUpdateFlag::BotStartToken
|
||||
| PeerUpdateFlag::MessagesTTL
|
||||
|
@ -944,7 +944,7 @@ HistoryWidget::HistoryWidget(
|
|||
| PeerUpdateFlag::OnlineStatus
|
||||
| PeerUpdateFlag::Rights
|
||||
| PeerUpdateFlag::ChannelAmIn
|
||||
| PeerUpdateFlag::ChannelLinkedChat)) {
|
||||
| PeerUpdateFlag::DiscussionLink)) {
|
||||
handlePeerUpdate();
|
||||
}
|
||||
if (flags & PeerUpdateFlag::MessagesTTL) {
|
||||
|
@ -7819,12 +7819,12 @@ void HistoryWidget::checkPinnedBarState() {
|
|||
}
|
||||
return (count > 1);
|
||||
}) | rpl::distinct_until_changed();
|
||||
auto markupRefreshed = HistoryView::PinnedBarItemWithReplyMarkup(
|
||||
auto customButtonItem = HistoryView::PinnedBarItemWithCustomButton(
|
||||
&session(),
|
||||
_pinnedTracker->shownMessageId());
|
||||
rpl::combine(
|
||||
rpl::duplicate(pinnedRefreshed),
|
||||
rpl::duplicate(markupRefreshed)
|
||||
rpl::duplicate(customButtonItem)
|
||||
) | rpl::start_with_next([=](bool many, HistoryItem *item) {
|
||||
refreshPinnedBarButton(many, item);
|
||||
}, _pinnedBar->lifetime());
|
||||
|
@ -7835,7 +7835,7 @@ void HistoryWidget::checkPinnedBarState() {
|
|||
_pinnedTracker->shownMessageId(),
|
||||
[bar = _pinnedBar.get()] { bar->customEmojiRepaint(); }),
|
||||
std::move(pinnedRefreshed),
|
||||
std::move(markupRefreshed)
|
||||
std::move(customButtonItem)
|
||||
) | rpl::map([=](Ui::MessageBarContent &&content, bool, HistoryItem*) {
|
||||
const auto id = (!content.title.isEmpty() || !content.text.empty())
|
||||
? _pinnedTracker->currentMessageId().message
|
||||
|
@ -7973,58 +7973,31 @@ void HistoryWidget::refreshPinnedBarButton(bool many, HistoryItem *item) {
|
|||
? id.message.msg
|
||||
: (id.message.msg - ServerMaxMsgId))));
|
||||
};
|
||||
if (const auto replyMarkup = item ? item->inlineReplyMarkup() : nullptr) {
|
||||
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 &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;
|
||||
}
|
||||
const auto context = [copy = _list](FullMsgId itemId) {
|
||||
if (const auto raw = copy.data()) {
|
||||
return raw->prepareClickHandlerContext(itemId);
|
||||
}
|
||||
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;
|
||||
auto button = object_ptr<Ui::IconButton>(
|
||||
this,
|
||||
|
|
|
@ -2325,11 +2325,17 @@ void SetupRestrictionView(
|
|||
) | rpl::start_with_next([=](Controls::WriteRestriction value) {
|
||||
using Type = Controls::WriteRestriction::Type;
|
||||
if (value.type == Type::Frozen) {
|
||||
state->icon = nullptr;
|
||||
state->unlock = nullptr;
|
||||
state->label = nullptr;
|
||||
state->button = FrozenWriteRestriction(
|
||||
widget,
|
||||
show,
|
||||
FrozenWriteRestrictionType::MessageField);
|
||||
} else if (const auto lifting = value.boostsToLift) {
|
||||
state->icon = nullptr;
|
||||
state->unlock = nullptr;
|
||||
state->label = nullptr;
|
||||
state->button = BoostsToLiftWriteRestriction(
|
||||
widget,
|
||||
show,
|
||||
|
|
|
@ -1421,7 +1421,7 @@ void Element::recountDisplayDateInBlocks() {
|
|||
|
||||
if (const auto previous = previousDisplayedInBlocks()) {
|
||||
const auto prev = previous->data();
|
||||
return prev->isEmpty()
|
||||
return prev->hideDisplayDate()
|
||||
|| (previous->dateTime().date() != dateTime().date());
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -4087,7 +4087,8 @@ void ListWidget::refreshAttachmentsFromTill(int from, int till) {
|
|||
const auto viewDate = view->dateTime();
|
||||
const auto nextDate = next->dateTime();
|
||||
next->setDisplayDate(_context != Context::ShortcutMessages
|
||||
&& nextDate.date() != viewDate.date());
|
||||
&& (nextDate.date() != viewDate.date()
|
||||
|| view->data()->hideDisplayDate()));
|
||||
auto attached = next->computeIsAttachToPrevious(view);
|
||||
next->setAttachToPrevious(attached, view);
|
||||
view->setAttachToNext(attached, next);
|
||||
|
|
|
@ -1224,6 +1224,9 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
|||
}
|
||||
if (!mediaOnBottom && (!_viewButton || !reactionsInBubble)) {
|
||||
localMediaBottom -= st::msgPadding.bottom();
|
||||
if (mediaDisplayed) {
|
||||
localMediaBottom -= st::mediaInBubbleSkip;
|
||||
}
|
||||
}
|
||||
if (check) {
|
||||
localMediaBottom -= check->height();
|
||||
|
|
|
@ -7,17 +7,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "history/view/history_view_pinned_bar.h"
|
||||
|
||||
#include "api/api_bot.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "history/view/history_view_pinned_tracker.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/history.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "base/weak_ptr.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_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
|
||||
|
||||
rpl::producer<Ui::MessageBarContent> MessageBarContentByItemId(
|
||||
|
@ -178,7 +224,7 @@ rpl::producer<Ui::MessageBarContent> PinnedBarContent(
|
|||
}) | rpl::flatten_latest();
|
||||
}
|
||||
|
||||
rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
|
||||
rpl::producer<HistoryItem*> PinnedBarItemWithCustomButton(
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<PinnedId> id) {
|
||||
return rpl::make_producer<HistoryItem*>([=,
|
||||
|
@ -187,7 +233,7 @@ rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
|
|||
consumer.put_next(nullptr);
|
||||
|
||||
struct State {
|
||||
bool hasReplyMarkup = false;
|
||||
bool hasCustomButton = false;
|
||||
base::has_weak_ptr guard;
|
||||
rpl::lifetime lifetime;
|
||||
FullMsgId resolvedId;
|
||||
|
@ -196,10 +242,17 @@ rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
|
|||
|
||||
const auto pushUnique = [=](not_null<HistoryItem*> item) {
|
||||
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;
|
||||
}
|
||||
state->hasReplyMarkup = (replyMarkup != nullptr);
|
||||
state->hasCustomButton = possiblyHasCustomButton;
|
||||
consumer.put_next(item.get());
|
||||
};
|
||||
|
||||
|
@ -217,12 +270,14 @@ rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
|
|||
using Update = Data::MessageUpdate;
|
||||
session->changes().messageUpdates(
|
||||
item,
|
||||
Update::Flag::ReplyMarkup | Update::Flag::Destroyed
|
||||
(Update::Flag::ReplyMarkup
|
||||
| Update::Flag::Edited
|
||||
| Update::Flag::Destroyed)
|
||||
) | rpl::start_with_next([=](const Update &update) {
|
||||
if (update.flags & Update::Flag::Destroyed) {
|
||||
state->lifetime.destroy();
|
||||
invalidate_weak_ptrs(&state->guard);
|
||||
state->hasReplyMarkup = false;
|
||||
state->hasCustomButton = false;
|
||||
consumer.put_next(nullptr);
|
||||
} else {
|
||||
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
|
||||
|
|
|
@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/object_ptr.h"
|
||||
#include "ui/chat/message_bar.h"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
struct ClickHandlerContext;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
@ -18,6 +21,7 @@ class Session;
|
|||
namespace Ui {
|
||||
class IconButton;
|
||||
class PlainShadow;
|
||||
class RoundButton;
|
||||
struct MessageBarContent;
|
||||
} // namespace Ui
|
||||
|
||||
|
@ -51,8 +55,13 @@ struct PinnedId {
|
|||
rpl::producer<PinnedId> id,
|
||||
Fn<void()> repaint);
|
||||
|
||||
[[nodiscard]] rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
|
||||
[[nodiscard]] rpl::producer<HistoryItem*> PinnedBarItemWithCustomButton(
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<PinnedId> id);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RoundButton> CreatePinnedBarCustomButton(
|
||||
not_null<QWidget*> parent,
|
||||
HistoryItem *item,
|
||||
Fn<ClickHandlerContext(FullMsgId)> context);
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
|
@ -1846,12 +1846,12 @@ void RepliesWidget::checkPinnedBarState() {
|
|||
}
|
||||
return (count > 1);
|
||||
}) | rpl::distinct_until_changed();
|
||||
auto markupRefreshed = HistoryView::PinnedBarItemWithReplyMarkup(
|
||||
auto customButtonItem = HistoryView::PinnedBarItemWithCustomButton(
|
||||
&session(),
|
||||
_pinnedTracker->shownMessageId());
|
||||
rpl::combine(
|
||||
rpl::duplicate(pinnedRefreshed),
|
||||
rpl::duplicate(markupRefreshed)
|
||||
rpl::duplicate(customButtonItem)
|
||||
) | rpl::start_with_next([=](bool many, HistoryItem *item) {
|
||||
refreshPinnedBarButton(many, item);
|
||||
}, _pinnedBar->lifetime());
|
||||
|
@ -1862,7 +1862,7 @@ void RepliesWidget::checkPinnedBarState() {
|
|||
_pinnedTracker->shownMessageId(),
|
||||
[bar = _pinnedBar.get()] { bar->customEmojiRepaint(); }),
|
||||
std::move(pinnedRefreshed),
|
||||
std::move(markupRefreshed),
|
||||
std::move(customButtonItem),
|
||||
_rootVisible.value()
|
||||
) | rpl::map([=](Ui::MessageBarContent &&content, auto, auto, bool show) {
|
||||
const auto shown = !content.title.isEmpty() && !content.text.empty();
|
||||
|
@ -1943,43 +1943,29 @@ void RepliesWidget::refreshPinnedBarButton(bool many, HistoryItem *item) {
|
|||
controller()->showSection(
|
||||
std::make_shared<PinnedMemento>(_topic, id.message.msg));
|
||||
};
|
||||
if (const auto replyMarkup = item ? item->inlineReplyMarkup() : nullptr) {
|
||||
const auto &rows = replyMarkup->data.rows;
|
||||
if ((rows.size() == 1) && (rows.front().size() == 1)) {
|
||||
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;
|
||||
}
|
||||
const auto context = [copy = _inner](FullMsgId itemId) {
|
||||
if (const auto raw = copy.data()) {
|
||||
return raw->prepareClickHandlerContext(itemId);
|
||||
}
|
||||
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;
|
||||
auto button = object_ptr<Ui::IconButton>(
|
||||
|
|
|
@ -1624,7 +1624,7 @@ void TopBarWidget::refreshUnreadBadge() {
|
|||
geometry.y() + st::titleUnreadCounterTop);
|
||||
}, _unreadBadge->lifetime());
|
||||
|
||||
_unreadBadge->show();
|
||||
_unreadBadge->setVisible(!rootChatsListBar());
|
||||
_unreadBadge->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
_controller->session().data().unreadBadgeChanges(
|
||||
) | rpl::start_with_next([=] {
|
||||
|
|
|
@ -372,10 +372,16 @@ void TranslateBar::setup(not_null<History*> history) {
|
|||
const auto&,
|
||||
const auto&) {
|
||||
using Flag = PeerData::TranslationFlag;
|
||||
const auto automatic = history->peer->autoTranslation();
|
||||
return (history->peer->translationFlag() != Flag::Enabled)
|
||||
? rpl::single(QString())
|
||||
: 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()
|
||||
? Ui::TranslateBarTo(to)
|
||||
: rpl::single(QString());
|
||||
|
|
|
@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "core/application.h"
|
||||
#include "core/core_settings.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_session.h"
|
||||
#include "history/history.h"
|
||||
|
@ -51,14 +53,21 @@ void TranslateTracker::setup() {
|
|||
const auto peer = _history->peer;
|
||||
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;
|
||||
_trackingLanguage = rpl::combine(
|
||||
Data::AmPremiumValue(&_history->session()),
|
||||
Core::App().settings().translateChatEnabledValue(),
|
||||
_1 && _2);
|
||||
|
||||
_trackingLanguage.value(
|
||||
) | rpl::start_with_next([=](bool tracking) {
|
||||
Data::AmPremiumValue(&_history->session()),
|
||||
std::move(autoTranslationValue),
|
||||
_1 && (_2 || _3));
|
||||
_trackingLanguage.value() | rpl::start_with_next([=](bool tracking) {
|
||||
_trackingLifetime.destroy();
|
||||
if (tracking) {
|
||||
recognizeCollected();
|
||||
|
@ -101,7 +110,7 @@ bool TranslateTracker::add(
|
|||
bool skipDependencies) {
|
||||
Expects(_addedInBunch >= 0);
|
||||
|
||||
if (item->out()
|
||||
if ((item->out() && !item->history()->peer->autoTranslation())
|
||||
|| item->isService()
|
||||
|| !item->isRegular()
|
||||
|| item->isOnlyEmojiAndSpaces()) {
|
||||
|
|
|
@ -172,7 +172,9 @@ QSize GroupedMedia::countOptimalSize() {
|
|||
_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 msgsigned = item->Get<HistoryMessageSigned>();
|
||||
const auto views = item->Get<HistoryMessageViews>();
|
||||
|
@ -236,7 +238,9 @@ QSize GroupedMedia::countCurrentSize(int newWidth) {
|
|||
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 msgsigned = item->Get<HistoryMessageSigned>();
|
||||
const auto views = item->Get<HistoryMessageViews>();
|
||||
|
|