diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 947fc9178..7a65b483f 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -28,6 +28,7 @@ jobs:
run: |
sudo apt update
curl -sSL https://install.python-poetry.org | python3 -
+ echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
- name: Free up some disk space.
uses: jlumbroso/free-disk-space@f68fdb76e2ea636224182cfb7377ff9a1708f9b8
@@ -40,6 +41,4 @@ jobs:
- name: Push the Docker image.
if: ${{ github.ref_name == github.event.repository.default_branch }}
- run: |
- echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
- docker push $IMAGE_TAG
+ run: docker push $IMAGE_TAG
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 4961c68f3..5ded5a040 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -43,15 +43,6 @@ jobs:
linux:
name: Rocky Linux 8
runs-on: ubuntu-latest
- container:
- image: ghcr.io/${{ github.repository }}/centos_env
- credentials:
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- defaults:
- run:
- shell: scl enable gcc-toolset-12 -- bash --noprofile --norc -eo pipefail {0}
strategy:
matrix:
@@ -75,12 +66,13 @@ jobs:
- name: First set up.
run: |
- gcc --version
- ln -s /usr/src/Libraries
+ echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
+ docker pull ghcr.io/$GITHUB_REPOSITORY/centos_env
+ docker tag ghcr.io/$GITHUB_REPOSITORY/centos_env tdesktop:centos_env
- name: Telegram Desktop build.
run: |
- cd $REPO_NAME/Telegram
+ cd $REPO_NAME
DEFINE=""
if [ -n "${{ matrix.defines }}" ]; then
@@ -91,7 +83,11 @@ jobs:
echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV
fi
- ./configure.sh \
+ docker run --rm \
+ -v $PWD:/usr/src/tdesktop \
+ -e DEBUG=1 \
+ tdesktop:centos_env \
+ /usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \
-D CMAKE_C_FLAGS_DEBUG="" \
-D CMAKE_CXX_FLAGS_DEBUG="" \
-D CMAKE_C_FLAGS="-Werror" \
@@ -101,8 +97,6 @@ jobs:
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
$DEFINE
- cmake --build ../out --config Debug --parallel
-
- name: Check.
run: |
filePath="$REPO_NAME/out/Debug/Telegram"
diff --git a/Telegram/Resources/art/winners.tgs b/Telegram/Resources/art/winners.tgs
new file mode 100644
index 000000000..d1112ecbe
Binary files /dev/null and b/Telegram/Resources/art/winners.tgs differ
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 42b25743a..9ba142178 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -591,6 +591,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_section_background" = "Chat background";
"lng_settings_bg_from_gallery" = "Choose from gallery";
"lng_settings_bg_from_file" = "Choose from file";
+"lng_settings_bg_remove" = "Remove wallpaper";
"lng_settings_bg_theme_edit" = "Edit theme";
"lng_settings_bg_theme_create" = "Create new theme";
"lng_settings_bg_cloud_themes" = "Custom themes";
@@ -813,6 +814,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_manage_enabled_dictionary" = "Dictionary is enabled";
"lng_settings_manage_remove_dictionary" = "Remove Dictionary";
+"lng_settings_gift_premium" = "Premium Gifting";
+"lng_settings_gift_premium_users_confirm" = "Proceed";
+"lng_settings_gift_premium_users_error#one" = "You can select maximum {count} user.";
+"lng_settings_gift_premium_users_error#other" = "You can select maximum {count} users.";
+
"lng_backgrounds_header" = "Choose Wallpaper";
"lng_theme_sure_keep" = "Keep this theme?";
"lng_theme_reverting#one" = "Reverting to the old theme in {count} second.";
@@ -833,6 +839,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_background_blur" = "Blurred";
"lng_background_sure_delete" = "Are you sure you want to delete this background?";
"lng_background_other_info" = "{user} will be able to apply this wallpaper";
+"lng_background_other_channel" = "All subscribers will see this wallpaper";
"lng_background_apply1" = "Apply the wallpaper in this chat.";
"lng_background_apply2" = "Enjoy the view.";
"lng_background_apply_button" = "Apply For This Chat";
@@ -841,6 +848,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_background_reset_default" = "Reset";
"lng_background_apply_me" = "Apply for me";
"lng_background_apply_both" = "Apply for me and {user}";
+"lng_background_apply_channel" = "Apply For Channel";
"lng_download_path_ask" = "Ask download path for each file";
"lng_download_path" = "Download path";
@@ -1682,7 +1690,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_giveaway_started" = "{from} just started a giveaway of Telegram Premium subscriptions to its followers.";
"lng_action_giveaway_results#one" = "{count} winner of the giveaway was randomly selected by Telegram and received private messages with giftcodes.";
"lng_action_giveaway_results#other" = "{count} winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes.";
-"lng_action_giveaway_results_some" = "Some winners of the giveaway was randomly selected by Telegram and received private messages with giftcodes.";
+"lng_action_giveaway_results_some" = "Some winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes.";
"lng_action_giveaway_results_none" = "No winners of the giveaway could be selected.";
"lng_similar_channels_title" = "Similar channels";
@@ -1836,6 +1844,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_sponsored_message_title" = "Sponsored";
"lng_recommended_message_title" = "Recommended";
"lng_edited" = "edited";
+"lng_commented" = "commented";
"lng_edited_date" = "Edited: {date}";
"lng_sent_date" = "Sent: {date}";
"lng_views_tooltip#one" = "Views: {count}";
@@ -2055,6 +2064,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_gift_per" = "{cost} / month";
"lng_premium_gift_terms" = "You can review the list of features and terms of use for Telegram Premium {link}.";
"lng_premium_gift_terms_link" = "here";
+"lng_premium_gifts_about_user1" = "Give **{user}** access to exclusive features.";
+"lng_premium_gifts_about_user2" = "Give **{user}** and **{second_user}** access to exclusive features.";
+"lng_premium_gifts_about_user3" = "Give **{user}**, **{second_user}** and **{name}** access to exclusive features.";
+"lng_premium_gifts_about_user_more#one" = "Give **{user}**, **{second_user}**, **{name}** and **{count}** more friend access to exclusive features.";
+"lng_premium_gifts_about_user_more#other" = "Give **{user}**, **{second_user}**, **{name}** and **{count}** more friends access to exclusive features.";
+"lng_premium_gifts_about_reward#one" = "You will receive {emoji}**{count}** boost.";
+"lng_premium_gifts_about_reward#other" = "You will receive {emoji}**{count}** boosts.";
+"lng_premium_gifts_about_paid_title" = "Gifts Sent!";
+"lng_premium_gifts_about_paid1" = "**{user}** has been notified about the gifts you purchased.";
+"lng_premium_gifts_about_paid2" = "**{user}** and **{second_user}** have been notified about the gifts you purchased.";
+"lng_premium_gifts_about_paid3" = "**{user}**, **{second_user}** and **{name}** have been notified about the gifts you purchased.";
+"lng_premium_gifts_about_paid_more#one" = "**{user}**, **{second_user}**, **{name}** and **{count}** other have been notified about the gifts you purchased.";
+"lng_premium_gifts_about_paid_more#other" = "**{user}**, **{second_user}**, **{name}** and **{count}** others have been notified about the gifts you purchased.";
+"lng_premium_gifts_about_paid_below#one" = "They now have access to additional features.";
+"lng_premium_gifts_about_paid_below#other" = "They now have access to additional features.";
+"lng_premium_gifts_summary_subtitle" = "What's Included";
"lng_boost_channel_button" = "Boost Channel";
"lng_boost_again_button" = "Boost Again";
@@ -2111,6 +2136,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color.";
"lng_boost_channel_needs_level_color#other" = "Your channel needs to reach **Level {count}** to change channel color.";
+"lng_boost_channel_title_wallpaper" = "Enable wallpapers";
+"lng_boost_channel_needs_level_wallpaper#one" = "Your channel needs to reach **Level {count}** to change channel wallpaper.";
+"lng_boost_channel_needs_level_wallpaper#other" = "Your channel needs to reach **Level {count}** to change channel wallpaper.";
+
+"lng_boost_channel_title_status" = "Enable emoji status";
+"lng_boost_channel_needs_level_status#one" = "Your channel needs to reach **Level {count}** to set emoji status.";
+"lng_boost_channel_needs_level_status#other" = "Your channel needs to reach **Level {count}** to set emoji status.";
+
"lng_boost_channel_title_reactions" = "Custom reactions";
"lng_boost_channel_needs_level_reactions#one" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as a reaction.";
"lng_boost_channel_needs_level_reactions#other" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as reactions.";
@@ -2170,6 +2203,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_giveaway_maximum_users_error#other" = "You can select maximum {count} users.";
"lng_giveaway_channels_confirm_title" = "Channel is Private";
"lng_giveaway_channels_confirm_about" = "Are you sure you want to add a private channel? Users won't be able to join it without an invite link.";
+"lng_giveaway_additional_prizes" = "Additional prizes";
+"lng_giveaway_additional_about" = "Turn this on if you want to give the winners your own prizes in addition to Premium subscriptions.";
+"lng_giveaway_additional_prizes_ph" = "Enter your prize";
+"lng_giveaway_prizes_just_premium#one" = "All prizes: **{count}** Telegram Premium subscription {duration}.";
+"lng_giveaway_prizes_just_premium#other" = "All prizes: **{count}** Telegram Premium subscriptions {duration}.";
+"lng_giveaway_prizes_additional#one" = "All prizes: **{count}** {prize} with Telegram Premium subscription {duration}.";
+"lng_giveaway_prizes_additional#other" = "All prizes: **{count}** {prize} with Telegram Premium subscriptions {duration}.";
+"lng_giveaway_show_winners" = "Show winners";
+"lng_giveaway_show_winners_about" = "Choose whether to make the list of winners public when the giveaway ends.";
"lng_giveaway_created_title" = "Giveaway created";
"lng_giveaway_created_body" = "Check your channels' {link} to see how this giveaway boosted your channel.";
@@ -2189,6 +2231,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_prizes_title#one" = "Giveaway Prize";
"lng_prizes_title#other" = "Giveaway Prizes";
+"lng_prizes_additional#one" = "**{count}** {prize}";
+"lng_prizes_additional#other" = "**{count}** {prize}";
+"lng_prizes_additional_with" = "with";
"lng_prizes_about#one" = "**{count}** Telegram Premium Subscription {duration}.";
"lng_prizes_about#other" = "**{count}** Telegram Premium Subscriptions {duration}.";
"lng_prizes_participants" = "Participants";
@@ -2207,6 +2252,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_prizes_end_text" = "This giveaway was sponsored by {admins}.";
"lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its followers";
"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers.";
+"lng_prizes_additional_added#one" = "{channel} also included **{count} {prize}** in the prize. Admins of the channel are responsible for delivering this prize.";
+"lng_prizes_additional_added#other" = "{channel} also included **{count} {prize}** in the prizes. Admins of the channel are responsible for delivering these prizes.";
"lng_prizes_how_when_finish" = "On {date}, Telegram will automatically select {winners}.";
"lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}.";
"lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link.";
@@ -2232,6 +2279,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_prizes_cancelled" = "The channel cancelled the prizes by reversing the payment for them.";
"lng_prizes_badge" = "x{amount}";
+"lng_prizes_results_title" = "Winners Selected!";
+"lng_prizes_results_about#one" = "**{count}** winner of the {link} was randomly selected by Telegram.";
+"lng_prizes_results_about#other" = "**{count}** winners of the {link} were randomly selected by Telegram.";
+"lng_prizes_results_link" = "Giveaway";
+"lng_prizes_results_winners" = "Winners";
+"lng_prizes_results_more#one" = "and {count} more!";
+"lng_prizes_results_more#other" = "and {count} more!";
+"lng_prizes_results_all" = "All winners received gift links in private messages.";
+"lng_prizes_results_some" = "Some winners couldn't be selected.";
+
"lng_gift_link_title" = "Gift Link";
"lng_gift_link_about" = "This link allows you to activate\na **Telegram Premium** subscription.";
"lng_gift_link_label_from" = "From";
@@ -2801,6 +2858,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_sign_messages" = "Sign messages";
"lng_edit_group" = "Edit group";
"lng_edit_channel_color" = "Change name color";
+"lng_edit_channel_level_min" = "Level 1+";
+"lng_edit_channel_wallpaper" = "Channel wallpaper";
+"lng_edit_channel_wallpaper_about" = "Set a wallpaper that will be visible for everyone reading your channel.";
+"lng_edit_channel_status" = "Channel emoji status";
+"lng_edit_channel_status_about" = "Choose a status that will be shown next to the channel's name.";
"lng_edit_self_title" = "Edit your name";
"lng_confirm_contact_data" = "New Contact";
"lng_add_contact" = "Create";
@@ -3615,6 +3677,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_admin_log_set_background_emoji" = "{from} set channel background emoji to {emoji}";
"lng_admin_log_change_background_emoji" = "{from} changed channel background emoji from {previous} to {emoji}";
"lng_admin_log_removed_background_emoji" = "{from} removed channel background emoji {emoji}";
+"lng_admin_log_change_profile_color" = "{from} changed channel profile color from {previous} to {color}";
+"lng_admin_log_set_profile_background_emoji" = "{from} set channel profile background emoji to {emoji}";
+"lng_admin_log_change_profile_background_emoji" = "{from} changed channel profile background emoji from {previous} to {emoji}";
+"lng_admin_log_removed_profile_background_emoji" = "{from} removed channel profile background emoji {emoji}";
+"lng_admin_log_change_wallpaper" = "{from} changed channel wallpaper";
+"lng_admin_log_set_status" = "{from} set channel emoji status to {emoji}";
+"lng_admin_log_change_status" = "{from} changed channel emoji status from {previous} to {emoji}";
+"lng_admin_log_removed_status" = "{from} removed channel emoji status {emoji}";
+"lng_admin_log_set_status_until" = "{from} set channel emoji status to {emoji} until {date}";
+"lng_admin_log_change_status_until" = "{from} changed channel emoji status from {previous} to {emoji} until {date}";
"lng_admin_log_user_with_username" = "{name} ({mention})";
"lng_admin_log_messages_ttl_set" = "{from} enabled messages auto-delete after {duration}";
"lng_admin_log_messages_ttl_changed" = "{from} changed messages auto-delete period from {previous} to {duration}";
@@ -4207,6 +4279,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_request_peer_confirm_rights" = "This will also add {bot} to {chat} with the following rights: {rights}.";
"lng_request_peer_confirm_send" = "Send";
"lng_request_user_title" = "Choose User";
+"lng_request_users_title" = "Choose Users";
"lng_request_user_premium_yes" = "The user should have a Premium subscription.";
"lng_request_user_premium_no" = "The user shouldn't have a Premium subscription.";
"lng_request_user_no" = "No such users";
diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc
index ce814218c..d94d26b65 100644
--- a/Telegram/Resources/qrc/telegram/telegram.qrc
+++ b/Telegram/Resources/qrc/telegram/telegram.qrc
@@ -15,6 +15,7 @@
../../art/slot_2_idle.tgs
../../art/slot_back.tgs
../../art/slot_pull.tgs
+ ../../art/winners.tgs
../../day-blue.tdesktop-theme
../../night.tdesktop-theme
../../night-green.tdesktop-theme
diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index f983e8585..b0314d210 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
+ Version="4.13.1.0" />
Telegram Desktop
Telegram Messenger LLP
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index 6df1b56ca..02853f16f 100644
--- a/Telegram/Resources/winrc/Telegram.rc
+++ b/Telegram/Resources/winrc/Telegram.rc
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,12,2,0
- PRODUCTVERSION 4,12,2,0
+ FILEVERSION 4,13,1,0
+ PRODUCTVERSION 4,13,1,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
- VALUE "FileVersion", "4.12.2.0"
+ VALUE "FileVersion", "4.13.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "4.12.2.0"
+ VALUE "ProductVersion", "4.13.1.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index 8db1805f8..4859e694e 100644
--- a/Telegram/Resources/winrc/Updater.rc
+++ b/Telegram/Resources/winrc/Updater.rc
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,12,2,0
- PRODUCTVERSION 4,12,2,0
+ FILEVERSION 4,13,1,0
+ PRODUCTVERSION 4,13,1,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater"
- VALUE "FileVersion", "4.12.2.0"
+ VALUE "FileVersion", "4.13.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "4.12.2.0"
+ VALUE "ProductVersion", "4.13.1.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/_other/updater_win.cpp b/Telegram/SourceFiles/_other/updater_win.cpp
index 255ecbd75..4d028d6e5 100644
--- a/Telegram/SourceFiles/_other/updater_win.cpp
+++ b/Telegram/SourceFiles/_other/updater_win.cpp
@@ -537,11 +537,12 @@ HANDLE _generateDumpFileAtPath(const WCHAR *path) {
GetLocalTime(&stLocalTime);
- wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
- szPath, szExeName, updaterVersionStr,
- stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
- stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
- GetCurrentProcessId(), GetCurrentThreadId());
+ wsprintf(
+ szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
+ szPath, szExeName, updaterVersionStr,
+ stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
+ stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
+ GetCurrentProcessId(), GetCurrentThreadId());
return CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
}
@@ -562,7 +563,7 @@ void _generateDump(EXCEPTION_POINTERS* pExceptionPointers) {
DWORD len = GetModuleFileName(GetModuleHandle(0), szPath, maxFileLen);
if (!len) return;
- WCHAR *pathEnd = szPath + len;
+ WCHAR *pathEnd = szPath + len;
if (!_wcsicmp(pathEnd - wcslen(_exeName), _exeName)) {
wsprintf(pathEnd - wcslen(_exeName), L"");
diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp
index 239d2d0ab..d49f1bbf7 100644
--- a/Telegram/SourceFiles/api/api_bot.cpp
+++ b/Telegram/SourceFiles/api/api_bot.cpp
@@ -415,12 +415,16 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
const auto peer = item->history()->peer;
const auto itemId = item->id;
const auto id = int32(button->buttonId);
- const auto chosen = [=](not_null result) {
+ const auto chosen = [=](std::vector> result) {
peer->session().api().request(MTPmessages_SendBotRequestedPeer(
peer->input,
MTP_int(itemId),
MTP_int(id),
- result->input
+ MTP_vector_from_range(
+ result
+ | ranges::views::transform([](
+ not_null peer) {
+ return MTPInputPeer(peer->input); }))
)).done([=](const MTPUpdates &result) {
peer->session().api().applyUpdates(result);
}).send();
diff --git a/Telegram/SourceFiles/api/api_peer_colors.cpp b/Telegram/SourceFiles/api/api_peer_colors.cpp
index 85d60d814..50c2382fd 100644
--- a/Telegram/SourceFiles/api/api_peer_colors.cpp
+++ b/Telegram/SourceFiles/api/api_peer_colors.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_peer_colors.h"
#include "apiwrap.h"
+#include "data/data_peer.h"
#include "ui/chat/chat_style.h"
namespace Api {
@@ -62,6 +63,16 @@ auto PeerColors::indicesValue() const
}));
}
+int PeerColors::requiredLevelFor(PeerId channel, uint8 index) const {
+ if (Data::DecideColorIndex(channel) == index) {
+ return 0;
+ } else if (const auto i = _requiredLevels.find(index)
+ ; i != end(_requiredLevels)) {
+ return i->second;
+ }
+ return 1;
+}
+
void PeerColors::apply(const MTPDhelp_peerColors &data) {
auto suggested = std::vector();
auto colors = std::make_shared<
@@ -89,6 +100,7 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) {
};
const auto &list = data.vcolors().v;
+ _requiredLevels.clear();
suggested.reserve(list.size());
for (const auto &color : list) {
const auto &data = color.data();
@@ -98,6 +110,9 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) {
continue;
}
const auto colorIndex = uint8(colorIndexBare);
+ if (const auto min = data.vchannel_min_level()) {
+ _requiredLevels[colorIndex] = min->v;
+ }
if (!data.is_hidden()) {
suggested.push_back(colorIndex);
}
diff --git a/Telegram/SourceFiles/api/api_peer_colors.h b/Telegram/SourceFiles/api/api_peer_colors.h
index de06d4221..0ad1a63c7 100644
--- a/Telegram/SourceFiles/api/api_peer_colors.h
+++ b/Telegram/SourceFiles/api/api_peer_colors.h
@@ -28,6 +28,10 @@ public:
[[nodiscard]] auto indicesValue() const
-> rpl::producer;
+ [[nodiscard]] int requiredLevelFor(
+ PeerId channel,
+ uint8 index) const;
+
private:
void request();
void apply(const MTPDhelp_peerColors &data);
@@ -38,6 +42,7 @@ private:
mtpRequestId _requestId = 0;
base::Timer _timer;
rpl::variable> _suggested;
+ base::flat_map _requiredLevels;
rpl::event_stream<> _colorIndicesChanged;
std::unique_ptr _colorIndicesCurrent;
diff --git a/Telegram/SourceFiles/api/api_peer_photo.cpp b/Telegram/SourceFiles/api/api_peer_photo.cpp
index 25a706587..c70ef4491 100644
--- a/Telegram/SourceFiles/api/api_peer_photo.cpp
+++ b/Telegram/SourceFiles/api/api_peer_photo.cpp
@@ -515,6 +515,7 @@ auto PeerPhoto::emojiList(EmojiListType type) -> EmojiListData & {
case EmojiListType::Profile: return _profileEmojiList;
case EmojiListType::Group: return _groupEmojiList;
case EmojiListType::Background: return _backgroundEmojiList;
+ case EmojiListType::NoChannelStatus: return _noChannelStatusEmojiList;
}
Unexpected("Type in PeerPhoto::emojiList.");
}
@@ -551,6 +552,8 @@ void PeerPhoto::requestEmojiList(EmojiListType type) {
? send(MTPaccount_GetDefaultProfilePhotoEmojis())
: (type == EmojiListType::Group)
? send(MTPaccount_GetDefaultGroupPhotoEmojis())
+ : (type == EmojiListType::NoChannelStatus)
+ ? send(MTPaccount_GetChannelRestrictedStatusEmojis())
: send(MTPaccount_GetDefaultBackgroundEmojis());
}
diff --git a/Telegram/SourceFiles/api/api_peer_photo.h b/Telegram/SourceFiles/api/api_peer_photo.h
index 1d4bd1071..71d340a03 100644
--- a/Telegram/SourceFiles/api/api_peer_photo.h
+++ b/Telegram/SourceFiles/api/api_peer_photo.h
@@ -32,6 +32,7 @@ public:
Profile,
Group,
Background,
+ NoChannelStatus,
};
struct UserPhoto {
@@ -112,6 +113,7 @@ private:
EmojiListData _profileEmojiList;
EmojiListData _groupEmojiList;
EmojiListData _backgroundEmojiList;
+ EmojiListData _noChannelStatusEmojiList;
};
diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp
index 58950f6f4..088c3cf87 100644
--- a/Telegram/SourceFiles/api/api_premium.cpp
+++ b/Telegram/SourceFiles/api/api_premium.cpp
@@ -26,7 +26,7 @@ namespace {
[[nodiscard]] GiftCode Parse(const MTPDpayments_checkedGiftCode &data) {
return {
- .from = peerFromMTP(data.vfrom_id()),
+ .from = data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(),
.to = data.vto_id() ? peerFromUser(*data.vto_id()) : PeerId(),
.giveawayId = data.vgiveaway_msg_id().value_or_empty(),
.date = data.vdate().v,
@@ -342,15 +342,12 @@ PremiumGiftCodeOptions::PremiumGiftCodeOptions(not_null peer)
rpl::producer PremiumGiftCodeOptions::request() {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
- const auto channel = _peer->asChannel();
- if (!channel) {
- return lifetime;
- }
using TLOption = MTPPremiumGiftCodeOption;
_api.request(MTPpayments_GetPremiumGiftCodeOptions(
- MTP_flags(
- MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer),
+ MTP_flags(_peer->isChannel()
+ ? MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer
+ : MTPpayments_GetPremiumGiftCodeOptions::Flag(0)),
_peer->input
)).done([=](const MTPVector &result) {
auto tlMapOptions = base::flat_map>();
@@ -420,6 +417,8 @@ const std::vector &PremiumGiftCodeOptions::availablePresets() const {
}
[[nodiscard]] int PremiumGiftCodeOptions::monthsFromPreset(int monthsIndex) {
+ Expects(monthsIndex >= 0 && monthsIndex < _availablePresets.size());
+
return _optionsForOnePerson.months[monthsIndex];
}
diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp
index 6655c618e..f24e156d0 100644
--- a/Telegram/SourceFiles/api/api_statistics.cpp
+++ b/Telegram/SourceFiles/api/api_statistics.cpp
@@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "data/data_channel.h"
#include "data/data_session.h"
+#include "data/data_stories.h"
+#include "data/data_story.h"
#include "history/history.h"
#include "main/main_session.h"
#include "statistics/statistics_data_deserialize.h"
@@ -359,161 +361,54 @@ PublicForwards::PublicForwards(
void PublicForwards::request(
const Data::PublicForwardsSlice::OffsetToken &token,
Fn done) {
- if (!_requestId) {
- if (_fullId.messageId) {
- requestMessage(token, std::move(done));
- } else if (_fullId.storyId) {
- requestStory(token, std::move(done));
- }
+ if (_requestId) {
+ return;
}
-}
-
-void PublicForwards::requestMessage(
- const Data::PublicForwardsSlice::OffsetToken &token,
- Fn done) {
- Expects(_fullId.messageId);
-
- const auto offsetPeer = channel()->owner().peer(token.fullId.peer);
- const auto tlOffsetPeer = offsetPeer
- ? offsetPeer->input
- : MTP_inputPeerEmpty();
- constexpr auto kLimit = tl::make_int(100);
- _requestId = makeRequest(MTPstats_GetMessagePublicForwards(
- channel()->inputChannel,
- MTP_int(_fullId.messageId.msg),
- MTP_int(token.rate),
- tlOffsetPeer,
- MTP_int(token.fullId.msg),
- kLimit
- )).done([=, channel = channel()](const MTPmessages_Messages &result) {
+ const auto channel = StatisticsRequestSender::channel();
+ const auto processResult = [=](const MTPstats_PublicForwards &tl) {
using Messages = QVector;
_requestId = 0;
- auto nextToken = Data::PublicForwardsSlice::OffsetToken();
- const auto process = [&](const MTPVector &messages) {
- auto result = Messages();
- for (const auto &message : messages.v) {
- const auto msgId = IdFromMessage(message);
- const auto peerId = PeerFromMessage(message);
- const auto lastDate = DateFromMessage(message);
- if (const auto peer = channel->owner().peerLoaded(peerId)) {
- if (lastDate) {
- channel->owner().addNewMessage(
- message,
- MessageFlags(),
- NewMessageType::Existing);
- nextToken.fullId = { peerId, msgId };
- result.push_back({ .messageId = nextToken.fullId });
- }
- }
- }
- return result;
- };
+ const auto &data = tl.data();
+ auto &owner = channel->owner();
- auto allLoaded = false;
- auto fullCount = 0;
- auto messages = result.match([&](const MTPDmessages_messages &data) {
- channel->owner().processUsers(data.vusers());
- channel->owner().processChats(data.vchats());
- auto list = process(data.vmessages());
- allLoaded = true;
- fullCount = list.size();
- return list;
- }, [&](const MTPDmessages_messagesSlice &data) {
- channel->owner().processUsers(data.vusers());
- channel->owner().processChats(data.vchats());
- auto list = process(data.vmessages());
+ owner.processUsers(data.vusers());
+ owner.processChats(data.vchats());
- if (const auto nextRate = data.vnext_rate()) {
- const auto rateUpdated = (nextRate->v != token.rate);
- if (rateUpdated) {
- nextToken.rate = nextRate->v;
- } else {
- allLoaded = true;
- }
- }
- fullCount = data.vcount().v;
- return list;
- }, [&](const MTPDmessages_channelMessages &data) {
- channel->owner().processUsers(data.vusers());
- channel->owner().processChats(data.vchats());
- auto list = process(data.vmessages());
- allLoaded = true;
- fullCount = data.vcount().v;
- return list;
- }, [&](const MTPDmessages_messagesNotModified &) {
- allLoaded = true;
- return Messages();
- });
+ const auto nextToken = data.vnext_offset()
+ ? qs(*data.vnext_offset())
+ : Data::PublicForwardsSlice::OffsetToken();
- _lastTotal = std::max(_lastTotal, fullCount);
- done({
- .list = std::move(messages),
- .total = _lastTotal,
- .allLoaded = allLoaded,
- .token = nextToken,
- });
- }).fail([=] {
- _requestId = 0;
- }).send();
-}
-
-void PublicForwards::requestStory(
- const Data::PublicForwardsSlice::OffsetToken &token,
- Fn done) {
- Expects(_fullId.storyId);
-
- constexpr auto kLimit = tl::make_int(100);
- _requestId = makeRequest(MTPstats_GetStoryPublicForwards(
- channel()->input,
- MTP_int(_fullId.storyId.story),
- MTP_string(token.storyOffset),
- kLimit
- )).done([=, channel = channel()](
- const MTPstats_PublicForwards &tlForwards) {
- using Messages = QVector;
- _requestId = 0;
-
- const auto &data = tlForwards.data();
-
- channel->owner().processUsers(data.vusers());
- channel->owner().processChats(data.vchats());
-
- const auto nextToken = Data::PublicForwardsSlice::OffsetToken({
- .storyOffset = data.vnext_offset().value_or_empty(),
- });
-
- const auto allLoaded = nextToken.storyOffset.isEmpty()
- || (nextToken.storyOffset == token.storyOffset);
const auto fullCount = data.vcount().v;
- auto recentList = Messages();
+ auto recentList = Messages(data.vforwards().v.size());
for (const auto &tlForward : data.vforwards().v) {
tlForward.match([&](const MTPDpublicForwardMessage &data) {
const auto &message = data.vmessage();
const auto msgId = IdFromMessage(message);
const auto peerId = PeerFromMessage(message);
const auto lastDate = DateFromMessage(message);
- if (const auto peer = channel->owner().peerLoaded(peerId)) {
+ if (const auto peer = owner.peerLoaded(peerId)) {
if (!lastDate) {
return;
}
- channel->owner().addNewMessage(
+ owner.addNewMessage(
message,
MessageFlags(),
NewMessageType::Existing);
recentList.push_back({ .messageId = { peerId, msgId } });
}
}, [&](const MTPDpublicForwardStory &data) {
- data.vstory().match([&](const MTPDstoryItem &d) {
- recentList.push_back({
- .storyId = { peerFromMTP(data.vpeer()), d.vid().v }
- });
- }, [](const auto &) {
- });
+ const auto story = owner.stories().applySingle(
+ peerFromMTP(data.vpeer()),
+ data.vstory());
+ if (story) {
+ recentList.push_back({ .storyId = story->fullId() });
+ }
});
}
+ const auto allLoaded = nextToken.isEmpty() || (nextToken == token);
_lastTotal = std::max(_lastTotal, fullCount);
done({
.list = std::move(recentList),
@@ -521,9 +416,24 @@ void PublicForwards::requestStory(
.allLoaded = allLoaded,
.token = nextToken,
});
- }).fail([=] {
- _requestId = 0;
- }).send();
+ };
+
+ constexpr auto kLimit = tl::make_int(100);
+ if (_fullId.messageId) {
+ _requestId = makeRequest(MTPstats_GetMessagePublicForwards(
+ channel->inputChannel,
+ MTP_int(_fullId.messageId.msg),
+ MTP_string(token),
+ kLimit
+ )).done(processResult).fail([=] { _requestId = 0; }).send();
+ } else if (_fullId.storyId) {
+ _requestId = makeRequest(MTPstats_GetStoryPublicForwards(
+ channel->input,
+ MTP_int(_fullId.storyId.story),
+ MTP_string(token),
+ kLimit
+ )).done(processResult).fail([=] { _requestId = 0; }).send();
+ }
}
MessageStatistics::MessageStatistics(
@@ -702,6 +612,7 @@ rpl::producer Boosts::request() {
_peer->input
)).done([=](const MTPpremium_BoostsStatus &result) {
const auto &data = result.data();
+ channel->updateLevelHint(data.vlevel().v);
const auto hasPremium = !!data.vpremium_audience();
const auto premiumMemberCount = hasPremium
? std::max(0, int(data.vpremium_audience()->data().vpart().v))
diff --git a/Telegram/SourceFiles/api/api_statistics.h b/Telegram/SourceFiles/api/api_statistics.h
index d1b5b6624..f5360adb2 100644
--- a/Telegram/SourceFiles/api/api_statistics.h
+++ b/Telegram/SourceFiles/api/api_statistics.h
@@ -77,13 +77,6 @@ public:
Fn done);
private:
- void requestMessage(
- const Data::PublicForwardsSlice::OffsetToken &token,
- Fn done);
- void requestStory(
- const Data::PublicForwardsSlice::OffsetToken &token,
- Fn done);
-
const Data::RecentPostId _fullId;
mtpRequestId _requestId = 0;
int _lastTotal = 0;
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 5c92b2566..e8c8046cc 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -225,11 +225,11 @@ void ApiWrap::setupSupportMode() {
void ApiWrap::requestChangelog(
const QString &sinceVersion,
Fn callback) {
- request(MTPhelp_GetAppChangelog(
- MTP_string(sinceVersion)
- )).done(
- callback
- ).send();
+ //request(MTPhelp_GetAppChangelog(
+ // MTP_string(sinceVersion)
+ //)).done(
+ // callback
+ //).send();
}
void ApiWrap::refreshTopPromotion() {
diff --git a/Telegram/SourceFiles/boxes/background_box.cpp b/Telegram/SourceFiles/boxes/background_box.cpp
index 0ae3023e0..877cf556e 100644
--- a/Telegram/SourceFiles/boxes/background_box.cpp
+++ b/Telegram/SourceFiles/boxes/background_box.cpp
@@ -129,6 +129,8 @@ private:
int row) const;
void validatePaperThumbnail(const Paper &paper) const;
+ [[nodiscard]] bool forChannel() const;
+
const not_null _session;
PeerData * const _forPeer = nullptr;
@@ -176,6 +178,23 @@ void BackgroundBox::prepare() {
st::infoIconMediaPhoto,
st::infoSharedMediaButtonIconPosition);
+ if (forChannel() && _forPeer->wallPaper()) {
+ const auto remove = container->add(object_ptr(
+ container,
+ tr::lng_settings_bg_remove(),
+ st::infoBlockButton));
+ object_ptr(
+ remove,
+ st::infoIconDeleteRed,
+ st::infoSharedMediaButtonIconPosition);
+
+ remove->setClickedCallback([=] {
+ if (const auto resolved = _inner->resolveResetCustomPaper()) {
+ chosen(*resolved);
+ }
+ });
+ }
+
button->setClickedCallback([=] {
chooseFromFile();
});
@@ -290,6 +309,23 @@ void BackgroundBox::chosen(const Data::WallPaper &paper) {
closeBox();
}
return;
+ } else if (forChannel()) {
+ if (_forPeer->wallPaper() && _forPeer->wallPaper()->equals(paper)) {
+ closeBox();
+ return;
+ }
+ const auto &themes = _forPeer->owner().cloudThemes();
+ for (const auto &theme : themes.chatThemes()) {
+ for (const auto &[type, themed] : theme.settings) {
+ if (themed.paper && themed.paper->equals(paper)) {
+ _controller->show(Box(
+ _controller,
+ Data::WallPaper::FromEmojiId(theme.emoticon),
+ BackgroundPreviewArgs{ _forPeer }));
+ return;
+ }
+ }
+ }
}
_controller->show(Box(
_controller,
@@ -316,6 +352,10 @@ void BackgroundBox::resetForPeer() {
}
}
+bool BackgroundBox::forChannel() const {
+ return _forPeer && _forPeer->isChannel();
+}
+
void BackgroundBox::removePaper(const Data::WallPaper &paper) {
const auto session = &_controller->session();
const auto remove = [=, weak = Ui::MakeWeak(this)](Fn &&close) {
@@ -345,9 +385,16 @@ BackgroundBox::Inner::Inner(
, _session(session)
, _forPeer(forPeer)
, _api(&_session->mtp())
-, _check(std::make_unique(st::overviewCheck, [=] { update(); })) {
+, _check(
+ std::make_unique(
+ st::overviewCheck,
+ [=] { update(); })) {
_check->setChecked(true, anim::type::instant);
- resize(st::boxWideWidth, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding);
+ resize(
+ st::boxWideWidth,
+ (2 * (st::backgroundSize.height() + st::backgroundPadding)
+ + st::backgroundPadding));
+
Window::Theme::IsNightModeValue(
) | rpl::start_with_next([=] {
updatePapers();
@@ -364,21 +411,31 @@ BackgroundBox::Inner::Inner(
_check->invalidateCache();
}, lifetime());
- using Update = Window::Theme::BackgroundUpdate;
- Window::Theme::Background()->updates(
- ) | rpl::start_with_next([=](const Update &update) {
- if (update.type == Update::Type::New) {
- sortPapers();
- requestPapers();
- this->update();
- }
- }, lifetime());
-
+ if (forChannel()) {
+ _session->data().cloudThemes().chatThemesUpdated(
+ ) | rpl::start_with_next([=] {
+ updatePapers();
+ }, lifetime());
+ } else {
+ using Update = Window::Theme::BackgroundUpdate;
+ Window::Theme::Background()->updates(
+ ) | rpl::start_with_next([=](const Update &update) {
+ if (update.type == Update::Type::New) {
+ sortPapers();
+ requestPapers();
+ this->update();
+ }
+ }, lifetime());
+ }
setMouseTracking(true);
}
void BackgroundBox::Inner::requestPapers() {
+ if (forChannel()) {
+ _session->data().cloudThemes().refreshChatThemes();
+ return;
+ }
_api.request(MTPaccount_GetWallPapers(
MTP_long(_session->data().wallpapersHash())
)).done([=](const MTPaccount_WallPapers &result) {
@@ -395,7 +452,7 @@ auto BackgroundBox::Inner::resolveResetCustomPaper() const
}
const auto nonCustom = Window::Theme::Background()->paper();
const auto themeEmoji = _forPeer->themeEmoji();
- if (themeEmoji.isEmpty()) {
+ if (forChannel() || themeEmoji.isEmpty()) {
return nonCustom;
}
const auto &themes = _forPeer->owner().cloudThemes();
@@ -443,6 +500,8 @@ void BackgroundBox::Inner::pushCustomPapers() {
}
void BackgroundBox::Inner::sortPapers() {
+ Expects(!forChannel());
+
const auto currentCustom = _forPeer ? _forPeer->wallPaper() : nullptr;
_currentId = currentCustom
? currentCustom->id()
@@ -472,23 +531,60 @@ void BackgroundBox::Inner::sortPapers() {
}
void BackgroundBox::Inner::updatePapers() {
- if (_session->data().wallpapers().empty()) {
- return;
+ if (forChannel()) {
+ if (_session->data().cloudThemes().chatThemes().empty()) {
+ return;
+ }
+ } else {
+ if (_session->data().wallpapers().empty()) {
+ return;
+ }
}
_over = _overDown = Selection();
- _papers = _session->data().wallpapers(
- ) | ranges::views::filter([&](const Data::WallPaper &paper) {
- return (!paper.isPattern() || !paper.backgroundColors().empty())
- && (!_forPeer
- || (!Data::IsDefaultWallPaper(paper)
- && (Data::IsCloudWallPaper(paper)
- || Data::IsCustomWallPaper(paper))));
- }) | ranges::views::transform([](const Data::WallPaper &paper) {
- return Paper{ paper };
- }) | ranges::to_vector;
- pushCustomPapers();
- sortPapers();
+ const auto was = base::take(_papers);
+ if (forChannel()) {
+ const auto now = _forPeer->wallPaper();
+ const auto &list = _session->data().cloudThemes().chatThemes();
+ if (list.empty()) {
+ return;
+ }
+ using Type = Data::CloudThemeType;
+ const auto type = Window::Theme::IsNightMode()
+ ? Type::Dark
+ : Type::Light;
+ _papers.reserve(list.size() + 1);
+ const auto nowEmojiId = now ? now->emojiId() : QString();
+ if (!now || !now->emojiId().isEmpty()) {
+ _papers.push_back({ Window::Theme::Background()->paper() });
+ _currentId = _papers.back().data.id();
+ } else {
+ _papers.push_back({ *now });
+ _currentId = now->id();
+ }
+ for (const auto &theme : list) {
+ const auto i = theme.settings.find(type);
+ if (i != end(theme.settings) && i->second.paper) {
+ _papers.push_back({ *i->second.paper });
+ if (nowEmojiId == theme.emoticon) {
+ _currentId = _papers.back().data.id();
+ }
+ }
+ }
+ } else {
+ _papers = _session->data().wallpapers(
+ ) | ranges::views::filter([&](const Data::WallPaper &paper) {
+ return (!paper.isPattern() || !paper.backgroundColors().empty())
+ && (!_forPeer
+ || (!Data::IsDefaultWallPaper(paper)
+ && (Data::IsCloudWallPaper(paper)
+ || Data::IsCustomWallPaper(paper))));
+ }) | ranges::views::transform([](const Data::WallPaper &paper) {
+ return Paper{ paper };
+ }) | ranges::to_vector;
+ pushCustomPapers();
+ sortPapers();
+ }
resizeToContentAndPreload();
}
@@ -587,6 +683,10 @@ void BackgroundBox::Inner::validatePaperThumbnail(
paper.thumbnail.setDevicePixelRatio(cRetinaFactor());
}
+bool BackgroundBox::Inner::forChannel() const {
+ return _forPeer && _forPeer->isChannel();
+}
+
void BackgroundBox::Inner::paintPaper(
QPainter &p,
const Paper &paper,
@@ -604,7 +704,8 @@ void BackgroundBox::Inner::paintPaper(
const auto checkLeft = x + st::backgroundSize.width() - st::overviewCheckSkip - st::overviewCheck.size;
const auto checkTop = y + st::backgroundSize.height() - st::overviewCheckSkip - st::overviewCheck.size;
_check->paint(p, checkLeft, checkTop, width());
- } else if (Data::IsCloudWallPaper(paper.data)
+ } else if (!forChannel()
+ && Data::IsCloudWallPaper(paper.data)
&& !Data::IsDefaultWallPaper(paper.data)
&& !Data::IsLegacy2DefaultWallPaper(paper.data)
&& !Data::IsLegacy3DefaultWallPaper(paper.data)
@@ -642,7 +743,8 @@ void BackgroundBox::Inner::mouseMoveEvent(QMouseEvent *e) {
- st::stickerPanDeleteIconBg.width();
const auto deleteBottom = row * (height + skip) + skip
+ st::stickerPanDeleteIconBg.height();
- const auto inDelete = (x >= deleteLeft)
+ const auto inDelete = !forChannel()
+ && (x >= deleteLeft)
&& (y < deleteBottom)
&& Data::IsCloudWallPaper(data)
&& !Data::IsDefaultWallPaper(data)
diff --git a/Telegram/SourceFiles/boxes/background_box.h b/Telegram/SourceFiles/boxes/background_box.h
index 3fe16d602..8aa63ff91 100644
--- a/Telegram/SourceFiles/boxes/background_box.h
+++ b/Telegram/SourceFiles/boxes/background_box.h
@@ -38,6 +38,7 @@ private:
const Data::WallPaper &paper) const;
void removePaper(const Data::WallPaper &paper);
void resetForPeer();
+ [[nodiscard]] bool forChannel() const;
void chooseFromFile();
diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp
index c54f581ce..4f46697c1 100644
--- a/Telegram/SourceFiles/boxes/background_preview_box.cpp
+++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp
@@ -8,11 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/background_preview_box.h"
#include "base/unixtime.h"
+#include "boxes/peers/edit_peer_color_box.h"
#include "boxes/premium_preview_box.h"
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "window/themes/window_theme.h"
#include "ui/boxes/confirm_box.h"
+#include "ui/boxes/boost_box.h"
#include "ui/controls/chat_service_checkbox.h"
#include "ui/chat/chat_theme.h"
#include "ui/chat/chat_style.h"
@@ -29,6 +31,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h"
#include "history/history_item_helpers.h"
#include "history/view/history_view_message.h"
+#include "main/main_account.h"
+#include "main/main_app_config.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "data/data_session.h"
@@ -159,6 +163,25 @@ constexpr auto kDefaultDimming = 50;
return result;
}
+[[nodiscard]] Data::WallPaper Resolve(
+ not_null session,
+ const Data::WallPaper &paper,
+ bool dark) {
+ if (paper.emojiId().isEmpty()) {
+ return paper;
+ }
+ const auto &themes = session->data().cloudThemes();
+ if (const auto theme = themes.themeForEmoji(paper.emojiId())) {
+ using Type = Data::CloudThemeType;
+ const auto type = dark ? Type::Dark : Type::Light;
+ const auto i = theme->settings.find(type);
+ if (i != end(theme->settings) && i->second.paper) {
+ return *i->second.paper;
+ }
+ }
+ return paper;
+}
+
} // namespace
struct BackgroundPreviewBox::OverridenStyle {
@@ -196,15 +219,17 @@ BackgroundPreviewBox::BackgroundPreviewBox(
? tr::lng_background_apply2(tr::now)
: tr::lng_background_text2(tr::now)),
true))
-, _paper(paper)
+, _paperEmojiId(paper.emojiId())
+, _paper(
+ Resolve(&controller->session(), paper, Window::Theme::IsNightMode()))
, _media(_paper.document() ? _paper.document()->createMediaView() : nullptr)
, _radial([=](crl::time now) { radialAnimationCallback(now); })
, _appNightMode(Window::Theme::IsNightModeValue())
, _boxDarkMode(_appNightMode.current())
-, _dimmingIntensity(std::clamp(paper.patternIntensity(), 0, 100))
+, _dimmingIntensity(std::clamp(_paper.patternIntensity(), 0, 100))
, _dimmed(_forPeer
- && (paper.document() || paper.localThumbnail())
- && !paper.isPattern()) {
+ && (_paper.document() || _paper.localThumbnail())
+ && !_paper.isPattern()) {
if (_media) {
_media->thumbnailWanted(_paper.fileOrigin());
}
@@ -244,7 +269,36 @@ BackgroundPreviewBox::BackgroundPreviewBox(
BackgroundPreviewBox::~BackgroundPreviewBox() = default;
+void BackgroundPreviewBox::recreate(bool dark) {
+ _paper = Resolve(
+ &_controller->session(),
+ Data::WallPaper::FromEmojiId(_paperEmojiId),
+ dark);
+ _media = _paper.document()
+ ? _paper.document()->createMediaView()
+ : nullptr;
+ if (_media) {
+ _media->thumbnailWanted(_paper.fileOrigin());
+ }
+ _full = QImage();
+ _generated = _scaled = _blurred = _fadeOutThumbnail = QPixmap();
+ _generating = {};
+ generateBackground();
+ _paper.loadDocument();
+ if (const auto document = _paper.document()) {
+ if (document->loading()) {
+ _radial.start(_media->progress());
+ }
+ }
+ checkLoadedDocument();
+ updateServiceBg(_paper.backgroundColors());
+ update();
+}
+
void BackgroundPreviewBox::applyDarkMode(bool dark) {
+ if (!_paperEmojiId.isEmpty()) {
+ recreate(dark);
+ }
const auto equals = (dark == Window::Theme::IsNightMode());
const auto &palette = (dark ? _darkPalette : _lightPalette);
if (!equals && !palette) {
@@ -410,6 +464,10 @@ auto BackgroundPreviewBox::prepareOverridenStyle(bool dark)
return result;
}
+bool BackgroundPreviewBox::forChannel() const {
+ return _forPeer && _forPeer->isChannel();
+}
+
void BackgroundPreviewBox::generateBackground() {
if (_paper.backgroundColors().empty()) {
return;
@@ -435,7 +493,9 @@ void BackgroundPreviewBox::resetTitle() {
void BackgroundPreviewBox::rebuildButtons(bool dark) {
clearButtons();
- addButton(_forPeer
+ addButton(forChannel()
+ ? tr::lng_background_apply_channel()
+ : _forPeer
? tr::lng_background_apply_button()
: tr::lng_settings_apply(), [=] { apply(); });
addButton(tr::lng_cancel(), [=] { closeBox(); });
@@ -624,6 +684,36 @@ void BackgroundPreviewBox::setExistingForPeer(
_controller->finishChatThemeEdit(_forPeer);
}
+void BackgroundPreviewBox::checkLevelForChannel() {
+ Expects(forChannel());
+
+ const auto show = _controller->uiShow();
+ _forPeerLevelCheck = true;
+ const auto weak = Ui::MakeWeak(this);
+ CheckBoostLevel(show, _forPeer, [=](int level) {
+ if (!weak) {
+ return std::optional();
+ }
+ const auto appConfig = &_forPeer->session().account().appConfig();
+ const auto defaultRequired = appConfig->get(
+ "channel_wallpaper_level_min",
+ 9);
+ const auto customRequired = appConfig->get(
+ "channel_custom_wallpaper_level_min",
+ 10);
+ const auto required = _paperEmojiId.isEmpty()
+ ? customRequired
+ : defaultRequired;
+ if (level >= required) {
+ applyForPeer(false);
+ return std::optional();
+ }
+ return std::make_optional(Ui::AskBoostReason{
+ Ui::AskBoostWallpaper{ required }
+ });
+ }, [=] { _forPeerLevelCheck = false; });
+}
+
void BackgroundPreviewBox::applyForPeer() {
Expects(_forPeer != nullptr);
@@ -636,105 +726,110 @@ void BackgroundPreviewBox::applyForPeer() {
}
}
- if (!_fromMessageId && _forPeer->session().premiumPossible()) {
- if (_forBothOverlay) {
- return;
- }
- const auto size = this->size() * style::DevicePixelRatio();
- const auto bg = Images::DitherImage(
- Images::BlurLargeImage(
- Ui::GrabWidgetToImage(this).scaled(
- size / style::ConvertScale(4),
- Qt::IgnoreAspectRatio,
- Qt::SmoothTransformation),
- 24).scaled(
- size,
- Qt::IgnoreAspectRatio,
- Qt::SmoothTransformation));
-
- _forBothOverlay = std::make_unique>(
- this,
- object_ptr(this));
- const auto overlay = _forBothOverlay->entity();
-
- sizeValue() | rpl::start_with_next([=](QSize size) {
- _forBothOverlay->setGeometry({ QPoint(), size });
- overlay->setGeometry({ QPoint(), size });
- }, _forBothOverlay->lifetime());
-
- overlay->paintRequest(
- ) | rpl::start_with_next([=](QRect clip) {
- auto p = QPainter(overlay);
- p.drawImage(0, 0, bg);
- p.fillRect(clip, QColor(0, 0, 0, 64));
- }, overlay->lifetime());
-
- using namespace Ui;
- const auto forMe = CreateChild(
- overlay,
- tr::lng_background_apply_me(),
- st::backgroundConfirm);
- forMe->setClickedCallback([=] {
- applyForPeer(false);
- });
- using namespace rpl::mappers;
- const auto forBoth = ::Settings::CreateLockedButton(
- overlay,
- tr::lng_background_apply_both(
- lt_user,
- rpl::single(_forPeer->shortName())),
- st::backgroundConfirm,
- Data::AmPremiumValue(&_forPeer->session()) | rpl::map(!_1));
- forBoth->setClickedCallback([=] {
- if (_forPeer->session().premium()) {
- applyForPeer(true);
- } else {
- ShowPremiumPreviewBox(
- _controller->uiShow(),
- PremiumPreview::Wallpapers);
- }
- });
- const auto cancel = CreateChild(
- overlay,
- tr::lng_cancel(),
- st::backgroundConfirmCancel);
- cancel->setClickedCallback([=] {
- const auto raw = _forBothOverlay.release();
- raw->shownValue() | rpl::filter(
- !rpl::mappers::_1
- ) | rpl::take(1) | rpl::start_with_next(crl::guard(raw, [=] {
- delete raw;
- }), raw->lifetime());
- raw->toggle(false, anim::type::normal);
- });
- forMe->setTextTransform(RoundButton::TextTransform::NoTransform);
- forBoth->setTextTransform(RoundButton::TextTransform::NoTransform);
- cancel->setTextTransform(RoundButton::TextTransform::NoTransform);
-
- overlay->sizeValue(
- ) | rpl::start_with_next([=](QSize size) {
- const auto padding = st::backgroundConfirmPadding;
- const auto width = size.width()
- - padding.left()
- - padding.right();
- const auto height = cancel->height();
- auto top = size.height() - padding.bottom() - height;
- cancel->setGeometry(padding.left(), top, width, height);
- top -= height + padding.top();
- forBoth->setGeometry(padding.left(), top, width, height);
- top -= height + padding.top();
- forMe->setGeometry(padding.left(), top, width, height);
- }, _forBothOverlay->lifetime());
-
- _forBothOverlay->hide(anim::type::instant);
- _forBothOverlay->show(anim::type::normal);
- } else {
+ if (forChannel()) {
+ checkLevelForChannel();
+ return;
+ } else if (_fromMessageId || !_forPeer->session().premiumPossible()) {
applyForPeer(false);
+ return;
+ } else if (_forBothOverlay) {
+ return;
}
+ const auto size = this->size() * style::DevicePixelRatio();
+ const auto bg = Images::DitherImage(
+ Images::BlurLargeImage(
+ Ui::GrabWidgetToImage(this).scaled(
+ size / style::ConvertScale(4),
+ Qt::IgnoreAspectRatio,
+ Qt::SmoothTransformation),
+ 24).scaled(
+ size,
+ Qt::IgnoreAspectRatio,
+ Qt::SmoothTransformation));
+
+ _forBothOverlay = std::make_unique>(
+ this,
+ object_ptr(this));
+ const auto overlay = _forBothOverlay->entity();
+
+ sizeValue() | rpl::start_with_next([=](QSize size) {
+ _forBothOverlay->setGeometry({ QPoint(), size });
+ overlay->setGeometry({ QPoint(), size });
+ }, _forBothOverlay->lifetime());
+
+ overlay->paintRequest(
+ ) | rpl::start_with_next([=](QRect clip) {
+ auto p = QPainter(overlay);
+ p.drawImage(0, 0, bg);
+ p.fillRect(clip, QColor(0, 0, 0, 64));
+ }, overlay->lifetime());
+
+ using namespace Ui;
+ const auto forMe = CreateChild(
+ overlay,
+ tr::lng_background_apply_me(),
+ st::backgroundConfirm);
+ forMe->setClickedCallback([=] {
+ applyForPeer(false);
+ });
+ using namespace rpl::mappers;
+ const auto forBoth = ::Settings::CreateLockedButton(
+ overlay,
+ tr::lng_background_apply_both(
+ lt_user,
+ rpl::single(_forPeer->shortName())),
+ st::backgroundConfirm,
+ Data::AmPremiumValue(&_forPeer->session()) | rpl::map(!_1));
+ forBoth->setClickedCallback([=] {
+ if (_forPeer->session().premium()) {
+ applyForPeer(true);
+ } else {
+ ShowPremiumPreviewBox(
+ _controller->uiShow(),
+ PremiumPreview::Wallpapers);
+ }
+ });
+ const auto cancel = CreateChild(
+ overlay,
+ tr::lng_cancel(),
+ st::backgroundConfirmCancel);
+ cancel->setClickedCallback([=] {
+ const auto raw = _forBothOverlay.release();
+ raw->shownValue() | rpl::filter(
+ !rpl::mappers::_1
+ ) | rpl::take(1) | rpl::start_with_next(crl::guard(raw, [=] {
+ delete raw;
+ }), raw->lifetime());
+ raw->toggle(false, anim::type::normal);
+ });
+ forMe->setTextTransform(RoundButton::TextTransform::NoTransform);
+ forBoth->setTextTransform(RoundButton::TextTransform::NoTransform);
+ cancel->setTextTransform(RoundButton::TextTransform::NoTransform);
+
+ overlay->sizeValue(
+ ) | rpl::start_with_next([=](QSize size) {
+ const auto padding = st::backgroundConfirmPadding;
+ const auto width = size.width()
+ - padding.left()
+ - padding.right();
+ const auto height = cancel->height();
+ auto top = size.height() - padding.bottom() - height;
+ cancel->setGeometry(padding.left(), top, width, height);
+ top -= height + padding.top();
+ forBoth->setGeometry(padding.left(), top, width, height);
+ top -= height + padding.top();
+ forMe->setGeometry(padding.left(), top, width, height);
+ }, _forBothOverlay->lifetime());
+
+ _forBothOverlay->hide(anim::type::instant);
+ _forBothOverlay->show(anim::type::normal);
}
void BackgroundPreviewBox::applyForPeer(bool both) {
- if (Data::IsCustomWallPaper(_paper)) {
+ using namespace Data;
+ if (forChannel() && !_paperEmojiId.isEmpty()) {
+ setExistingForPeer(WallPaper::FromEmojiId(_paperEmojiId), both);
+ } else if (IsCustomWallPaper(_paper)) {
uploadForPeer(both);
} else {
setExistingForPeer(_paper, both);
@@ -855,7 +950,7 @@ int BackgroundPreviewBox::textsTop() const {
- st::historyPaddingBottom
- (_service ? _service->height() : 0)
- _text1->height()
- - _text2->height();
+ - (forChannel() ? _text2->height() : 0);
}
QRect BackgroundPreviewBox::radialRect() const {
@@ -885,10 +980,11 @@ void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) {
context.outbg = _text1->hasOutLayout();
_text1->draw(p, context);
p.translate(0, height1);
-
- context.outbg = _text2->hasOutLayout();
- _text2->draw(p, context);
- p.translate(0, height2);
+ if (!forChannel()) {
+ context.outbg = _text2->hasOutLayout();
+ _text2->draw(p, context);
+ p.translate(0, height2);
+ }
}
void BackgroundPreviewBox::radialAnimationCallback(crl::time now) {
@@ -988,7 +1084,9 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector &bg) {
_service = GenerateServiceItem(
delegate(),
_serviceHistory,
- ((_forPeer && !_fromMessageId)
+ (forChannel()
+ ? tr::lng_background_other_channel(tr::now)
+ : (_forPeer && !_fromMessageId)
? tr::lng_background_other_info(
tr::now,
lt_user,
diff --git a/Telegram/SourceFiles/boxes/background_preview_box.h b/Telegram/SourceFiles/boxes/background_preview_box.h
index 8c1bd4346..3eb06d1a5 100644
--- a/Telegram/SourceFiles/boxes/background_preview_box.h
+++ b/Telegram/SourceFiles/boxes/background_preview_box.h
@@ -94,18 +94,24 @@ private:
void applyDarkMode(bool dark);
[[nodiscard]] OverridenStyle prepareOverridenStyle(bool dark);
+ [[nodiscard]] bool forChannel() const;
+ void checkLevelForChannel();
+
+ void recreate(bool dark);
void resetTitle();
void rebuildButtons(bool dark);
void createDimmingSlider(bool dark);
const not_null _controller;
PeerData * const _forPeer = nullptr;
+ bool _forPeerLevelCheck = false;
FullMsgId _fromMessageId;
std::unique_ptr _chatStyle;
const not_null _serviceHistory;
AdminLog::OwnedItem _service;
AdminLog::OwnedItem _text1;
AdminLog::OwnedItem _text2;
+ QString _paperEmojiId;
Data::WallPaper _paper;
std::shared_ptr _media;
QImage _full;
diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
index 1244964d2..92c6862fd 100644
--- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp
+++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
@@ -12,18 +12,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "base/unixtime.h"
#include "base/weak_ptr.h"
+#include "boxes/peer_list_controllers.h" // ContactsBoxController.
#include "boxes/peers/prepare_short_info_box.h"
+#include "boxes/peers/replace_boost_box.h" // BoostsForGift.
+#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
+#include "core/ui_integration.h" // Core::MarkedTextContext.
#include "data/data_boosts.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
-#include "data/data_media_types.h" // Data::Giveaway
+#include "data/data_media_types.h" // Data::GiveawayStart.
#include "data/data_peer_values.h" // Data::PeerPremiumValue.
#include "data/data_session.h"
#include "data/data_subscription_option.h"
#include "data/data_user.h"
+#include "data/stickers/data_custom_emoji.h"
+#include "info/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mainwidget.h"
+#include "payments/payments_checkout_process.h"
+#include "payments/payments_form.h"
#include "settings/settings_premium.h"
#include "ui/basic_click_handlers.h" // UrlClickHandler::Open.
#include "ui/boxes/boost_box.h" // StartFireworks.
@@ -33,11 +41,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/premium_top_bar.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/layers/generic_box.h"
+#include "ui/painter.h"
#include "ui/rect.h"
+#include "ui/vertical_list.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/gradient_round_button.h"
#include "ui/wrap/padding_wrap.h"
+#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/table_layout.h"
#include "window/window_peer_menu.h" // ShowChooseRecipientBox.
#include "window/window_session_controller.h"
@@ -52,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kDiscountDivider = 5.;
+constexpr auto kUserpicsMax = size_t(3);
using GiftOption = Data::SubscriptionOption;
using GiftOptions = Data::SubscriptionOptions;
@@ -72,6 +84,137 @@ GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
return result;
}
+using TagUser1 = lngtag_user;
+using TagUser2 = lngtag_second_user;
+using TagUser3 = lngtag_name;
+[[nodiscard]] rpl::producer ComplexAboutLabel(
+ const std::vector> &users,
+ tr::phrase phrase1,
+ tr::phrase phrase2,
+ tr::phrase phrase3,
+ tr::phrase phraseMore) {
+ Expects(!users.empty());
+
+ const auto count = users.size();
+ const auto nameValue = [&](not_null user) {
+ return user->session().changes().peerFlagsValue(
+ user,
+ Data::PeerUpdate::Flag::Name
+ ) | rpl::map([=] { return TextWithEntities{ user->firstName }; });
+ };
+ if (count == 1) {
+ return phrase1(
+ lt_user,
+ nameValue(users.front()),
+ Ui::Text::RichLangValue);
+ } else if (count == 2) {
+ return phrase2(
+ lt_user,
+ nameValue(users.front()),
+ lt_second_user,
+ nameValue(users[1]),
+ Ui::Text::RichLangValue);
+ } else if (count == 3) {
+ return phrase3(
+ lt_user,
+ nameValue(users.front()),
+ lt_second_user,
+ nameValue(users[1]),
+ lt_name,
+ nameValue(users[2]),
+ Ui::Text::RichLangValue);
+ } else {
+ return phraseMore(
+ lt_count,
+ rpl::single(count - kUserpicsMax) | tr::to_count(),
+ lt_user,
+ nameValue(users.front()),
+ lt_second_user,
+ nameValue(users[1]),
+ lt_name,
+ nameValue(users[2]),
+ Ui::Text::RichLangValue);
+ }
+}
+
+[[nodiscard]] not_null CircleBadge(
+ not_null parent,
+ const QString &text) {
+ const auto widget = Ui::CreateChild(parent.get());
+
+ const auto full = Rect(st::premiumGiftsUserpicBadgeSize);
+ const auto inner = full - Margins(st::premiumGiftsUserpicBadgeInner);
+ auto gradient = QLinearGradient(
+ QPointF(0, full.height()),
+ QPointF(full.width(), 0));
+ gradient.setStops(Ui::Premium::GiftGradientStops());
+
+ widget->paintRequest(
+ ) | rpl::start_with_next([=] {
+ auto p = QPainter(widget);
+ auto hq = PainterHighQualityEnabler(p);
+ p.setPen(Qt::NoPen);
+ p.setBrush(st::boxBg);
+ p.drawEllipse(full);
+ p.setPen(Qt::NoPen);
+ p.setBrush(gradient);
+ p.drawEllipse(inner);
+ p.setFont(st::premiumGiftsUserpicBadgeFont);
+ p.setPen(st::premiumButtonFg);
+ p.drawText(full, text, style::al_center);
+ }, widget->lifetime());
+ widget->resize(full.size());
+ return widget;
+}
+
+[[nodiscard]] not_null UserpicsContainer(
+ not_null parent,
+ std::vector> users) {
+ Expects(!users.empty());
+
+ if (users.size() == 1) {
+ const auto userpic = Ui::CreateChild(
+ parent.get(),
+ users.front(),
+ st::defaultUserpicButton);
+ userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
+ return userpic;
+ }
+
+ const auto &singleSize = st::defaultUserpicButton.size;
+
+ const auto container = Ui::CreateChild(parent.get());
+ const auto single = singleSize.width();
+ const auto shift = single - st::boostReplaceUserpicsShift;
+ const auto maxWidth = users.size() * (single - shift) + shift;
+ container->resize(maxWidth, singleSize.height());
+ container->setAttribute(Qt::WA_TransparentForMouseEvents);
+
+ const auto diff = (single - st::premiumGiftsUserpicButton.size.width())
+ / 2;
+ for (auto i = 0; i < users.size(); i++) {
+ const auto bg = Ui::CreateChild(container);
+ bg->resize(singleSize);
+ bg->paintRequest(
+ ) | rpl::start_with_next([=] {
+ auto p = QPainter(bg);
+ auto hq = PainterHighQualityEnabler(p);
+ p.setPen(Qt::NoPen);
+ p.setBrush(st::boxBg);
+ p.drawEllipse(bg->rect());
+ }, bg->lifetime());
+ bg->moveToLeft(std::max(0, i * (single - shift)), 0);
+
+ const auto userpic = Ui::CreateChild(
+ bg,
+ users[i],
+ st::premiumGiftsUserpicButton);
+ userpic->moveToLeft(diff, diff);
+ }
+
+ return container;
+}
+
void GiftBox(
not_null box,
not_null controller,
@@ -95,12 +238,11 @@ void GiftBox(
+ st::defaultUserpicButton.size.height()));
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
- const auto stars = box->lifetime().make_state(top, true);
-
- const auto userpic = Ui::CreateChild(
+ const auto stars = box->lifetime().make_state(
top,
- user,
- st::defaultUserpicButton);
+ true);
+
+ const auto userpic = UserpicsContainer(top, { user });
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
top->widthValue(
) | rpl::start_with_next([=](int width) {
@@ -211,7 +353,7 @@ void GiftBox(
auto raw = Settings::CreateSubscribeButton({
controller,
box,
- [] { return QString("gift"); },
+ [] { return u"gift"_q; },
state->buttonText.events(),
Ui::Premium::GiftGradientStops(),
[=] {
@@ -222,10 +364,8 @@ void GiftBox(
},
});
auto button = object_ptr::fromRaw(raw);
- button->resizeToWidth(boxWidth
- - stButton.buttonPadding.left()
- - stButton.buttonPadding.right());
- box->setShowFinishedCallback([raw = button.data()]{
+ button->resizeToWidth(boxWidth - rect::m::sum::h(stButton.buttonPadding));
+ box->setShowFinishedCallback([raw = button.data()] {
raw->startGlareAnimation();
});
box->addButton(std::move(button));
@@ -239,6 +379,302 @@ void GiftBox(
}, box->lifetime());
}
+void GiftsBox(
+ not_null box,
+ not_null controller,
+ std::vector> users,
+ not_null api) {
+ Expects(!users.empty());
+
+ const auto boxWidth = st::boxWideWidth;
+ box->setWidth(boxWidth);
+ box->setNoContentMargin(true);
+ const auto buttonsParent = box->verticalLayout().get();
+ const auto session = &users.front()->session();
+
+ struct State {
+ rpl::event_stream buttonText;
+ rpl::variable confirmButtonBusy = false;
+ rpl::variable isPaymentComplete = false;
+ };
+ const auto state = box->lifetime().make_state();
+
+ const auto userpicPadding = st::premiumGiftUserpicPadding;
+ const auto top = box->addRow(object_ptr(
+ buttonsParent,
+ userpicPadding.top()
+ + userpicPadding.bottom()
+ + st::defaultUserpicButton.size.height()));
+
+ using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
+ const auto stars = box->lifetime().make_state(
+ top,
+ true);
+
+ const auto maxWithUserpic = std::min(users.size(), kUserpicsMax);
+ const auto userpics = UserpicsContainer(
+ top,
+ { users.begin(), users.begin() + maxWithUserpic });
+ top->widthValue(
+ ) | rpl::start_with_next([=](int width) {
+ userpics->moveToLeft(
+ (width - userpics->width()) / 2,
+ userpicPadding.top());
+
+ const auto center = top->rect().center();
+ const auto size = QSize(
+ userpics->width() * Ui::Premium::MiniStars::kSizeFactor,
+ userpics->height());
+ const auto ministarsRect = QRect(
+ QPoint(center.x() - size.width(), center.y() - size.height()),
+ QPoint(center.x() + size.width(), center.y() + size.height()));
+ stars->setPosition(ministarsRect.topLeft());
+ stars->setSize(ministarsRect.size());
+ }, userpics->lifetime());
+ if (const auto rest = users.size() - maxWithUserpic; rest > 0) {
+ const auto badge = CircleBadge(
+ userpics,
+ QChar('+') + QString::number(rest));
+ badge->moveToRight(0, userpics->height() - badge->height());
+ }
+
+ top->paintRequest(
+ ) | rpl::start_with_next([=](const QRect &r) {
+ auto p = QPainter(top);
+
+ p.fillRect(r, Qt::transparent);
+ stars->paint(p);
+ }, top->lifetime());
+
+ const auto close = Ui::CreateChild(
+ buttonsParent,
+ st::infoTopBarClose);
+ close->setClickedCallback([=] { box->closeBox(); });
+
+ buttonsParent->widthValue(
+ ) | rpl::start_with_next([=](int width) {
+ close->moveToRight(0, 0, width);
+ }, close->lifetime());
+
+ // Header.
+ const auto &padding = st::premiumGiftAboutPadding;
+ const auto available = boxWidth - padding.left() - padding.right();
+ const auto &stTitle = st::premiumPreviewAboutTitle;
+ auto titleLabel = object_ptr(
+ box,
+ rpl::conditional(
+ state->isPaymentComplete.value(),
+ tr::lng_premium_gifts_about_paid_title(),
+ tr::lng_premium_gift_title()),
+ stTitle);
+ titleLabel->resizeToWidth(available);
+ box->addRow(
+ object_ptr>(
+ box,
+ std::move(titleLabel)),
+ st::premiumGiftTitlePadding);
+
+ // About.
+ {
+ const auto emoji = Ui::Text::SingleCustomEmoji(
+ session->data().customEmojiManager().registerInternalEmoji(
+ st::premiumGiftsBoostIcon,
+ QMargins(0, st::premiumGiftsUserpicBadgeInner, 0, 0),
+ false));
+ auto text = rpl::conditional(
+ state->isPaymentComplete.value(),
+ ComplexAboutLabel(
+ users,
+ tr::lng_premium_gifts_about_paid1,
+ tr::lng_premium_gifts_about_paid2,
+ tr::lng_premium_gifts_about_paid3,
+ tr::lng_premium_gifts_about_paid_more
+ ) | rpl::map([count = users.size()](TextWithEntities text) {
+ text.append('\n');
+ text.append('\n');
+ text.append(tr::lng_premium_gifts_about_paid_below(
+ tr::now,
+ lt_count,
+ float64(count),
+ Ui::Text::RichLangValue));
+ return text;
+ }),
+ ComplexAboutLabel(
+ users,
+ tr::lng_premium_gifts_about_user1,
+ tr::lng_premium_gifts_about_user2,
+ tr::lng_premium_gifts_about_user3,
+ tr::lng_premium_gifts_about_user_more
+ ) | rpl::map([=, count = users.size()](TextWithEntities text) {
+ text.append('\n');
+ text.append('\n');
+ text.append(tr::lng_premium_gifts_about_reward(
+ tr::now,
+ lt_count,
+ count * BoostsForGift(session),
+ lt_emoji,
+ emoji,
+ Ui::Text::RichLangValue));
+ return text;
+ })
+ );
+ const auto label = box->addRow(
+ object_ptr>(
+ box,
+ object_ptr(box, st::premiumPreviewAbout)),
+ padding)->entity();
+ std::move(
+ text
+ ) | rpl::start_with_next([=](const TextWithEntities &t) {
+ using namespace Core;
+ label->setMarkedText(t, MarkedTextContext{ .session = session });
+ }, label->lifetime());
+ label->setTextColorOverride(stTitle.textFg->c);
+ label->resizeToWidth(available);
+ }
+
+ // List.
+ const auto optionsContainer = buttonsParent->add(
+ object_ptr>(
+ buttonsParent,
+ object_ptr(buttonsParent)));
+ const auto options = api->options(users.size());
+ const auto group = std::make_shared();
+ const auto groupValueChangedCallback = [=](int value) {
+ Expects(value < options.size() && value >= 0);
+ auto text = tr::lng_premium_gift_button(
+ tr::now,
+ lt_cost,
+ options[value].costTotal);
+ state->buttonText.fire(std::move(text));
+ };
+ group->setChangedCallback(groupValueChangedCallback);
+ Ui::Premium::AddGiftOptions(
+ optionsContainer->entity(),
+ group,
+ options,
+ st::premiumGiftOption);
+ optionsContainer->toggleOn(
+ state->isPaymentComplete.value() | rpl::map(!rpl::mappers::_1),
+ anim::type::instant);
+
+ // Summary.
+ {
+ {
+ // Will be hidden after payment.
+ const auto content = optionsContainer->entity();
+ Ui::AddSkip(content);
+ Ui::AddDivider(content);
+ Ui::AddSkip(content);
+ Ui::AddSubsectionTitle(
+ content,
+ tr::lng_premium_gifts_summary_subtitle());
+ }
+ const auto content = box->addRow(
+ object_ptr(box),
+ {});
+ auto buttonCallback = [=](PremiumPreview section) {
+ stars->setPaused(true);
+ const auto previewBoxShown = [=](
+ not_null previewBox) {
+ previewBox->boxClosing(
+ ) | rpl::start_with_next(crl::guard(box, [=] {
+ stars->setPaused(false);
+ }), previewBox->lifetime());
+ };
+
+ ShowPremiumPreviewBox(
+ controller->uiShow(),
+ section,
+ previewBoxShown,
+ true);
+ };
+ Settings::AddSummaryPremium(
+ content,
+ controller,
+ u"gift"_q,
+ std::move(buttonCallback));
+ }
+
+ // Footer.
+ {
+ box->addRow(
+ object_ptr(
+ box,
+ object_ptr(
+ box,
+ session->api().premium().statusTextValue(), // TODO.
+ st::premiumGiftTerms),
+ st::defaultBoxDividerLabelPadding),
+ {});
+ }
+
+ // Button.
+ const auto &stButton = st::premiumGiftBox;
+ box->setStyle(stButton);
+ auto raw = Settings::CreateSubscribeButton({
+ controller,
+ box,
+ [] { return u"gift"_q; },
+ rpl::combine(
+ state->buttonText.events(),
+ state->confirmButtonBusy.value(),
+ state->isPaymentComplete.value()
+ ) | rpl::map([](const QString &text, bool busy, bool paid) {
+ return busy
+ ? QString()
+ : paid
+ ? tr::lng_close(tr::now)
+ : text;
+ }),
+ Ui::Premium::GiftGradientStops(),
+ });
+ raw->setClickedCallback([=] {
+ if (state->confirmButtonBusy.current()) {
+ return;
+ }
+ if (state->isPaymentComplete.current()) {
+ return box->closeBox();
+ }
+ auto invoice = api->invoice(
+ users.size(),
+ api->monthsFromPreset(group->value()));
+ invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{ users };
+
+ state->confirmButtonBusy = true;
+ const auto show = box->uiShow();
+ const auto weak = Ui::MakeWeak(box.get());
+ const auto done = [=](Payments::CheckoutResult result) {
+ if (const auto strong = weak.data()) {
+ strong->window()->setFocus();
+ state->confirmButtonBusy = false;
+ if (result == Payments::CheckoutResult::Paid) {
+ state->isPaymentComplete = true;
+ Ui::StartFireworks(box->parentWidget());
+ }
+ }
+ };
+
+ Payments::CheckoutProcess::Start(std::move(invoice), done);
+ });
+ {
+ using namespace Info::Statistics;
+ const auto loadingAnimation = InfiniteRadialAnimationWidget(
+ raw,
+ raw->height() / 2);
+ AddChildToWidgetCenter(raw, loadingAnimation);
+ loadingAnimation->showOn(state->confirmButtonBusy.value());
+ }
+ auto button = object_ptr::fromRaw(raw);
+ button->resizeToWidth(boxWidth - rect::m::sum::h(stButton.buttonPadding));
+ box->setShowFinishedCallback([raw = button.data()] {
+ raw->startGlareAnimation();
+ });
+ box->addButton(std::move(button));
+
+ groupValueChangedCallback(0);
+}
+
[[nodiscard]] Data::GiftCodeLink MakeGiftCodeLink(
not_null session,
const QString &slug) {
@@ -370,18 +806,20 @@ void AddTable(
container,
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
- AddTableRow(
- table,
- tr::lng_gift_link_label_from(),
- controller,
- current.from);
- if (current.to) {
+ if (current.from) {
+ AddTableRow(
+ table,
+ tr::lng_gift_link_label_from(),
+ controller,
+ current.from);
+ }
+ if (current.from && current.to) {
AddTableRow(
table,
tr::lng_gift_link_label_to(),
controller,
current.to);
- } else {
+ } else if (current.from) {
AddTableRow(
table,
tr::lng_gift_link_label_to(),
@@ -394,7 +832,7 @@ void AddTable(
lt_duration,
GiftDurationValue(current.months) | Ui::Text::ToWithEntities(),
Ui::Text::WithEntities));
- if (!skipReason) {
+ if (!skipReason && current.from) {
const auto reason = AddTableRow(
table,
tr::lng_gift_link_label_reason(),
@@ -439,6 +877,112 @@ void GiftPremiumValidator::cancel() {
_requestId = 0;
}
+void GiftPremiumValidator::showChoosePeerBox() {
+ if (_manyGiftsLifetime) {
+ return;
+ }
+ using namespace Api;
+ const auto api = _manyGiftsLifetime.make_state(
+ _controller->session().user());
+ const auto show = _controller->uiShow();
+ api->request(
+ ) | rpl::start_with_error_done([=](const QString &error) {
+ show->showToast(error);
+ }, [=] {
+ const auto maxAmount = *ranges::max_element(api->availablePresets());
+
+ class Controller final : public ContactsBoxController {
+ public:
+ Controller(
+ not_null session,
+ Fn checkErrorCallback)
+ : ContactsBoxController(session)
+ , _checkErrorCallback(std::move(checkErrorCallback)) {
+ }
+
+ protected:
+ std::unique_ptr createRow(
+ not_null user) override {
+ return !user->isSelf()
+ ? ContactsBoxController::createRow(user)
+ : nullptr;
+ }
+
+ void rowClicked(not_null row) override {
+ const auto checked = !row->checked();
+ if (checked
+ && _checkErrorCallback
+ && _checkErrorCallback(
+ delegate()->peerListSelectedRowsCount())) {
+ return;
+ }
+ delegate()->peerListSetRowChecked(row, checked);
+ }
+
+ private:
+ const Fn _checkErrorCallback;
+
+ };
+ auto initBox = [=](not_null peersBox) {
+ const auto ignoreClose = peersBox->lifetime().make_state(0);
+
+ auto process = [=] {
+ const auto selected = peersBox->collectSelectedRows();
+ const auto users = ranges::views::all(
+ selected
+ ) | ranges::views::transform([](not_null p) {
+ return p->asUser();
+ }) | ranges::views::filter([](UserData *u) -> bool {
+ return u;
+ }) | ranges::to>>();
+ if (!users.empty()) {
+ const auto giftBox = show->show(
+ Box(GiftsBox, _controller, users, api));
+ giftBox->boxClosing(
+ ) | rpl::start_with_next([=] {
+ _manyGiftsLifetime.destroy();
+ }, giftBox->lifetime());
+ }
+ (*ignoreClose) = true;
+ peersBox->closeBox();
+ };
+
+ peersBox->setTitle(tr::lng_premium_gift_title());
+ peersBox->addButton(
+ tr::lng_settings_gift_premium_users_confirm(),
+ std::move(process));
+ peersBox->addButton(tr::lng_cancel(), [=] {
+ peersBox->closeBox();
+ });
+ peersBox->boxClosing(
+ ) | rpl::start_with_next([=] {
+ if (!(*ignoreClose)) {
+ _manyGiftsLifetime.destroy();
+ }
+ }, peersBox->lifetime());
+ };
+
+ auto listController = std::make_unique(
+ &_controller->session(),
+ [=](int count) {
+ if (count <= maxAmount) {
+ return false;
+ }
+ show->showToast(tr::lng_settings_gift_premium_users_error(
+ tr::now,
+ lt_count,
+ maxAmount));
+ return true;
+ });
+ show->showBox(
+ Box(
+ std::move(listController),
+ std::move(initBox)),
+ Ui::LayerOption::KeepOther);
+
+ }, _manyGiftsLifetime);
+}
+
void GiftPremiumValidator::showBox(not_null user) {
if (_requestId) {
return;
@@ -723,10 +1267,22 @@ void GiftCodePendingBox(
void ResolveGiftCode(
not_null controller,
- const QString &slug) {
+ const QString &slug,
+ PeerId fromId,
+ PeerId toId) {
const auto done = [=](Api::GiftCode code) {
+ const auto session = &controller->session();
+ const auto selfId = session->userPeerId();
if (!code) {
controller->showToast(tr::lng_gift_link_expired(tr::now));
+ } else if (!code.from && fromId == selfId) {
+ code.from = fromId;
+ code.to = toId;
+ const auto self = (fromId == selfId);
+ const auto peer = session->data().peer(self ? toId : fromId);
+ const auto months = code.months;
+ const auto parent = controller->parentController();
+ Settings::ShowGiftPremium(parent, peer, months, self);
} else {
controller->uiShow()->showBox(Box(GiftCodeBox, controller, slug));
}
@@ -739,8 +1295,11 @@ void ResolveGiftCode(
void GiveawayInfoBox(
not_null box,
not_null controller,
- Data::Giveaway giveaway,
+ std::optional start,
+ std::optional results,
Api::GiveawayInfo info) {
+ Expects(start || results);
+
using State = Api::GiveawayState;
const auto finished = (info.state == State::Finished)
|| (info.state == State::Refunded);
@@ -749,10 +1308,31 @@ void GiveawayInfoBox(
? tr::lng_prizes_end_title
: tr::lng_prizes_how_title)());
- const auto first = !giveaway.channels.empty()
- ? giveaway.channels.front()->name()
+ const auto first = results
+ ? results->channel->name()
+ : !start->channels.empty()
+ ? start->channels.front()->name()
: u"channel"_q;
- auto text = (finished
+ auto text = TextWithEntities();
+
+ if (!info.giftCode.isEmpty()) {
+ text.append("\n\n");
+ text.append(Ui::Text::Bold(tr::lng_prizes_you_won(
+ tr::now,
+ lt_cup,
+ QString::fromUtf8("\xf0\x9f\x8f\x86"))));
+ text.append("\n\n");
+ } else if (info.state == State::Finished) {
+ text.append("\n\n");
+ text.append(Ui::Text::Bold(tr::lng_prizes_you_didnt(tr::now)));
+ text.append("\n\n");
+ }
+
+ const auto quantity = start
+ ? start->quantity
+ : (results->winnersCount + results->unclaimedCount);
+ const auto months = start ? start->months : results->months;
+ text.append((finished
? tr::lng_prizes_end_text
: tr::lng_prizes_how_text)(
tr::now,
@@ -760,18 +1340,21 @@ void GiveawayInfoBox(
tr::lng_prizes_admins(
tr::now,
lt_count,
- giveaway.quantity,
+ quantity,
lt_channel,
Ui::Text::Bold(first),
lt_duration,
- TextWithEntities{ GiftDuration(giveaway.months) },
+ TextWithEntities{ GiftDuration(months) },
Ui::Text::RichLangValue),
- Ui::Text::RichLangValue);
- const auto many = (giveaway.channels.size() > 1);
+ Ui::Text::RichLangValue));
+ const auto many = start
+ ? (start->channels.size() > 1)
+ : (results->additionalPeersCount > 0);
const auto count = info.winnersCount
? info.winnersCount
- : giveaway.quantity;
- auto winners = giveaway.all
+ : quantity;
+ const auto all = start ? start->all : results->all;
+ auto winners = all
? (many
? tr::lng_prizes_winners_all_of_many
: tr::lng_prizes_winners_all_of_one)(
@@ -793,13 +1376,30 @@ void GiveawayInfoBox(
Ui::Text::Bold(
langDateTime(base::unixtime::parse(info.startDate))),
Ui::Text::RichLangValue);
+ const auto additionalPrize = results
+ ? results->additionalPrize
+ : start->additionalPrize;
+ if (!additionalPrize.isEmpty()) {
+ text.append("\n\n").append(tr::lng_prizes_additional_added(
+ tr::now,
+ lt_count,
+ count,
+ lt_channel,
+ Ui::Text::Bold(first),
+ lt_prize,
+ TextWithEntities{ additionalPrize },
+ Ui::Text::RichLangValue));
+ }
+ const auto untilDate = start
+ ? start->untilDate
+ : results->untilDate;
text.append("\n\n").append((finished
? tr::lng_prizes_end_when_finish
: tr::lng_prizes_how_when_finish)(
tr::now,
lt_date,
Ui::Text::Bold(langDayOfMonthFull(
- base::unixtime::parse(giveaway.untilDate).date())),
+ base::unixtime::parse(untilDate).date())),
lt_winners,
winners,
Ui::Text::RichLangValue));
@@ -810,17 +1410,9 @@ void GiveawayInfoBox(
info.activatedCount,
Ui::Text::RichLangValue));
}
- if (!info.giftCode.isEmpty()) {
- text.append("\n\n");
- text.append(tr::lng_prizes_you_won(
- tr::now,
- lt_cup,
- QString::fromUtf8("\xf0\x9f\x8f\x86")));
- } else if (info.state == State::Finished) {
- text.append("\n\n");
- text.append(tr::lng_prizes_you_didnt(tr::now));
- } else if (info.state == State::Preparing) {
-
+ if (!info.giftCode.isEmpty()
+ || info.state == State::Finished
+ || info.state == State::Preparing) {
} else if (info.state != State::Refunded) {
if (info.adminChannelId) {
const auto channel = controller->session().data().channel(
@@ -858,7 +1450,7 @@ void GiveawayInfoBox(
Ui::Text::Bold(first),
lt_date,
Ui::Text::Bold(langDayOfMonthFull(
- base::unixtime::parse(giveaway.untilDate).date())),
+ base::unixtime::parse(untilDate).date())),
Ui::Text::RichLangValue));
}
}
@@ -902,14 +1494,15 @@ void ResolveGiveawayInfo(
not_null controller,
not_null peer,
MsgId messageId,
- Data::Giveaway giveaway) {
+ std::optional start,
+ std::optional results) {
const auto show = [=](Api::GiveawayInfo info) {
if (!info) {
controller->showToast(
tr::lng_confirm_phone_link_invalid(tr::now));
} else {
controller->uiShow()->showBox(
- Box(GiveawayInfoBox, controller, giveaway, info));
+ Box(GiveawayInfoBox, controller, start, results, info));
}
};
controller->session().api().premium().resolveGiveawayInfo(
diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h
index 1891ce5ff..5a0bfb659 100644
--- a/Telegram/SourceFiles/boxes/gift_premium_box.h
+++ b/Telegram/SourceFiles/boxes/gift_premium_box.h
@@ -16,7 +16,8 @@ struct GiftCode;
} // namespace Api
namespace Data {
-struct Giveaway;
+struct GiveawayStart;
+struct GiveawayResults;
} // namespace Data
namespace Ui {
@@ -33,6 +34,7 @@ public:
GiftPremiumValidator(not_null controller);
void showBox(not_null user);
+ void showChoosePeerBox();
void cancel();
private:
@@ -41,6 +43,8 @@ private:
mtpRequestId _requestId = 0;
+ rpl::lifetime _manyGiftsLifetime;
+
};
[[nodiscard]] rpl::producer GiftDurationValue(int months);
@@ -56,10 +60,13 @@ void GiftCodePendingBox(
const Api::GiftCode &data);
void ResolveGiftCode(
not_null controller,
- const QString &slug);
+ const QString &slug,
+ PeerId fromId = 0,
+ PeerId toId = 0);
void ResolveGiveawayInfo(
not_null controller,
not_null peer,
MsgId messageId,
- Data::Giveaway giveaway);
+ std::optional start,
+ std::optional results);
diff --git a/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp b/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp
index 6ccc564fc..7d4d74453 100644
--- a/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp
@@ -40,11 +40,14 @@ public:
not_null navigation,
not_null bot,
RequestPeerQuery query,
- Fn)> callback);
+ Fn>)> callback);
Main::Session &session() const override;
void rowClicked(not_null row) override;
+ [[nodiscard]] rpl::producer selectedCountValue() const;
+ void submit();
+
QString savedMessagesChatStatus() const override {
return tr::lng_saved_forward_here(tr::now);
}
@@ -60,7 +63,9 @@ private:
not_null _bot;
RequestPeerQuery _query;
base::flat_set> _commonGroups;
- Fn)> _callback;
+ base::flat_set> _selected;
+ rpl::variable _selectedCount;
+ Fn>)> _callback;
};
@@ -253,10 +258,10 @@ object_ptr CreatePeerByQueryBox(
not_null navigation,
not_null bot,
RequestPeerQuery query,
- Fn)> done) {
+ Fn>)> done) {
const auto weak = std::make_shared>();
auto callback = [=](not_null peer) {
- done(peer);
+ done({ peer });
if (const auto strong = weak->data()) {
strong->closeBox();
}
@@ -332,7 +337,7 @@ ChoosePeerBoxController::ChoosePeerBoxController(
not_null navigation,
not_null bot,
RequestPeerQuery query,
- Fn)> callback)
+ Fn>)> callback)
: ChatsListBoxController(&navigation->session())
, _navigation(navigation)
, _bot(bot)
@@ -415,6 +420,8 @@ void ChoosePeerBoxController::prepareViewHook() {
switch (_query.type) {
case Type::User: return (_query.userIsBot == Restriction::Yes)
? tr::lng_request_bot_title()
+ : (_query.maxQuantity > 1)
+ ? tr::lng_request_users_title()
: tr::lng_request_user_title();
case Type::Group: return tr::lng_request_group_title();
case Type::Broadcast: return tr::lng_request_channel_title();
@@ -425,10 +432,24 @@ void ChoosePeerBoxController::prepareViewHook() {
}
void ChoosePeerBoxController::rowClicked(not_null row) {
+ const auto limit = _query.maxQuantity;
+ const auto multiselect = (limit > 1);
const auto peer = row->peer();
+ if (multiselect) {
+ if (_selected.contains(peer) || _selected.size() < limit) {
+ delegate()->peerListSetRowChecked(row, !row->checked());
+ if (row->checked()) {
+ _selected.emplace(peer);
+ } else {
+ _selected.remove(peer);
+ }
+ _selectedCount = int(_selected.size());
+ }
+ return;
+ }
const auto done = [callback = _callback, peer] {
const auto onstack = callback;
- onstack(peer);
+ onstack({ peer });
};
if (const auto user = peer->asUser()) {
done();
@@ -438,6 +459,15 @@ void ChoosePeerBoxController::rowClicked(not_null row) {
}
}
+rpl::producer ChoosePeerBoxController::selectedCountValue() const {
+ return _selectedCount.value();
+}
+
+void ChoosePeerBoxController::submit() {
+ const auto onstack = _callback;
+ onstack(ranges::to_vector(_selected));
+}
+
auto ChoosePeerBoxController::createRow(not_null history)
-> std::unique_ptr {
return FilterPeerByQuery(history->peer, _query, _commonGroups)
@@ -474,7 +504,7 @@ void ShowChoosePeerBox(
not_null navigation,
not_null bot,
RequestPeerQuery query,
- Fn)> chosen) {
+ Fn>)> chosen) {
const auto needCommonGroups = query.isBotParticipant
&& (query.type == RequestPeerQuery::Type::Group)
&& !query.myRights;
@@ -488,22 +518,39 @@ void ShowChoosePeerBox(
return;
}
const auto weak = std::make_shared>();
- auto initBox = [=](not_null box) {
- box->addButton(tr::lng_cancel(), [box] {
- box->closeBox();
- });
- };
- auto callback = [=, done = std::move(chosen)](not_null peer) {
- done(peer);
+ auto callback = [=, done = std::move(chosen)](
+ std::vector> peers) {
+ done(std::move(peers));
if (const auto strong = weak->data()) {
strong->closeBox();
}
};
+ const auto limit = query.maxQuantity;
+ auto controller = std::make_unique(
+ navigation,
+ bot,
+ query,
+ std::move(callback));
+ auto initBox = [=, ptr = controller.get()](not_null box) {
+ ptr->selectedCountValue() | rpl::start_with_next([=](int count) {
+ box->clearButtons();
+ if (limit > 1) {
+ box->setAdditionalTitle(rpl::single(u"%1 / %2"_q.arg(count).arg(limit)));
+ }
+ if (count > 0) {
+ box->addButton(tr::lng_intro_submit(), [=] {
+ ptr->submit();
+ if (*weak) {
+ (*weak)->closeBox();
+ }
+ });
+ }
+ box->addButton(tr::lng_cancel(), [box] {
+ box->closeBox();
+ });
+ }, box->lifetime());
+ };
*weak = navigation->parentController()->show(Box(
- std::make_unique(
- navigation,
- bot,
- query,
- std::move(callback)),
+ std::move(controller),
std::move(initBox)));
}
diff --git a/Telegram/SourceFiles/boxes/peers/choose_peer_box.h b/Telegram/SourceFiles/boxes/peers/choose_peer_box.h
index 2c9ab7a1d..982ec4784 100644
--- a/Telegram/SourceFiles/boxes/peers/choose_peer_box.h
+++ b/Telegram/SourceFiles/boxes/peers/choose_peer_box.h
@@ -21,4 +21,4 @@ void ShowChoosePeerBox(
not_null navigation,
not_null bot,
RequestPeerQuery query,
- Fn)> chosen);
+ Fn>)> chosen);
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp
index fd3458c1d..38f064cb2 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp
@@ -9,12 +9,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "api/api_peer_colors.h"
+#include "api/api_peer_photo.h"
#include "base/unixtime.h"
#include "boxes/peers/replace_boost_box.h"
+#include "boxes/background_box.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/stickers/data_custom_emoji.h"
+#include "data/data_emoji_statuses.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/data_web_page.h"
@@ -428,11 +431,17 @@ HistoryView::Context PreviewDelegate::elementContext() {
return HistoryView::Context::AdminLog;
}
+struct SetValues {
+ uint8 colorIndex = 0;
+ DocumentId backgroundEmojiId = 0;
+ DocumentId statusId = 0;
+ TimeId statusUntil = 0;
+ bool statusChanged = false;
+};
void Set(
std::shared_ptr show,
not_null peer,
- uint8 colorIndex,
- DocumentId backgroundEmojiId) {
+ SetValues values) {
const auto wasIndex = peer->colorIndex();
const auto wasEmojiId = peer->backgroundEmojiId();
@@ -444,7 +453,7 @@ void Set(
peer,
UpdateFlag::Color | UpdateFlag::BackgroundEmoji);
};
- setLocal(colorIndex, backgroundEmojiId);
+ setLocal(values.colorIndex, values.backgroundEmojiId);
const auto done = [=] {
show->showToast(peer->isSelf()
@@ -452,8 +461,11 @@ void Set(
: tr::lng_settings_color_changed_channel(tr::now));
};
const auto fail = [=](const MTP::Error &error) {
- setLocal(wasIndex, wasEmojiId);
- show->showToast(error.type());
+ const auto type = error.type();
+ if (type != u"CHAT_NOT_MODIFIED"_q) {
+ setLocal(wasIndex, wasEmojiId);
+ show->showToast(type);
+ }
};
const auto send = [&](auto &&request) {
peer->session().api().request(
@@ -464,15 +476,23 @@ void Set(
using Flag = MTPaccount_UpdateColor::Flag;
send(MTPaccount_UpdateColor(
MTP_flags(Flag::f_color | Flag::f_background_emoji_id),
- MTP_int(colorIndex),
- MTP_long(backgroundEmojiId)));
+ MTP_int(values.colorIndex),
+ MTP_long(values.backgroundEmojiId)));
} else if (const auto channel = peer->asChannel()) {
using Flag = MTPchannels_UpdateColor::Flag;
send(MTPchannels_UpdateColor(
- MTP_flags(Flag::f_background_emoji_id),
+ MTP_flags(Flag::f_color | Flag::f_background_emoji_id),
channel->inputChannel,
- MTP_int(colorIndex),
- MTP_long(backgroundEmojiId)));
+ MTP_int(values.colorIndex),
+ MTP_long(values.backgroundEmojiId)));
+
+ if (values.statusChanged
+ && (values.statusId || peer->emojiStatusId())) {
+ peer->owner().emojiStatuses().set(
+ channel,
+ values.statusId,
+ values.statusUntil);
+ }
} else {
Unexpected("Invalid peer type in Set(colorIndex).");
}
@@ -481,13 +501,13 @@ void Set(
void Apply(
std::shared_ptr show,
not_null peer,
- uint8 colorIndex,
- DocumentId backgroundEmojiId,
+ SetValues values,
Fn close,
Fn cancel) {
const auto session = &peer->session();
- if (peer->colorIndex() == colorIndex
- && peer->backgroundEmojiId() == backgroundEmojiId) {
+ if (peer->colorIndex() == values.colorIndex
+ && peer->backgroundEmojiId() == values.backgroundEmojiId
+ && !values.statusChanged) {
close();
} else if (peer->isSelf() && !session->premium()) {
Settings::ShowPremiumPromoToast(
@@ -502,39 +522,45 @@ void Apply(
u"name_color"_q);
cancel();
} else if (peer->isSelf()) {
- Set(show, peer, colorIndex, backgroundEmojiId);
+ Set(show, peer, values);
close();
} else {
- session->api().request(MTPpremium_GetBoostsStatus(
- peer->input
- )).done([=](const MTPpremium_BoostsStatus &result) {
- const auto &data = result.data();
- const auto required = session->account().appConfig().get(
- "channel_color_level_min",
- 5);
- if (data.vlevel().v >= required) {
- Set(show, peer, colorIndex, backgroundEmojiId);
+ CheckBoostLevel(show, peer, [=](int level) {
+ const auto peerColors = &peer->session().api().peerColors();
+ const auto colorRequired = peerColors->requiredLevelFor(
+ peer->id,
+ values.colorIndex);
+ const auto iconRequired = values.backgroundEmojiId
+ ? session->account().appConfig().get(
+ "channel_bg_icon_level_min",
+ 5)
+ : 0;
+ const auto statusRequired = (values.statusChanged
+ && values.statusId)
+ ? session->account().appConfig().get(
+ "channel_emoji_status_level_min",
+ 8)
+ : 0;
+ const auto required = std::max({
+ colorRequired,
+ iconRequired,
+ statusRequired,
+ });
+ if (level >= required) {
+ Set(show, peer, values);
close();
- return;
+ return std::optional();
}
- const auto openStatistics = [=] {
- if (const auto controller = show->resolveWindow(
- ChatHelpers::WindowUsage::PremiumPromo)) {
- controller->showSection(Info::Boosts::Make(peer));
+ const auto reason = [&]() -> Ui::AskBoostReason {
+ if (level < statusRequired) {
+ return { Ui::AskBoostEmojiStatus{ statusRequired } };
+ } else if (level < iconRequired) {
+ return { Ui::AskBoostChannelColor{ iconRequired } };
}
- };
- auto counters = ParseBoostCounters(result);
- counters.mine = 0; // Don't show current level as just-reached.
- show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
- .link = qs(data.vboost_url()),
- .boost = counters,
- .reason = { Ui::AskBoostChannelColor{ required } },
- }, openStatistics, nullptr));
- cancel();
- }).fail([=](const MTP::Error &error) {
- show->showToast(error.type());
- cancel();
- }).send();
+ return { Ui::AskBoostChannelColor{ colorRequired } };
+ }();
+ return std::make_optional(reason);
+ }, cancel);
}
}
@@ -672,15 +698,18 @@ int ColorSelector::resizeGetHeight(int newWidth) {
const auto right = Ui::CreateChild(raw);
right->show();
+ using namespace Info::Profile;
struct State {
- Info::Profile::EmojiStatusPanel panel;
+ EmojiStatusPanel panel;
std::unique_ptr emoji;
DocumentId emojiId = 0;
uint8 index = 0;
};
const auto state = right->lifetime().make_state();
- state->panel.backgroundEmojiChosen(
- ) | rpl::start_with_next(emojiIdChosen, raw->lifetime());
+ state->panel.someCustomChosen(
+ ) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
+ emojiIdChosen(chosen.id);
+ }, raw->lifetime());
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
state->index = index;
@@ -748,7 +777,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
state->panel.show({
.controller = controller,
.button = right,
- .currentBackgroundEmojiId = state->emojiId,
+ .ensureAddedEmojiId = state->emojiId,
.customTextColor = customTextColor,
.backgroundEmojiMode = true,
});
@@ -758,6 +787,108 @@ int ColorSelector::resizeGetHeight(int newWidth) {
return result;
}
+[[nodiscard]] object_ptr CreateEmojiStatusButton(
+ not_null parent,
+ std::shared_ptr show,
+ rpl::producer statusIdValue,
+ Fn statusIdChosen) {
+ const auto &basicSt = st::settingsButtonNoIcon;
+ const auto ratio = style::DevicePixelRatio();
+ const auto added = st::normalFont->spacew;
+ const auto emojiSize = Data::FrameSizeFromTag({}) / ratio;
+ const auto noneWidth = added
+ + st::normalFont->width(tr::lng_settings_color_emoji_off(tr::now));
+ const auto emojiWidth = added + emojiSize;
+ const auto rightPadding = std::max(noneWidth, emojiWidth)
+ + basicSt.padding.right();
+ const auto st = parent->lifetime().make_state(
+ basicSt);
+ st->padding.setRight(rightPadding);
+ auto result = object_ptr(
+ parent,
+ tr::lng_edit_channel_status(),
+ *st);
+ const auto raw = result.data();
+
+ const auto right = Ui::CreateChild(raw);
+ right->show();
+
+ using namespace Info::Profile;
+ struct State {
+ EmojiStatusPanel panel;
+ std::unique_ptr emoji;
+ DocumentId statusId = 0;
+ };
+ const auto state = right->lifetime().make_state();
+ state->panel.someCustomChosen(
+ ) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
+ statusIdChosen(chosen.id, chosen.until);
+ }, raw->lifetime());
+
+ const auto session = &show->session();
+ std::move(statusIdValue) | rpl::start_with_next([=](DocumentId id) {
+ state->statusId = id;
+ state->emoji = id
+ ? session->data().customEmojiManager().create(
+ id,
+ [=] { right->update(); })
+ : nullptr;
+ right->resize(
+ (id ? emojiWidth : noneWidth) + added,
+ right->height());
+ right->update();
+ }, right->lifetime());
+
+ rpl::combine(
+ raw->sizeValue(),
+ right->widthValue()
+ ) | rpl::start_with_next([=](QSize outer, int width) {
+ right->resize(width, outer.height());
+ const auto skip = st::settingsButton.padding.right();
+ right->moveToRight(skip - added, 0, outer.width());
+ }, right->lifetime());
+
+ right->paintRequest(
+ ) | rpl::start_with_next([=] {
+ if (state->panel.paintBadgeFrame(right)) {
+ return;
+ }
+ auto p = QPainter(right);
+ const auto height = right->height();
+ if (state->emoji) {
+ state->emoji->paint(p, {
+ .textColor = anim::color(
+ st::stickerPanPremium1,
+ st::stickerPanPremium2,
+ 0.5),
+ .position = QPoint(added, (height - emojiSize) / 2),
+ });
+ } else {
+ const auto &font = st::normalFont;
+ p.setFont(font);
+ p.setPen(st::windowActiveTextFg);
+ p.drawText(
+ QPoint(added, (height - font->height) / 2 + font->ascent),
+ tr::lng_settings_color_emoji_off(tr::now));
+ }
+ }, right->lifetime());
+
+ raw->setClickedCallback([=] {
+ const auto controller = show->resolveWindow(
+ ChatHelpers::WindowUsage::PremiumPromo);
+ if (controller) {
+ state->panel.show({
+ .controller = controller,
+ .button = right,
+ .ensureAddedEmojiId = state->statusId,
+ .channelStatusMode = true,
+ });
+ }
+ });
+
+ return result;
+}
+
} // namespace
void EditPeerColorBox(
@@ -772,12 +903,16 @@ void EditPeerColorBox(
struct State {
rpl::variable index;
rpl::variable emojiId;
+ rpl::variable statusId;
+ TimeId statusUntil = 0;
+ bool statusChanged = false;
bool changing = false;
bool applying = false;
};
const auto state = box->lifetime().make_state();
state->index = peer->colorIndex();
state->emojiId = peer->backgroundEmojiId();
+ state->statusId = peer->emojiStatusId();
box->addRow(object_ptr(
box,
@@ -820,14 +955,61 @@ void EditPeerColorBox(
? tr::lng_settings_color_emoji_about()
: tr::lng_settings_color_emoji_about_channel());
+ if (const auto channel = peer->asChannel()) {
+ Ui::AddSkip(container, st::settingsColorSampleSkip);
+ container->add(object_ptr(
+ container,
+ tr::lng_edit_channel_wallpaper(),
+ st::settingsButtonNoIcon)
+ )->setClickedCallback([=] {
+ const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
+ if (const auto strong = show->resolveWindow(usage)) {
+ show->show(Box(strong, channel));
+ }
+ });
+
+ Ui::AddSkip(container, st::settingsColorSampleSkip);
+ Ui::AddDividerText(
+ container,
+ tr::lng_edit_channel_wallpaper_about());
+
+ // Preload exceptions list.
+ const auto peerPhoto = &channel->session().api().peerPhoto();
+ [[maybe_unused]] auto list = peerPhoto->emojiListValue(
+ Api::PeerPhoto::EmojiListType::NoChannelStatus
+ );
+
+ const auto statuses = &channel->owner().emojiStatuses();
+ statuses->refreshChannelDefault();
+ statuses->refreshChannelColored();
+
+ Ui::AddSkip(container, st::settingsColorSampleSkip);
+ container->add(CreateEmojiStatusButton(
+ container,
+ show,
+ state->statusId.value(),
+ [=](DocumentId id, TimeId until) {
+ state->statusId = id;
+ state->statusUntil = until;
+ state->statusChanged = true;
+ }));
+
+ Ui::AddSkip(container, st::settingsColorSampleSkip);
+ Ui::AddDividerText(container, tr::lng_edit_channel_status_about());
+ }
+
box->addButton(tr::lng_settings_apply(), [=] {
if (state->applying) {
return;
}
state->applying = true;
- const auto index = state->index.current();
- const auto emojiId = state->emojiId.current();
- Apply(show, peer, index, emojiId, crl::guard(box, [=] {
+ Apply(show, peer, {
+ state->index.current(),
+ state->emojiId.current(),
+ state->statusId.current(),
+ state->statusUntil,
+ state->statusChanged,
+ }, crl::guard(box, [=] {
box->closeBox();
}), crl::guard(box, [=] {
state->applying = false;
@@ -842,11 +1024,12 @@ void AddPeerColorButton(
not_null container,
std::shared_ptr show,
not_null peer) {
+ auto label = peer->isSelf()
+ ? tr::lng_settings_theme_name_color()
+ : tr::lng_edit_channel_color();
const auto button = AddButtonWithIcon(
container,
- (peer->isSelf()
- ? tr::lng_settings_theme_name_color()
- : tr::lng_edit_channel_color()),
+ rpl::duplicate(label),
st::settingsColorButton,
{ &st::menuIconChangeColors });
@@ -873,7 +1056,7 @@ void AddPeerColorButton(
rpl::combine(
button->widthValue(),
- tr::lng_settings_theme_name_color(),
+ rpl::duplicate(label),
rpl::duplicate(colorIndexValue)
) | rpl::start_with_next([=](
int width,
@@ -920,3 +1103,39 @@ void AddPeerColorButton(
show->show(Box(EditPeerColorBox, show, peer, style, theme));
});
}
+
+void CheckBoostLevel(
+ std::shared_ptr show,
+ not_null peer,
+ Fn(int level)> askMore,
+ Fn cancel) {
+ peer->session().api().request(MTPpremium_GetBoostsStatus(
+ peer->input
+ )).done([=](const MTPpremium_BoostsStatus &result) {
+ const auto &data = result.data();
+ if (const auto channel = peer->asChannel()) {
+ channel->updateLevelHint(data.vlevel().v);
+ }
+ const auto reason = askMore(data.vlevel().v);
+ if (!reason) {
+ return;
+ }
+ const auto openStatistics = [=] {
+ if (const auto controller = show->resolveWindow(
+ ChatHelpers::WindowUsage::PremiumPromo)) {
+ controller->showSection(Info::Boosts::Make(peer));
+ }
+ };
+ auto counters = ParseBoostCounters(result);
+ counters.mine = 0; // Don't show current level as just-reached.
+ show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
+ .link = qs(data.vboost_url()),
+ .boost = counters,
+ .reason = *reason,
+ }, openStatistics, nullptr));
+ cancel();
+ }).fail([=](const MTP::Error &error) {
+ show->showToast(error.type());
+ cancel();
+ }).send();
+}
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.h
index 68aecaab2..6b7284449 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.h
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.h
@@ -16,6 +16,7 @@ class GenericBox;
class ChatStyle;
class ChatTheme;
class VerticalLayout;
+struct AskBoostReason;
} // namespace Ui
void EditPeerColorBox(
@@ -29,3 +30,9 @@ void AddPeerColorButton(
not_null container,
std::shared_ptr show,
not_null peer);
+
+void CheckBoostLevel(
+ std::shared_ptr show,
+ not_null peer,
+ Fn(int level)> askMore,
+ Fn cancel);
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
index 7b1010272..fba0c7303 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
@@ -1294,6 +1294,9 @@ void Controller::editReactions() {
_peer->input
)).done([=](const MTPpremium_BoostsStatus &result) {
_controls.levelRequested = false;
+ if (const auto channel = _peer->asChannel()) {
+ channel->updateLevelHint(result.data().vlevel().v);
+ }
const auto link = qs(result.data().vboost_url());
const auto weak = base::make_weak(_navigation->parentController());
auto counters = ParseBoostCounters(result);
diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp
index 6cdb9a10a..92240b424 100644
--- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp
+++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp
@@ -66,6 +66,7 @@ struct Descriptor {
bool fromSettings = false;
Fn hiddenCallback;
Fn)> shownCallback;
+ bool hideSubscriptionButton = false;
};
bool operator==(const Descriptor &a, const Descriptor &b) {
@@ -1025,7 +1026,8 @@ void PreviewBox(
state->preload();
}
};
- if (descriptor.fromSettings && show->session().premium()) {
+ if ((descriptor.fromSettings && show->session().premium())
+ || descriptor.hideSubscriptionButton) {
box->setShowFinishedCallback(showFinished);
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
} else {
@@ -1151,7 +1153,7 @@ void DecorateListPromoBox(
box->boxClosing() | rpl::start_with_next(hidden, box->lifetime());
}
- if (session->premium()) {
+ if (session->premium() || descriptor.hideSubscriptionButton) {
box->addButton(tr::lng_close(), [=] {
box->closeBox();
});
@@ -1286,10 +1288,12 @@ void ShowPremiumPreviewBox(
void ShowPremiumPreviewBox(
std::shared_ptr show,
PremiumPreview section,
- Fn)> shown) {
+ Fn)> shown,
+ bool hideSubscriptionButton) {
Show(std::move(show), Descriptor{
.section = section,
.shownCallback = std::move(shown),
+ .hideSubscriptionButton = hideSubscriptionButton,
});
}
diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.h b/Telegram/SourceFiles/boxes/premium_preview_box.h
index 35c055717..60c5699ca 100644
--- a/Telegram/SourceFiles/boxes/premium_preview_box.h
+++ b/Telegram/SourceFiles/boxes/premium_preview_box.h
@@ -73,7 +73,8 @@ void ShowPremiumPreviewBox(
void ShowPremiumPreviewBox(
std::shared_ptr show,
PremiumPreview section,
- Fn)> shown = nullptr);
+ Fn)> shown = nullptr,
+ bool hideSubscriptionButton = false);
void ShowPremiumPreviewToBuy(
not_null controller,
diff --git a/Telegram/SourceFiles/boxes/sessions_box.cpp b/Telegram/SourceFiles/boxes/sessions_box.cpp
index ec428042a..4328fc443 100644
--- a/Telegram/SourceFiles/boxes/sessions_box.cpp
+++ b/Telegram/SourceFiles/boxes/sessions_box.cpp
@@ -211,7 +211,7 @@ void RenameBox(not_null box) {
return Type::Other;
} else if (const auto browser = detectBrowser()) {
return *browser;
- } else if (device.contains("iphone")) {
+ } else if (device.contains("iphone")) {
return Type::iPhone;
} else if (device.contains("ipad")) {
return Type::iPad;
@@ -221,9 +221,9 @@ void RenameBox(not_null box) {
return *desktop;
} else if (platform.contains("android") || system.contains("android")) {
return Type::Android;
- } else if (platform.contains("ios") || system.contains("ios")) {
+ } else if (platform.contains("ios") || system.contains("ios")) {
return Type::iPhone;
- }
+ }
return Type::Other;
}
diff --git a/Telegram/SourceFiles/boxes/translate_box.cpp b/Telegram/SourceFiles/boxes/translate_box.cpp
index e0a760350..39380bd3e 100644
--- a/Telegram/SourceFiles/boxes/translate_box.cpp
+++ b/Telegram/SourceFiles/boxes/translate_box.cpp
@@ -305,7 +305,7 @@ bool SkipTranslate(TextWithEntities textWithEntities) {
const auto skip = Core::App().settings().skipTranslationLanguages();
return result.known() && ranges::contains(skip, result);
#else
- return false;
+ return false;
#endif
}
diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp
index 9ae411d24..2d27f1db4 100644
--- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp
@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "chat_helpers/emoji_list_widget.h"
+#include "api/api_peer_photo.h"
+#include "apiwrap.h"
#include "base/unixtime.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/tabbed_search.h"
@@ -477,10 +479,23 @@ EmojiListWidget::EmojiListWidget(
setAttribute(Qt::WA_OpaquePaintEvent);
}
- if (_mode != Mode::RecentReactions && _mode != Mode::BackgroundEmoji) {
+ if (_mode != Mode::RecentReactions
+ && _mode != Mode::BackgroundEmoji
+ && _mode != Mode::ChannelStatus) {
setupSearch();
}
+ if (_mode == Mode::ChannelStatus) {
+ session().api().peerPhoto().emojiListValue(
+ Api::PeerPhoto::EmojiListType::NoChannelStatus
+ ) | rpl::start_with_next([=](const std::vector &list) {
+ _restrictedCustomList = { begin(list), end(list) };
+ if (!_custom.empty()) {
+ refreshCustom();
+ }
+ }, lifetime());
+ }
+
_customSingleSize = Data::FrameSizeFromTag(
Data::CustomEmojiManager::SizeTag::Large
) / style::DevicePixelRatio();
@@ -1034,7 +1049,9 @@ void EmojiListWidget::fillRecentFrom(const std::vector &list) {
if (!id && _mode == Mode::EmojiStatus) {
const auto star = QString::fromUtf8("\xe2\xad\x90\xef\xb8\x8f");
_recent.push_back({ .id = { Ui::Emoji::Find(star) } });
- } else if (!id && _mode == Mode::BackgroundEmoji) {
+ } else if (!id
+ && (_mode == Mode::BackgroundEmoji
+ || _mode == Mode::ChannelStatus)) {
const auto fakeId = DocumentId(5246772116543512028ULL);
const auto no = QString::fromUtf8("\xe2\x9b\x94\xef\xb8\x8f");
_recent.push_back({
@@ -1070,7 +1087,7 @@ base::unique_qptr EmojiListWidget::fillContextMenu(
: st::defaultPopupMenu));
if (_mode == Mode::Full) {
fillRecentMenu(menu, section, index);
- } else if (_mode == Mode::EmojiStatus) {
+ } else if (_mode == Mode::EmojiStatus || _mode == Mode::ChannelStatus) {
fillEmojiStatusMenu(menu, section, index);
}
if (menu->empty()) {
@@ -1205,7 +1222,7 @@ void EmojiListWidget::validateEmojiPaintContext(
auto value = Ui::Text::CustomEmojiPaintContext{
.textColor = (_customTextColor
? _customTextColor()
- : (_mode == Mode::EmojiStatus)
+ : (_mode == Mode::EmojiStatus || _mode == Mode::ChannelStatus)
? anim::color(
st::stickerPanPremium1,
st::stickerPanPremium2,
@@ -1402,6 +1419,10 @@ void EmojiListWidget::drawRecent(
_emojiPaintContext->position = position
+ _innerPosition
+ _customPosition;
+ if (_mode == Mode::ChannelStatus) {
+ _emojiPaintContext->internal.forceFirstFrame
+ = (recent.id == _recent.front().id);
+ }
custom->paint(p, *_emojiPaintContext);
} else if (const auto emoji = std::get_if(&recent.id.data)) {
if (_mode == Mode::EmojiStatus) {
@@ -1642,6 +1663,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
Settings::ShowPremium(resolved, u"infinite_reactions"_q);
break;
case Mode::EmojiStatus:
+ case Mode::ChannelStatus:
Settings::ShowPremium(resolved, u"emoji_status"_q);
break;
case Mode::TopicIcon:
@@ -2018,8 +2040,9 @@ void EmojiListWidget::refreshCustom() {
auto it = sets.find(setId);
if (it == sets.cend()
|| it->second->stickers.isEmpty()
- || (_mode == Mode::BackgroundEmoji
- && !it->second->textColor())) {
+ || (_mode == Mode::BackgroundEmoji && !it->second->textColor())
+ || (_mode == Mode::ChannelStatus
+ && !it->second->channelStatus())) {
return;
}
const auto canRemove = !!(it->second->flags
@@ -2070,7 +2093,9 @@ void EmojiListWidget::refreshCustom() {
auto set = std::vector();
set.reserve(list.size());
for (const auto document : list) {
- if (const auto sticker = document->sticker()) {
+ if (_restrictedCustomList.contains(document->id)) {
+ continue;
+ } else if (const auto sticker = document->sticker()) {
set.push_back({
.custom = resolveCustomEmoji(document, setId),
.document = document,
diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h
index 5735d7a55..c2a8ef3ed 100644
--- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h
+++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h
@@ -71,6 +71,7 @@ enum class EmojiListMode {
Full,
TopicIcon,
EmojiStatus,
+ ChannelStatus,
FullReactions,
RecentReactions,
UserpicBuilder,
@@ -384,6 +385,7 @@ private:
bool _grabbingChosen = false;
QVector _emoji[kEmojiSectionCount];
std::vector _custom;
+ base::flat_set _restrictedCustomList;
base::flat_map _customEmoji;
base::flat_map<
DocumentId,
diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp
index dcebbfdc4..69aaddee1 100644
--- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp
+++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp
@@ -26,6 +26,7 @@ const QString DicePacks::kDartString = QString::fromUtf8("\xF0\x9F\x8E\xAF");
const QString DicePacks::kSlotString = QString::fromUtf8("\xF0\x9F\x8E\xB0");
const QString DicePacks::kFballString = QString::fromUtf8("\xE2\x9A\xBD");
const QString DicePacks::kBballString = QString::fromUtf8("\xF0\x9F\x8F\x80");
+const QString DicePacks::kPartyPopper = QString::fromUtf8("\xf0\x9f\x8e\x89");
DicePack::DicePack(not_null session, const QString &emoji)
: _session(session)
@@ -35,7 +36,7 @@ DicePack::DicePack(not_null session, const QString &emoji)
DicePack::~DicePack() = default;
DocumentData *DicePack::lookup(int value) {
- if (!_requestId) {
+ if (!_requestId && _emoji != DicePacks::kPartyPopper) {
load();
}
tryGenerateLocalZero();
@@ -117,6 +118,8 @@ void DicePack::tryGenerateLocalZero() {
generateLocal(8, u"slot_0_idle"_q);
generateLocal(14, u"slot_1_idle"_q);
generateLocal(20, u"slot_2_idle"_q);
+ } else if (_emoji == DicePacks::kPartyPopper) {
+ generateLocal(0, u"winners"_q);
}
}
diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h
index 53564363a..3a1c49f46 100644
--- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h
+++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h
@@ -44,6 +44,7 @@ public:
static const QString kSlotString;
static const QString kFballString;
static const QString kBballString;
+ static const QString kPartyPopper;
[[nodiscard]] static bool IsSlot(const QString &emoji) {
return (emoji == kSlotString);
diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp
index 9d6f669cd..6fc2c555a 100644
--- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp
+++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp
@@ -332,6 +332,7 @@ TabbedSelector::TabbedSelector(
: TabbedSelector(parent, {
.show = std::move(show),
.st = ((mode == Mode::EmojiStatus
+ || mode == Mode::ChannelStatus
|| mode == Mode::BackgroundEmoji
|| mode == Mode::FullReactions)
? st::statusEmojiPan
@@ -521,6 +522,8 @@ TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) {
.show = _show,
.mode = (_mode == Mode::EmojiStatus
? EmojiMode::EmojiStatus
+ : _mode == Mode::ChannelStatus
+ ? EmojiMode::ChannelStatus
: _mode == Mode::BackgroundEmoji
? EmojiMode::BackgroundEmoji
: _mode == Mode::FullReactions
diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h
index c70118df2..3b2f45d5d 100644
--- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h
+++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h
@@ -81,6 +81,7 @@ enum class TabbedSelectorMode {
EmojiOnly,
MediaEditor,
EmojiStatus,
+ ChannelStatus,
BackgroundEmoji,
FullReactions,
RecentReactions,
diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp
index 4af76fa1d..cc8ab9064 100644
--- a/Telegram/SourceFiles/core/application.cpp
+++ b/Telegram/SourceFiles/core/application.cpp
@@ -428,11 +428,12 @@ void Application::showOpenGLCrashNotification() {
Local::writeSettings();
Restart();
};
- const auto keepDisabled = [=] {
+ const auto keepDisabled = [=](Fn close) {
Ui::GL::ForceDisable(true);
Ui::GL::CrashCheckFinish();
settings().setDisableOpenGL(true);
Local::writeSettings();
+ close();
};
_lastActivePrimaryWindow->show(Ui::MakeConfirmBox({
.text = ""
diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp
index 2d5f88283..582a695fe 100644
--- a/Telegram/SourceFiles/core/local_url_handlers.cpp
+++ b/Telegram/SourceFiles/core/local_url_handlers.cpp
@@ -344,9 +344,19 @@ bool ResolveUsernameOrPhone(
qthelp::UrlParamNameTransform::ToLower);
const auto domainParam = params.value(u"domain"_q);
const auto appnameParam = params.value(u"appname"_q);
+ const auto myContext = context.value();
if (domainParam == u"giftcode"_q && !appnameParam.isEmpty()) {
- ResolveGiftCode(controller, appnameParam);
+ const auto itemId = myContext.itemId;
+ const auto item = controller->session().data().message(itemId);
+ const auto fromId = item ? item->from()->id : PeerId();
+ const auto selfId = controller->session().userPeerId();
+ const auto toId = !item
+ ? PeerId()
+ : (fromId == selfId)
+ ? item->history()->peer->id
+ : selfId;
+ ResolveGiftCode(controller, appnameParam, fromId, toId);
return true;
}
@@ -413,7 +423,6 @@ bool ResolveUsernameOrPhone(
startToken = params.value(u"startapp"_q);
}
}
- const auto myContext = context.value();
controller->window().activate();
controller->showPeerByLink(Window::PeerByLinkInfo{
.usernameOrId = domain,
@@ -732,7 +741,8 @@ void ExportTestChatTheme(
MTP_int(color(bg.size() > 2 ? bg[2] : Qt::black)),
MTP_int(color(bg.size() > 3 ? bg[3] : Qt::black)),
MTP_int(fields.paper->patternIntensity()),
- MTP_int(0)));
+ MTP_int(0), // rotation
+ MTPstring())); // emoticon
};
const auto light = inputSettings(Data::CloudThemeType::Light);
if (!light) {
diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp
index fc4d61983..0b92376cd 100644
--- a/Telegram/SourceFiles/core/sandbox.cpp
+++ b/Telegram/SourceFiles/core/sandbox.cpp
@@ -40,10 +40,12 @@ namespace {
base::options::toggle OptionForceWaylandFractionalScaling({
.id = kOptionForceWaylandFractionalScaling,
- .name = "Force enable fractional-scale-v1",
- .description = "Enable fractional-scale-v1 on Wayland without "
+ .name = "Enable xdg-output fractional scaling",
+ .description = "Enable xdg-output based fractional scaling on Wayland. "
+ "This works without fractional-scale-v1 and without "
"precise High DPI scaling. "
"Requires Qt with Desktop App Toolkit patches.",
+ .defaultValue = true,
.scope = [] {
#ifdef DESKTOP_APP_QT_PATCHED
return Platform::IsWayland();
@@ -251,12 +253,7 @@ void Sandbox::setupScreenScale() {
logEnv("QT_USE_PHYSICAL_DPI");
logEnv("QT_FONT_DPI");
- // Like Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor.
- // Round up for .75 and higher. This favors "small UI" over "large UI".
- const auto roundedRatio = ((ratio - qFloor(ratio)) < 0.75)
- ? qFloor(ratio)
- : qCeil(ratio);
- const auto useRatio = std::clamp(roundedRatio, 1, 3);
+ const auto useRatio = std::clamp(qCeil(ratio), 1, 3);
style::SetDevicePixelRatio(useRatio);
const auto screen = Sandbox::primaryScreen();
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index 5f1536b0b..112f7da3f 100644
--- a/Telegram/SourceFiles/core/version.h
+++ b/Telegram/SourceFiles/core/version.h
@@ -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 = 4012002;
-constexpr auto AppVersionStr = "4.12.2";
+constexpr auto AppVersion = 4013001;
+constexpr auto AppVersionStr = "4.13.1";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp
index 6f25e02c8..fecbe6287 100644
--- a/Telegram/SourceFiles/data/data_channel.cpp
+++ b/Telegram/SourceFiles/data/data_channel.cpp
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_histories.h"
#include "data/data_group_call.h"
#include "data/data_message_reactions.h"
+#include "data/data_wall_paper.h"
#include "data/notify/data_notify_settings.h"
#include "main/main_session.h"
#include "main/session/send_as_peers.h"
@@ -948,6 +949,14 @@ void ChannelData::processTopics(const MTPVector &topics) {
}
}
+int ChannelData::levelHint() const {
+ return _levelHint;
+}
+
+void ChannelData::updateLevelHint(int levelHint) {
+ _levelHint = levelHint;
+}
+
namespace Data {
void ApplyMigration(
@@ -1142,6 +1151,13 @@ void ApplyChannelUpdate(
session->sendAsPeers().setChosen(channel, PeerId());
}
+ if (const auto paper = update.vwallpaper()) {
+ channel->setWallPaper(
+ Data::WallPaper::Create(&channel->session(), *paper));
+ } else {
+ channel->setWallPaper({});
+ }
+
// For clearUpTill() call.
channel->owner().sendHistoryChangeNotifications();
}
diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h
index 0d1faf269..d55e8a4e6 100644
--- a/Telegram/SourceFiles/data/data_channel.h
+++ b/Telegram/SourceFiles/data/data_channel.h
@@ -463,6 +463,9 @@ public:
void processTopics(const MTPVector &topics);
+ [[nodiscard]] int levelHint() const;
+ void updateLevelHint(int levelHint);
+
// Still public data members.
uint64 access = 0;
@@ -497,6 +500,7 @@ private:
int _restrictedCount = 0;
int _kickedCount = 0;
int _pendingRequestsCount = 0;
+ int _levelHint = 0;
std::vector _recentRequesters;
MsgId _availableMinId = 0;
diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.cpp b/Telegram/SourceFiles/data/data_emoji_statuses.cpp
index a394edc0c..dd1ad583b 100644
--- a/Telegram/SourceFiles/data/data_emoji_statuses.cpp
+++ b/Telegram/SourceFiles/data/data_emoji_statuses.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_emoji_statuses.h"
#include "main/main_session.h"
+#include "data/data_channel.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "data/data_document.h"
@@ -53,6 +54,7 @@ EmojiStatuses::EmojiStatuses(not_null owner)
kRefreshDefaultListEach
) | rpl::start_with_next([=] {
refreshDefault();
+ refreshChannelDefault();
}, _lifetime);
}
@@ -74,6 +76,14 @@ void EmojiStatuses::refreshColored() {
requestColored();
}
+void EmojiStatuses::refreshChannelDefault() {
+ requestChannelDefault();
+}
+
+void EmojiStatuses::refreshChannelColored() {
+ requestChannelColored();
+}
+
void EmojiStatuses::refreshRecentDelayed() {
if (_recentRequestId || _recentRequestScheduled) {
return;
@@ -91,6 +101,8 @@ const std::vector &EmojiStatuses::list(Type type) const {
case Type::Recent: return _recent;
case Type::Default: return _default;
case Type::Colored: return _colored;
+ case Type::ChannelDefault: return _channelDefault;
+ case Type::ChannelColored: return _channelColored;
}
Unexpected("Type in EmojiStatuses::list.");
}
@@ -103,20 +115,24 @@ rpl::producer<> EmojiStatuses::defaultUpdates() const {
return _defaultUpdated.events();
}
+rpl::producer<> EmojiStatuses::channelDefaultUpdates() const {
+ return _channelDefaultUpdated.events();
+}
+
void EmojiStatuses::registerAutomaticClear(
- not_null user,
+ not_null peer,
TimeId until) {
if (!until) {
- _clearing.remove(user);
+ _clearing.remove(peer);
if (_clearing.empty()) {
_clearingTimer.cancel();
}
- } else if (auto &already = _clearing[user]; already != until) {
+ } else if (auto &already = _clearing[peer]; already != until) {
already = until;
const auto i = ranges::min_element(_clearing, {}, [](auto &&pair) {
return pair.second;
});
- if (i->first == user) {
+ if (i->first == peer) {
const auto now = base::unixtime::now();
if (now < until) {
processClearingIn(until - now);
@@ -266,7 +282,7 @@ void EmojiStatuses::requestDefault() {
}
auto &api = _owner->session().api();
_defaultRequestId = api.request(MTPaccount_GetDefaultEmojiStatuses(
- MTP_long(_recentHash)
+ MTP_long(_defaultHash)
)).done([=](const MTPaccount_EmojiStatuses &result) {
_defaultRequestId = 0;
result.match([&](const MTPDaccount_emojiStatuses &data) {
@@ -299,6 +315,45 @@ void EmojiStatuses::requestColored() {
}).send();
}
+void EmojiStatuses::requestChannelDefault() {
+ if (_channelDefaultRequestId) {
+ return;
+ }
+ auto &api = _owner->session().api();
+ _channelDefaultRequestId = api.request(MTPaccount_GetDefaultEmojiStatuses(
+ MTP_long(_channelDefaultHash)
+ )).done([=](const MTPaccount_EmojiStatuses &result) {
+ _channelDefaultRequestId = 0;
+ result.match([&](const MTPDaccount_emojiStatuses &data) {
+ updateChannelDefault(data);
+ }, [&](const MTPDaccount_emojiStatusesNotModified &) {
+ });
+ }).fail([=] {
+ _channelDefaultRequestId = 0;
+ _channelDefaultHash = 0;
+ }).send();
+}
+
+void EmojiStatuses::requestChannelColored() {
+ if (_channelColoredRequestId) {
+ return;
+ }
+ auto &api = _owner->session().api();
+ _channelColoredRequestId = api.request(MTPmessages_GetStickerSet(
+ MTP_inputStickerSetEmojiChannelDefaultStatuses(),
+ MTP_int(0) // hash
+ )).done([=](const MTPmessages_StickerSet &result) {
+ _channelColoredRequestId = 0;
+ result.match([&](const MTPDmessages_stickerSet &data) {
+ updateChannelColored(data);
+ }, [](const MTPDmessages_stickerSetNotModified &) {
+ LOG(("API Error: Unexpected messages.stickerSetNotModified."));
+ });
+ }).fail([=] {
+ _channelColoredRequestId = 0;
+ }).send();
+}
+
void EmojiStatuses::updateRecent(const MTPDaccount_emojiStatuses &data) {
_recentHash = data.vhash().v;
_recent = ListFromMTP(data);
@@ -321,27 +376,57 @@ void EmojiStatuses::updateColored(const MTPDmessages_stickerSet &data) {
_coloredUpdated.fire({});
}
-void EmojiStatuses::set(DocumentId id, TimeId until) {
- auto &api = _owner->session().api();
- if (_sentRequestId) {
- api.request(base::take(_sentRequestId)).cancel();
+void EmojiStatuses::updateChannelDefault(
+ const MTPDaccount_emojiStatuses &data) {
+ _channelDefaultHash = data.vhash().v;
+ _channelDefault = ListFromMTP(data);
+ _channelDefaultUpdated.fire({});
+}
+
+void EmojiStatuses::updateChannelColored(
+ const MTPDmessages_stickerSet &data) {
+ const auto &list = data.vdocuments().v;
+ _channelColored.clear();
+ _channelColored.reserve(list.size());
+ for (const auto &sticker : data.vdocuments().v) {
+ _channelColored.push_back(_owner->processDocument(sticker)->id);
}
- _owner->session().user()->setEmojiStatus(id, until);
- _sentRequestId = api.request(MTPaccount_UpdateEmojiStatus(
- !id
+ _channelColoredUpdated.fire({});
+}
+
+void EmojiStatuses::set(DocumentId id, TimeId until) {
+ set(_owner->session().user(), id, until);
+}
+
+void EmojiStatuses::set(
+ not_null peer,
+ DocumentId id,
+ TimeId until) {
+ auto &api = _owner->session().api();
+ auto &requestId = _sentRequests[peer];
+ if (requestId) {
+ api.request(base::take(requestId)).cancel();
+ }
+ peer->setEmojiStatus(id, until);
+ const auto send = [&](auto &&request) {
+ requestId = api.request(
+ std::move(request)
+ ).done([=] {
+ _sentRequests.remove(peer);
+ }).fail([=] {
+ _sentRequests.remove(peer);
+ }).send();
+ };
+ const auto status = !id
? MTP_emojiStatusEmpty()
: !until
? MTP_emojiStatus(MTP_long(id))
- : MTP_emojiStatusUntil(MTP_long(id), MTP_int(until))
- )).done([=] {
- _sentRequestId = 0;
- }).fail([=] {
- _sentRequestId = 0;
- }).send();
-}
-
-bool EmojiStatuses::setting() const {
- return _sentRequestId != 0;;
+ : MTP_emojiStatusUntil(MTP_long(id), MTP_int(until));
+ if (peer->isSelf()) {
+ send(MTPaccount_UpdateEmojiStatus(status));
+ } else if (const auto channel = peer->asChannel()) {
+ send(MTPchannels_UpdateEmojiStatus(channel->inputChannel, status));
+ }
}
EmojiStatusData ParseEmojiStatus(const MTPEmojiStatus &status) {
diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.h b/Telegram/SourceFiles/data/data_emoji_statuses.h
index 29bd8491c..5501eb8bc 100644
--- a/Telegram/SourceFiles/data/data_emoji_statuses.h
+++ b/Telegram/SourceFiles/data/data_emoji_statuses.h
@@ -36,22 +36,26 @@ public:
void refreshRecentDelayed();
void refreshDefault();
void refreshColored();
+ void refreshChannelDefault();
+ void refreshChannelColored();
enum class Type {
Recent,
Default,
Colored,
+ ChannelDefault,
+ ChannelColored,
};
[[nodiscard]] const std::vector &list(Type type) const;
[[nodiscard]] rpl::producer<> recentUpdates() const;
[[nodiscard]] rpl::producer<> defaultUpdates() const;
- [[nodiscard]] rpl::producer<> coloredUpdates() const;
+ [[nodiscard]] rpl::producer<> channelDefaultUpdates() const;
void set(DocumentId id, TimeId until = 0);
- [[nodiscard]] bool setting() const;
+ void set(not_null peer, DocumentId id, TimeId until = 0);
- void registerAutomaticClear(not_null user, TimeId until);
+ void registerAutomaticClear(not_null peer, TimeId until);
using Groups = std::vector