mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-14 13:17:08 +02:00
Merge 4.13.1
This commit is contained in:
commit
421aa31d59
177 changed files with 5409 additions and 1901 deletions
5
.github/workflows/docker.yml
vendored
5
.github/workflows/docker.yml
vendored
|
@ -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
|
||||
|
|
24
.github/workflows/linux.yml
vendored
24
.github/workflows/linux.yml
vendored
|
@ -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"
|
||||
|
|
BIN
Telegram/Resources/art/winners.tgs
Normal file
BIN
Telegram/Resources/art/winners.tgs
Normal file
Binary file not shown.
|
@ -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";
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<file alias="art/slot_2_idle.tgs">../../art/slot_2_idle.tgs</file>
|
||||
<file alias="art/slot_back.tgs">../../art/slot_back.tgs</file>
|
||||
<file alias="art/slot_pull.tgs">../../art/slot_pull.tgs</file>
|
||||
<file alias="art/winners.tgs">../../art/winners.tgs</file>
|
||||
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
|
||||
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
|
||||
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="4.12.2.0" />
|
||||
Version="4.13.1.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
|
|
@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"");
|
||||
|
|
|
@ -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<PeerData*> result) {
|
||||
const auto chosen = [=](std::vector<not_null<PeerData*>> 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<PeerData*> peer) {
|
||||
return MTPInputPeer(peer->input); }))
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
peer->session().api().applyUpdates(result);
|
||||
}).send();
|
||||
|
|
|
@ -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<uint8>();
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -28,6 +28,10 @@ public:
|
|||
[[nodiscard]] auto indicesValue() const
|
||||
-> rpl::producer<Ui::ColorIndicesCompressed>;
|
||||
|
||||
[[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<std::vector<uint8>> _suggested;
|
||||
base::flat_map<uint8, int> _requiredLevels;
|
||||
rpl::event_stream<> _colorIndicesChanged;
|
||||
std::unique_ptr<Ui::ColorIndicesCompressed> _colorIndicesCurrent;
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ public:
|
|||
Profile,
|
||||
Group,
|
||||
Background,
|
||||
NoChannelStatus,
|
||||
};
|
||||
|
||||
struct UserPhoto {
|
||||
|
@ -112,6 +113,7 @@ private:
|
|||
EmojiListData _profileEmojiList;
|
||||
EmojiListData _groupEmojiList;
|
||||
EmojiListData _backgroundEmojiList;
|
||||
EmojiListData _noChannelStatusEmojiList;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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<PeerData*> peer)
|
|||
rpl::producer<rpl::no_value, QString> 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<TLOption> &result) {
|
||||
auto tlMapOptions = base::flat_map<Amount, QVector<TLOption>>();
|
||||
|
@ -420,6 +417,8 @@ const std::vector<int> &PremiumGiftCodeOptions::availablePresets() const {
|
|||
}
|
||||
|
||||
[[nodiscard]] int PremiumGiftCodeOptions::monthsFromPreset(int monthsIndex) {
|
||||
Expects(monthsIndex >= 0 && monthsIndex < _availablePresets.size());
|
||||
|
||||
return _optionsForOnePerson.months[monthsIndex];
|
||||
}
|
||||
|
||||
|
|
|
@ -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<void(Data::PublicForwardsSlice)> 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<void(Data::PublicForwardsSlice)> 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<Data::RecentPostId>;
|
||||
_requestId = 0;
|
||||
|
||||
auto nextToken = Data::PublicForwardsSlice::OffsetToken();
|
||||
const auto process = [&](const MTPVector<MTPMessage> &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<void(Data::PublicForwardsSlice)> 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<Data::RecentPostId>;
|
||||
_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<rpl::no_value, QString> 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))
|
||||
|
|
|
@ -77,13 +77,6 @@ public:
|
|||
Fn<void(Data::PublicForwardsSlice)> done);
|
||||
|
||||
private:
|
||||
void requestMessage(
|
||||
const Data::PublicForwardsSlice::OffsetToken &token,
|
||||
Fn<void(Data::PublicForwardsSlice)> done);
|
||||
void requestStory(
|
||||
const Data::PublicForwardsSlice::OffsetToken &token,
|
||||
Fn<void(Data::PublicForwardsSlice)> done);
|
||||
|
||||
const Data::RecentPostId _fullId;
|
||||
mtpRequestId _requestId = 0;
|
||||
int _lastTotal = 0;
|
||||
|
|
|
@ -225,11 +225,11 @@ void ApiWrap::setupSupportMode() {
|
|||
void ApiWrap::requestChangelog(
|
||||
const QString &sinceVersion,
|
||||
Fn<void(const MTPUpdates &result)> callback) {
|
||||
request(MTPhelp_GetAppChangelog(
|
||||
MTP_string(sinceVersion)
|
||||
)).done(
|
||||
callback
|
||||
).send();
|
||||
//request(MTPhelp_GetAppChangelog(
|
||||
// MTP_string(sinceVersion)
|
||||
//)).done(
|
||||
// callback
|
||||
//).send();
|
||||
}
|
||||
|
||||
void ApiWrap::refreshTopPromotion() {
|
||||
|
|
|
@ -129,6 +129,8 @@ private:
|
|||
int row) const;
|
||||
void validatePaperThumbnail(const Paper &paper) const;
|
||||
|
||||
[[nodiscard]] bool forChannel() const;
|
||||
|
||||
const not_null<Main::Session*> _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<Ui::SettingsButton>(
|
||||
container,
|
||||
tr::lng_settings_bg_remove(),
|
||||
st::infoBlockButton));
|
||||
object_ptr<Info::Profile::FloatingIcon>(
|
||||
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<BackgroundPreviewBox>(
|
||||
_controller,
|
||||
Data::WallPaper::FromEmojiId(theme.emoticon),
|
||||
BackgroundPreviewArgs{ _forPeer }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_controller->show(Box<BackgroundPreviewBox>(
|
||||
_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<void()> &&close) {
|
||||
|
@ -345,9 +385,16 @@ BackgroundBox::Inner::Inner(
|
|||
, _session(session)
|
||||
, _forPeer(forPeer)
|
||||
, _api(&_session->mtp())
|
||||
, _check(std::make_unique<Ui::RoundCheckbox>(st::overviewCheck, [=] { update(); })) {
|
||||
, _check(
|
||||
std::make_unique<Ui::RoundCheckbox>(
|
||||
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)
|
||||
|
|
|
@ -38,6 +38,7 @@ private:
|
|||
const Data::WallPaper &paper) const;
|
||||
void removePaper(const Data::WallPaper &paper);
|
||||
void resetForPeer();
|
||||
[[nodiscard]] bool forChannel() const;
|
||||
|
||||
void chooseFromFile();
|
||||
|
||||
|
|
|
@ -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<Main::Session*> 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<Ui::AskBoostReason>();
|
||||
}
|
||||
const auto appConfig = &_forPeer->session().account().appConfig();
|
||||
const auto defaultRequired = appConfig->get<int>(
|
||||
"channel_wallpaper_level_min",
|
||||
9);
|
||||
const auto customRequired = appConfig->get<int>(
|
||||
"channel_custom_wallpaper_level_min",
|
||||
10);
|
||||
const auto required = _paperEmojiId.isEmpty()
|
||||
? customRequired
|
||||
: defaultRequired;
|
||||
if (level >= required) {
|
||||
applyForPeer(false);
|
||||
return std::optional<Ui::AskBoostReason>();
|
||||
}
|
||||
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<Ui::FadeWrap<>>(
|
||||
this,
|
||||
object_ptr<Ui::RpWidget>(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<RoundButton>(
|
||||
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<RoundButton>(
|
||||
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<Ui::FadeWrap<>>(
|
||||
this,
|
||||
object_ptr<Ui::RpWidget>(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<RoundButton>(
|
||||
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<RoundButton>(
|
||||
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<QColor> &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,
|
||||
|
|
|
@ -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<Window::SessionController*> _controller;
|
||||
PeerData * const _forPeer = nullptr;
|
||||
bool _forPeerLevelCheck = false;
|
||||
FullMsgId _fromMessageId;
|
||||
std::unique_ptr<Ui::ChatStyle> _chatStyle;
|
||||
const not_null<History*> _serviceHistory;
|
||||
AdminLog::OwnedItem _service;
|
||||
AdminLog::OwnedItem _text1;
|
||||
AdminLog::OwnedItem _text2;
|
||||
QString _paperEmojiId;
|
||||
Data::WallPaper _paper;
|
||||
std::shared_ptr<Data::DocumentMedia> _media;
|
||||
QImage _full;
|
||||
|
|
|
@ -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<TextWithEntities> ComplexAboutLabel(
|
||||
const std::vector<not_null<UserData*>> &users,
|
||||
tr::phrase<TagUser1> phrase1,
|
||||
tr::phrase<TagUser1, TagUser2> phrase2,
|
||||
tr::phrase<TagUser1, TagUser2, TagUser3> phrase3,
|
||||
tr::phrase<lngtag_count, TagUser1, TagUser2, TagUser3> phraseMore) {
|
||||
Expects(!users.empty());
|
||||
|
||||
const auto count = users.size();
|
||||
const auto nameValue = [&](not_null<UserData*> 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<Ui::RpWidget*> CircleBadge(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const QString &text) {
|
||||
const auto widget = Ui::CreateChild<Ui::RpWidget>(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<Ui::RpWidget*> UserpicsContainer(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
std::vector<not_null<UserData*>> users) {
|
||||
Expects(!users.empty());
|
||||
|
||||
if (users.size() == 1) {
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
|
||||
parent.get(),
|
||||
users.front(),
|
||||
st::defaultUserpicButton);
|
||||
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
return userpic;
|
||||
}
|
||||
|
||||
const auto &singleSize = st::defaultUserpicButton.size;
|
||||
|
||||
const auto container = Ui::CreateChild<Ui::RpWidget>(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<Ui::RpWidget>(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<Ui::UserpicButton>(
|
||||
bg,
|
||||
users[i],
|
||||
st::premiumGiftsUserpicButton);
|
||||
userpic->moveToLeft(diff, diff);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
void GiftBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
|
@ -95,12 +238,11 @@ void GiftBox(
|
|||
+ st::defaultUserpicButton.size.height()));
|
||||
|
||||
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
|
||||
const auto stars = box->lifetime().make_state<ColoredMiniStars>(top, true);
|
||||
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
|
||||
const auto stars = box->lifetime().make_state<ColoredMiniStars>(
|
||||
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<Ui::GradientButton>::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<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
std::vector<not_null<UserData*>> users,
|
||||
not_null<Api::PremiumGiftCodeOptions*> 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<QString> buttonText;
|
||||
rpl::variable<bool> confirmButtonBusy = false;
|
||||
rpl::variable<bool> isPaymentComplete = false;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
|
||||
const auto userpicPadding = st::premiumGiftUserpicPadding;
|
||||
const auto top = box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
||||
buttonsParent,
|
||||
userpicPadding.top()
|
||||
+ userpicPadding.bottom()
|
||||
+ st::defaultUserpicButton.size.height()));
|
||||
|
||||
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
|
||||
const auto stars = box->lifetime().make_state<ColoredMiniStars>(
|
||||
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<Ui::IconButton>(
|
||||
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<Ui::FlatLabel>(
|
||||
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<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
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<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(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<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
buttonsParent,
|
||||
object_ptr<Ui::VerticalLayout>(buttonsParent)));
|
||||
const auto options = api->options(users.size());
|
||||
const auto group = std::make_shared<Ui::RadiobuttonGroup>();
|
||||
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<Ui::VerticalLayout>(box),
|
||||
{});
|
||||
auto buttonCallback = [=](PremiumPreview section) {
|
||||
stars->setPaused(true);
|
||||
const auto previewBoxShown = [=](
|
||||
not_null<Ui::BoxContent*> 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<Ui::DividerLabel>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
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<Ui::GradientButton>::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<Main::Session*> 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<PremiumGiftCodeOptions>(
|
||||
_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<Main::Session*> session,
|
||||
Fn<bool(int)> checkErrorCallback)
|
||||
: ContactsBoxController(session)
|
||||
, _checkErrorCallback(std::move(checkErrorCallback)) {
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) override {
|
||||
return !user->isSelf()
|
||||
? ContactsBoxController::createRow(user)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
void rowClicked(not_null<PeerListRow*> row) override {
|
||||
const auto checked = !row->checked();
|
||||
if (checked
|
||||
&& _checkErrorCallback
|
||||
&& _checkErrorCallback(
|
||||
delegate()->peerListSelectedRowsCount())) {
|
||||
return;
|
||||
}
|
||||
delegate()->peerListSetRowChecked(row, checked);
|
||||
}
|
||||
|
||||
private:
|
||||
const Fn<bool(int)> _checkErrorCallback;
|
||||
|
||||
};
|
||||
auto initBox = [=](not_null<PeerListBox*> peersBox) {
|
||||
const auto ignoreClose = peersBox->lifetime().make_state<bool>(0);
|
||||
|
||||
auto process = [=] {
|
||||
const auto selected = peersBox->collectSelectedRows();
|
||||
const auto users = ranges::views::all(
|
||||
selected
|
||||
) | ranges::views::transform([](not_null<PeerData*> p) {
|
||||
return p->asUser();
|
||||
}) | ranges::views::filter([](UserData *u) -> bool {
|
||||
return u;
|
||||
}) | ranges::to<std::vector<not_null<UserData*>>>();
|
||||
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>(
|
||||
&_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<PeerListBox>(
|
||||
std::move(listController),
|
||||
std::move(initBox)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
|
||||
}, _manyGiftsLifetime);
|
||||
}
|
||||
|
||||
void GiftPremiumValidator::showBox(not_null<UserData*> user) {
|
||||
if (_requestId) {
|
||||
return;
|
||||
|
@ -723,10 +1267,22 @@ void GiftCodePendingBox(
|
|||
|
||||
void ResolveGiftCode(
|
||||
not_null<Window::SessionNavigation*> 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<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
Data::Giveaway giveaway,
|
||||
std::optional<Data::GiveawayStart> start,
|
||||
std::optional<Data::GiveawayResults> 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<Window::SessionNavigation*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
MsgId messageId,
|
||||
Data::Giveaway giveaway) {
|
||||
std::optional<Data::GiveawayStart> start,
|
||||
std::optional<Data::GiveawayResults> 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(
|
||||
|
|
|
@ -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<Window::SessionController*> controller);
|
||||
|
||||
void showBox(not_null<UserData*> user);
|
||||
void showChoosePeerBox();
|
||||
void cancel();
|
||||
|
||||
private:
|
||||
|
@ -41,6 +43,8 @@ private:
|
|||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
rpl::lifetime _manyGiftsLifetime;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> GiftDurationValue(int months);
|
||||
|
@ -56,10 +60,13 @@ void GiftCodePendingBox(
|
|||
const Api::GiftCode &data);
|
||||
void ResolveGiftCode(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const QString &slug);
|
||||
const QString &slug,
|
||||
PeerId fromId = 0,
|
||||
PeerId toId = 0);
|
||||
|
||||
void ResolveGiveawayInfo(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
MsgId messageId,
|
||||
Data::Giveaway giveaway);
|
||||
std::optional<Data::GiveawayStart> start,
|
||||
std::optional<Data::GiveawayResults> results);
|
||||
|
|
|
@ -40,11 +40,14 @@ public:
|
|||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<UserData*> bot,
|
||||
RequestPeerQuery query,
|
||||
Fn<void(not_null<PeerData*>)> callback);
|
||||
Fn<void(std::vector<not_null<PeerData*>>)> callback);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
[[nodiscard]] rpl::producer<int> selectedCountValue() const;
|
||||
void submit();
|
||||
|
||||
QString savedMessagesChatStatus() const override {
|
||||
return tr::lng_saved_forward_here(tr::now);
|
||||
}
|
||||
|
@ -60,7 +63,9 @@ private:
|
|||
not_null<UserData*> _bot;
|
||||
RequestPeerQuery _query;
|
||||
base::flat_set<not_null<PeerData*>> _commonGroups;
|
||||
Fn<void(not_null<PeerData*>)> _callback;
|
||||
base::flat_set<not_null<PeerData*>> _selected;
|
||||
rpl::variable<int> _selectedCount;
|
||||
Fn<void(std::vector<not_null<PeerData*>>)> _callback;
|
||||
|
||||
};
|
||||
|
||||
|
@ -253,10 +258,10 @@ object_ptr<Ui::BoxContent> CreatePeerByQueryBox(
|
|||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<UserData*> bot,
|
||||
RequestPeerQuery query,
|
||||
Fn<void(not_null<PeerData*>)> done) {
|
||||
Fn<void(std::vector<not_null<PeerData*>>)> done) {
|
||||
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||
auto callback = [=](not_null<PeerData*> peer) {
|
||||
done(peer);
|
||||
done({ peer });
|
||||
if (const auto strong = weak->data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
|
@ -332,7 +337,7 @@ ChoosePeerBoxController::ChoosePeerBoxController(
|
|||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<UserData*> bot,
|
||||
RequestPeerQuery query,
|
||||
Fn<void(not_null<PeerData*>)> callback)
|
||||
Fn<void(std::vector<not_null<PeerData*>>)> 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<PeerListRow*> 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<PeerListRow*> row) {
|
|||
}
|
||||
}
|
||||
|
||||
rpl::producer<int> ChoosePeerBoxController::selectedCountValue() const {
|
||||
return _selectedCount.value();
|
||||
}
|
||||
|
||||
void ChoosePeerBoxController::submit() {
|
||||
const auto onstack = _callback;
|
||||
onstack(ranges::to_vector(_selected));
|
||||
}
|
||||
|
||||
auto ChoosePeerBoxController::createRow(not_null<History*> history)
|
||||
-> std::unique_ptr<Row> {
|
||||
return FilterPeerByQuery(history->peer, _query, _commonGroups)
|
||||
|
@ -474,7 +504,7 @@ void ShowChoosePeerBox(
|
|||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<UserData*> bot,
|
||||
RequestPeerQuery query,
|
||||
Fn<void(not_null<PeerData*>)> chosen) {
|
||||
Fn<void(std::vector<not_null<PeerData*>>)> 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<QPointer<Ui::BoxContent>>();
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_cancel(), [box] {
|
||||
box->closeBox();
|
||||
});
|
||||
};
|
||||
auto callback = [=, done = std::move(chosen)](not_null<PeerData*> peer) {
|
||||
done(peer);
|
||||
auto callback = [=, done = std::move(chosen)](
|
||||
std::vector<not_null<PeerData*>> peers) {
|
||||
done(std::move(peers));
|
||||
if (const auto strong = weak->data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
};
|
||||
const auto limit = query.maxQuantity;
|
||||
auto controller = std::make_unique<ChoosePeerBoxController>(
|
||||
navigation,
|
||||
bot,
|
||||
query,
|
||||
std::move(callback));
|
||||
auto initBox = [=, ptr = controller.get()](not_null<PeerListBox*> 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<PeerListBox>(
|
||||
std::make_unique<ChoosePeerBoxController>(
|
||||
navigation,
|
||||
bot,
|
||||
query,
|
||||
std::move(callback)),
|
||||
std::move(controller),
|
||||
std::move(initBox)));
|
||||
}
|
||||
|
|
|
@ -21,4 +21,4 @@ void ShowChoosePeerBox(
|
|||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<UserData*> bot,
|
||||
RequestPeerQuery query,
|
||||
Fn<void(not_null<PeerData*>)> chosen);
|
||||
Fn<void(std::vector<not_null<PeerData*>>)> chosen);
|
||||
|
|
|
@ -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<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> 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<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
uint8 colorIndex,
|
||||
DocumentId backgroundEmojiId,
|
||||
SetValues values,
|
||||
Fn<void()> close,
|
||||
Fn<void()> 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<int>(
|
||||
"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<int>(
|
||||
"channel_bg_icon_level_min",
|
||||
5)
|
||||
: 0;
|
||||
const auto statusRequired = (values.statusChanged
|
||||
&& values.statusId)
|
||||
? session->account().appConfig().get<int>(
|
||||
"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<Ui::AskBoostReason>();
|
||||
}
|
||||
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<Ui::RpWidget>(raw);
|
||||
right->show();
|
||||
|
||||
using namespace Info::Profile;
|
||||
struct State {
|
||||
Info::Profile::EmojiStatusPanel panel;
|
||||
EmojiStatusPanel panel;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
|
||||
DocumentId emojiId = 0;
|
||||
uint8 index = 0;
|
||||
};
|
||||
const auto state = right->lifetime().make_state<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<Ui::SettingsButton> CreateEmojiStatusButton(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
rpl::producer<DocumentId> statusIdValue,
|
||||
Fn<void(DocumentId,TimeId)> 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<style::SettingsButton>(
|
||||
basicSt);
|
||||
st->padding.setRight(rightPadding);
|
||||
auto result = object_ptr<Ui::SettingsButton>(
|
||||
parent,
|
||||
tr::lng_edit_channel_status(),
|
||||
*st);
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
|
||||
right->show();
|
||||
|
||||
using namespace Info::Profile;
|
||||
struct State {
|
||||
EmojiStatusPanel panel;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
|
||||
DocumentId statusId = 0;
|
||||
};
|
||||
const auto state = right->lifetime().make_state<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<uint8> index;
|
||||
rpl::variable<DocumentId> emojiId;
|
||||
rpl::variable<DocumentId> statusId;
|
||||
TimeId statusUntil = 0;
|
||||
bool statusChanged = false;
|
||||
bool changing = false;
|
||||
bool applying = false;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
state->index = peer->colorIndex();
|
||||
state->emojiId = peer->backgroundEmojiId();
|
||||
state->statusId = peer->emojiStatusId();
|
||||
|
||||
box->addRow(object_ptr<PreviewWrap>(
|
||||
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<Ui::SettingsButton>(
|
||||
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<BackgroundBox>(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<Ui::VerticalLayout*> container,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> 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<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Fn<std::optional<Ui::AskBoostReason>(int level)> askMore,
|
||||
Fn<void()> 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();
|
||||
}
|
||||
|
|
|
@ -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<Ui::VerticalLayout*> container,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
void CheckBoostLevel(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Fn<std::optional<Ui::AskBoostReason>(int level)> askMore,
|
||||
Fn<void()> cancel);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -66,6 +66,7 @@ struct Descriptor {
|
|||
bool fromSettings = false;
|
||||
Fn<void()> hiddenCallback;
|
||||
Fn<void(not_null<Ui::BoxContent*>)> 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<ChatHelpers::Show> show,
|
||||
PremiumPreview section,
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shown) {
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shown,
|
||||
bool hideSubscriptionButton) {
|
||||
Show(std::move(show), Descriptor{
|
||||
.section = section,
|
||||
.shownCallback = std::move(shown),
|
||||
.hideSubscriptionButton = hideSubscriptionButton,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,8 @@ void ShowPremiumPreviewBox(
|
|||
void ShowPremiumPreviewBox(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
PremiumPreview section,
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shown = nullptr);
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shown = nullptr,
|
||||
bool hideSubscriptionButton = false);
|
||||
|
||||
void ShowPremiumPreviewToBuy(
|
||||
not_null<Window::SessionController*> controller,
|
||||
|
|
|
@ -211,7 +211,7 @@ void RenameBox(not_null<Ui::GenericBox*> 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<Ui::GenericBox*> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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<DocumentId> &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<DocumentId> &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<Ui::PopupMenu> 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<EmojiPtr>(&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<CustomOne>();
|
||||
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,
|
||||
|
|
|
@ -71,6 +71,7 @@ enum class EmojiListMode {
|
|||
Full,
|
||||
TopicIcon,
|
||||
EmojiStatus,
|
||||
ChannelStatus,
|
||||
FullReactions,
|
||||
RecentReactions,
|
||||
UserpicBuilder,
|
||||
|
@ -384,6 +385,7 @@ private:
|
|||
bool _grabbingChosen = false;
|
||||
QVector<EmojiPtr> _emoji[kEmojiSectionCount];
|
||||
std::vector<CustomSet> _custom;
|
||||
base::flat_set<DocumentId> _restrictedCustomList;
|
||||
base::flat_map<DocumentId, CustomEmojiInstance> _customEmoji;
|
||||
base::flat_map<
|
||||
DocumentId,
|
||||
|
|
|
@ -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<Main::Session*> session, const QString &emoji)
|
||||
: _session(session)
|
||||
|
@ -35,7 +36,7 @@ DicePack::DicePack(not_null<Main::Session*> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -81,6 +81,7 @@ enum class TabbedSelectorMode {
|
|||
EmojiOnly,
|
||||
MediaEditor,
|
||||
EmojiStatus,
|
||||
ChannelStatus,
|
||||
BackgroundEmoji,
|
||||
FullReactions,
|
||||
RecentReactions,
|
||||
|
|
|
@ -428,11 +428,12 @@ void Application::showOpenGLCrashNotification() {
|
|||
Local::writeSettings();
|
||||
Restart();
|
||||
};
|
||||
const auto keepDisabled = [=] {
|
||||
const auto keepDisabled = [=](Fn<void()> close) {
|
||||
Ui::GL::ForceDisable(true);
|
||||
Ui::GL::CrashCheckFinish();
|
||||
settings().setDisableOpenGL(true);
|
||||
Local::writeSettings();
|
||||
close();
|
||||
};
|
||||
_lastActivePrimaryWindow->show(Ui::MakeConfirmBox({
|
||||
.text = ""
|
||||
|
|
|
@ -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<ClickHandlerContext>();
|
||||
|
||||
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<ClickHandlerContext>();
|
||||
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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<MTPForumTopic> &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();
|
||||
}
|
||||
|
|
|
@ -463,6 +463,9 @@ public:
|
|||
|
||||
void processTopics(const MTPVector<MTPForumTopic> &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<UserId> _recentRequesters;
|
||||
MsgId _availableMinId = 0;
|
||||
|
||||
|
|
|
@ -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<Session*> 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<DocumentId> &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<UserData*> user,
|
||||
not_null<PeerData*> 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<PeerData*> 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) {
|
||||
|
|
|
@ -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<DocumentId> &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<PeerData*> peer, DocumentId id, TimeId until = 0);
|
||||
|
||||
void registerAutomaticClear(not_null<UserData*> user, TimeId until);
|
||||
void registerAutomaticClear(not_null<PeerData*> peer, TimeId until);
|
||||
|
||||
using Groups = std::vector<Ui::EmojiGroup>;
|
||||
[[nodiscard]] rpl::producer<Groups> emojiGroupsValue() const;
|
||||
|
@ -71,10 +75,14 @@ private:
|
|||
void requestRecent();
|
||||
void requestDefault();
|
||||
void requestColored();
|
||||
void requestChannelDefault();
|
||||
void requestChannelColored();
|
||||
|
||||
void updateRecent(const MTPDaccount_emojiStatuses &data);
|
||||
void updateDefault(const MTPDaccount_emojiStatuses &data);
|
||||
void updateColored(const MTPDmessages_stickerSet &data);
|
||||
void updateChannelDefault(const MTPDaccount_emojiStatuses &data);
|
||||
void updateChannelColored(const MTPDmessages_stickerSet &data);
|
||||
|
||||
void processClearingIn(TimeId wait);
|
||||
void processClearing();
|
||||
|
@ -87,9 +95,13 @@ private:
|
|||
std::vector<DocumentId> _recent;
|
||||
std::vector<DocumentId> _default;
|
||||
std::vector<DocumentId> _colored;
|
||||
std::vector<DocumentId> _channelDefault;
|
||||
std::vector<DocumentId> _channelColored;
|
||||
rpl::event_stream<> _recentUpdated;
|
||||
rpl::event_stream<> _defaultUpdated;
|
||||
rpl::event_stream<> _coloredUpdated;
|
||||
rpl::event_stream<> _channelDefaultUpdated;
|
||||
rpl::event_stream<> _channelColoredUpdated;
|
||||
|
||||
mtpRequestId _recentRequestId = 0;
|
||||
bool _recentRequestScheduled = false;
|
||||
|
@ -100,9 +112,14 @@ private:
|
|||
|
||||
mtpRequestId _coloredRequestId = 0;
|
||||
|
||||
mtpRequestId _sentRequestId = 0;
|
||||
mtpRequestId _channelDefaultRequestId = 0;
|
||||
uint64 _channelDefaultHash = 0;
|
||||
|
||||
base::flat_map<not_null<UserData*>, TimeId> _clearing;
|
||||
mtpRequestId _channelColoredRequestId = 0;
|
||||
|
||||
base::flat_map<not_null<PeerData*>, mtpRequestId> _sentRequests;
|
||||
|
||||
base::flat_map<not_null<PeerData*>, TimeId> _clearing;
|
||||
base::Timer _clearingTimer;
|
||||
|
||||
GroupsType _emojiGroups;
|
||||
|
|
|
@ -61,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_file_origin.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "data/data_story.h"
|
||||
#include "data/data_user.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "core/application.h"
|
||||
|
@ -363,10 +364,10 @@ Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
|
|||
return result;
|
||||
}
|
||||
|
||||
Giveaway ComputeGiveawayData(
|
||||
GiveawayStart ComputeGiveawayStartData(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPDmessageMediaGiveaway &data) {
|
||||
auto result = Giveaway{
|
||||
auto result = GiveawayStart{
|
||||
.untilDate = data.vuntil_date().v,
|
||||
.quantity = data.vquantity().v,
|
||||
.months = data.vmonths().v,
|
||||
|
@ -383,6 +384,35 @@ Giveaway ComputeGiveawayData(
|
|||
result.countries.push_back(qs(country));
|
||||
}
|
||||
}
|
||||
if (const auto additional = data.vprize_description()) {
|
||||
result.additionalPrize = qs(*additional);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
GiveawayResults ComputeGiveawayResultsData(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPDmessageMediaGiveawayResults &data) {
|
||||
const auto additional = data.vadditional_peers_count();
|
||||
auto result = GiveawayResults{
|
||||
.channel = item->history()->owner().channel(data.vchannel_id()),
|
||||
.untilDate = data.vuntil_date().v,
|
||||
.launchId = data.vlaunch_msg_id().v,
|
||||
.additionalPeersCount = additional.value_or_empty(),
|
||||
.winnersCount = data.vwinners_count().v,
|
||||
.unclaimedCount = data.vunclaimed_count().v,
|
||||
.months = data.vmonths().v,
|
||||
.refunded = data.is_refunded(),
|
||||
.all = !data.is_only_new_subscribers(),
|
||||
};
|
||||
result.winners.reserve(data.vwinners().v.size());
|
||||
const auto owner = &item->history()->owner();
|
||||
for (const auto &id : data.vwinners().v) {
|
||||
result.winners.push_back(owner->user(UserId(id)));
|
||||
}
|
||||
if (const auto additional = data.vprize_description()) {
|
||||
result.additionalPrize = qs(*additional);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -453,7 +483,11 @@ bool Media::storyMention() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
const Giveaway *Media::giveaway() const {
|
||||
const GiveawayStart *Media::giveawayStart() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const GiveawayResults *Media::giveawayResults() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -1506,15 +1540,57 @@ bool MediaWebPage::replyPreviewLoaded() const {
|
|||
}
|
||||
|
||||
ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const {
|
||||
auto text = options.ignoreMessageText
|
||||
? TextWithEntities()
|
||||
: options.translated
|
||||
? parent()->translatedText()
|
||||
: parent()->originalText();
|
||||
if (text.empty()) {
|
||||
text = Ui::Text::Colorized(_page->url);
|
||||
const auto caption = [&] {
|
||||
const auto text = options.ignoreMessageText
|
||||
? TextWithEntities()
|
||||
: options.translated
|
||||
? parent()->translatedText()
|
||||
: parent()->originalText();
|
||||
return text.empty() ? Ui::Text::Colorized(_page->url) : text;
|
||||
}();
|
||||
const auto pageTypeWithPreview = _page->type == WebPageType::Photo
|
||||
|| _page->type == WebPageType::Video
|
||||
|| _page->type == WebPageType::Document;
|
||||
if (pageTypeWithPreview || !_page->collage.items.empty()) {
|
||||
if (auto found = FindCachedPreview(options.existing, _page, false)) {
|
||||
return { .text = caption, .images = { std::move(found) } };
|
||||
}
|
||||
auto context = std::any();
|
||||
auto images = std::vector<ItemPreviewImage>();
|
||||
auto prepared = ItemPreviewImage();
|
||||
const auto radius = ImageRoundRadius::Small;
|
||||
if (const auto photo = MediaWebPage::photo()) {
|
||||
const auto media = photo->createMediaView();
|
||||
prepared = PreparePhotoPreview(parent(), media, radius, false);
|
||||
if (prepared || !prepared.cacheKey) {
|
||||
images.push_back(std::move(prepared));
|
||||
if (!prepared.cacheKey) {
|
||||
context = media;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const auto document = MediaWebPage::document();
|
||||
if (document
|
||||
&& document->hasThumbnail()
|
||||
&& (document->isGifv() || document->isVideoFile())) {
|
||||
const auto media = document->createMediaView();
|
||||
prepared = PrepareFilePreview(parent(), media, radius, false);
|
||||
if (prepared || !prepared.cacheKey) {
|
||||
images.push_back(std::move(prepared));
|
||||
if (!prepared.cacheKey) {
|
||||
context = media;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
.text = caption,
|
||||
.images = std::move(images),
|
||||
.loadingContext = std::move(context),
|
||||
};
|
||||
} else {
|
||||
return { .text = caption };
|
||||
}
|
||||
return { .text = text };
|
||||
}
|
||||
|
||||
TextWithEntities MediaWebPage::notificationText() const {
|
||||
|
@ -2196,51 +2272,103 @@ std::unique_ptr<HistoryView::Media> MediaStory::createView(
|
|||
}
|
||||
}
|
||||
|
||||
MediaGiveaway::MediaGiveaway(
|
||||
MediaGiveawayStart::MediaGiveawayStart(
|
||||
not_null<HistoryItem*> parent,
|
||||
const Giveaway &data)
|
||||
const GiveawayStart &data)
|
||||
: Media(parent)
|
||||
, _giveaway(data) {
|
||||
, _data(data) {
|
||||
parent->history()->session().giftBoxStickersPacks().load();
|
||||
}
|
||||
|
||||
std::unique_ptr<Media> MediaGiveaway::clone(not_null<HistoryItem*> parent) {
|
||||
return std::make_unique<MediaGiveaway>(parent, _giveaway);
|
||||
std::unique_ptr<Media> MediaGiveawayStart::clone(
|
||||
not_null<HistoryItem*> parent) {
|
||||
return std::make_unique<MediaGiveawayStart>(parent, _data);
|
||||
}
|
||||
|
||||
const Giveaway *MediaGiveaway::giveaway() const {
|
||||
return &_giveaway;
|
||||
const GiveawayStart *MediaGiveawayStart::giveawayStart() const {
|
||||
return &_data;
|
||||
}
|
||||
|
||||
TextWithEntities MediaGiveaway::notificationText() const {
|
||||
TextWithEntities MediaGiveawayStart::notificationText() const {
|
||||
return {
|
||||
.text = tr::lng_prizes_title(tr::now, lt_count, _giveaway.quantity),
|
||||
.text = tr::lng_prizes_title(tr::now, lt_count, _data.quantity),
|
||||
};
|
||||
}
|
||||
|
||||
QString MediaGiveaway::pinnedTextSubstring() const {
|
||||
QString MediaGiveawayStart::pinnedTextSubstring() const {
|
||||
return QString::fromUtf8("\xC2\xAB")
|
||||
+ notificationText().text
|
||||
+ QString::fromUtf8("\xC2\xBB");
|
||||
}
|
||||
|
||||
TextForMimeData MediaGiveaway::clipboardText() const {
|
||||
TextForMimeData MediaGiveawayStart::clipboardText() const {
|
||||
return TextForMimeData();
|
||||
}
|
||||
|
||||
bool MediaGiveaway::updateInlineResultMedia(const MTPMessageMedia &media) {
|
||||
bool MediaGiveawayStart::updateInlineResultMedia(const MTPMessageMedia &media) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MediaGiveaway::updateSentMedia(const MTPMessageMedia &media) {
|
||||
bool MediaGiveawayStart::updateSentMedia(const MTPMessageMedia &media) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<HistoryView::Media> MediaGiveaway::createView(
|
||||
std::unique_ptr<HistoryView::Media> MediaGiveawayStart::createView(
|
||||
not_null<HistoryView::Element*> message,
|
||||
not_null<HistoryItem*> realParent,
|
||||
HistoryView::Element *replacing) {
|
||||
return std::make_unique<HistoryView::Giveaway>(message, &_giveaway);
|
||||
return std::make_unique<HistoryView::MediaInBubble>(
|
||||
message,
|
||||
HistoryView::GenerateGiveawayStart(message, &_data));
|
||||
}
|
||||
|
||||
MediaGiveawayResults::MediaGiveawayResults(
|
||||
not_null<HistoryItem*> parent,
|
||||
const GiveawayResults &data)
|
||||
: Media(parent)
|
||||
, _data(data) {
|
||||
}
|
||||
|
||||
std::unique_ptr<Media> MediaGiveawayResults::clone(
|
||||
not_null<HistoryItem*> parent) {
|
||||
return std::make_unique<MediaGiveawayResults>(parent, _data);
|
||||
}
|
||||
|
||||
const GiveawayResults *MediaGiveawayResults::giveawayResults() const {
|
||||
return &_data;
|
||||
}
|
||||
|
||||
TextWithEntities MediaGiveawayResults::notificationText() const {
|
||||
return {
|
||||
.text = tr::lng_prizes_results_title(tr::now),
|
||||
};
|
||||
}
|
||||
|
||||
QString MediaGiveawayResults::pinnedTextSubstring() const {
|
||||
return QString::fromUtf8("\xC2\xAB")
|
||||
+ notificationText().text
|
||||
+ QString::fromUtf8("\xC2\xBB");
|
||||
}
|
||||
|
||||
TextForMimeData MediaGiveawayResults::clipboardText() const {
|
||||
return TextForMimeData();
|
||||
}
|
||||
|
||||
bool MediaGiveawayResults::updateInlineResultMedia(const MTPMessageMedia &media) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MediaGiveawayResults::updateSentMedia(const MTPMessageMedia &media) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<HistoryView::Media> MediaGiveawayResults::createView(
|
||||
not_null<HistoryView::Element*> message,
|
||||
not_null<HistoryItem*> realParent,
|
||||
HistoryView::Element *replacing) {
|
||||
return std::make_unique<HistoryView::MediaInBubble>(
|
||||
message,
|
||||
HistoryView::GenerateGiveawayResults(message, &_data));
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -90,15 +90,30 @@ struct Invoice {
|
|||
bool isTest = false;
|
||||
};
|
||||
|
||||
struct Giveaway {
|
||||
struct GiveawayStart {
|
||||
std::vector<not_null<ChannelData*>> channels;
|
||||
std::vector<QString> countries;
|
||||
QString additionalPrize;
|
||||
TimeId untilDate = 0;
|
||||
int quantity = 0;
|
||||
int months = 0;
|
||||
bool all = false;
|
||||
};
|
||||
|
||||
struct GiveawayResults {
|
||||
not_null<ChannelData*> channel;
|
||||
std::vector<not_null<PeerData*>> winners;
|
||||
QString additionalPrize;
|
||||
TimeId untilDate = 0;
|
||||
MsgId launchId = 0;
|
||||
int additionalPeersCount = 0;
|
||||
int winnersCount = 0;
|
||||
int unclaimedCount = 0;
|
||||
int months = 0;
|
||||
bool refunded = false;
|
||||
bool all = false;
|
||||
};
|
||||
|
||||
struct GiftCode {
|
||||
QString slug;
|
||||
ChannelData *channel = nullptr;
|
||||
|
@ -135,7 +150,8 @@ public:
|
|||
virtual FullStoryId storyId() const;
|
||||
virtual bool storyExpired(bool revalidate = false);
|
||||
virtual bool storyMention() const;
|
||||
virtual const Giveaway *giveaway() const;
|
||||
virtual const GiveawayStart *giveawayStart() const;
|
||||
virtual const GiveawayResults *giveawayResults() const;
|
||||
|
||||
virtual bool uploading() const;
|
||||
virtual Storage::SharedMediaTypesMask sharedMediaTypes() const;
|
||||
|
@ -634,15 +650,15 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class MediaGiveaway final : public Media {
|
||||
class MediaGiveawayStart final : public Media {
|
||||
public:
|
||||
MediaGiveaway(
|
||||
MediaGiveawayStart(
|
||||
not_null<HistoryItem*> parent,
|
||||
const Giveaway &data);
|
||||
const GiveawayStart &data);
|
||||
|
||||
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
||||
|
||||
const Giveaway *giveaway() const override;
|
||||
const GiveawayStart *giveawayStart() const override;
|
||||
|
||||
TextWithEntities notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
|
@ -656,7 +672,33 @@ public:
|
|||
HistoryView::Element *replacing = nullptr) override;
|
||||
|
||||
private:
|
||||
Giveaway _giveaway;
|
||||
GiveawayStart _data;
|
||||
|
||||
};
|
||||
|
||||
class MediaGiveawayResults final : public Media {
|
||||
public:
|
||||
MediaGiveawayResults(
|
||||
not_null<HistoryItem*> parent,
|
||||
const GiveawayResults &data);
|
||||
|
||||
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
||||
|
||||
const GiveawayResults *giveawayResults() const override;
|
||||
|
||||
TextWithEntities notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
|
||||
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
|
||||
bool updateSentMedia(const MTPMessageMedia &media) override;
|
||||
std::unique_ptr<HistoryView::Media> createView(
|
||||
not_null<HistoryView::Element*> message,
|
||||
not_null<HistoryItem*> realParent,
|
||||
HistoryView::Element *replacing = nullptr) override;
|
||||
|
||||
private:
|
||||
GiveawayResults _data;
|
||||
|
||||
};
|
||||
|
||||
|
@ -670,8 +712,12 @@ private:
|
|||
|
||||
[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call);
|
||||
|
||||
[[nodiscard]] Giveaway ComputeGiveawayData(
|
||||
[[nodiscard]] GiveawayStart ComputeGiveawayStartData(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPDmessageMediaGiveaway &data);
|
||||
|
||||
[[nodiscard]] GiveawayResults ComputeGiveawayResultsData(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPDmessageMediaGiveawayResults &data);
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_chat_participant_status.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_emoji_statuses.h"
|
||||
#include "data/data_message_reaction_id.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_folder.h"
|
||||
|
@ -926,6 +927,24 @@ bool PeerData::changeBackgroundEmojiId(DocumentId id) {
|
|||
_backgroundEmojiId = id;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PeerData::setEmojiStatus(const MTPEmojiStatus &status) {
|
||||
const auto parsed = Data::ParseEmojiStatus(status);
|
||||
setEmojiStatus(parsed.id, parsed.until);
|
||||
}
|
||||
|
||||
void PeerData::setEmojiStatus(DocumentId emojiStatusId, TimeId until) {
|
||||
if (_emojiStatusId != emojiStatusId) {
|
||||
_emojiStatusId = emojiStatusId;
|
||||
session().changes().peerUpdated(this, UpdateFlag::EmojiStatus);
|
||||
}
|
||||
owner().emojiStatuses().registerAutomaticClear(this, until);
|
||||
}
|
||||
|
||||
DocumentId PeerData::emojiStatusId() const {
|
||||
return _emojiStatusId;
|
||||
}
|
||||
|
||||
bool PeerData::isSelf() const {
|
||||
if (const auto user = asUser()) {
|
||||
return (user->flags() & UserDataFlag::Self);
|
||||
|
|
|
@ -178,6 +178,10 @@ public:
|
|||
[[nodiscard]] DocumentId backgroundEmojiId() const;
|
||||
bool changeBackgroundEmojiId(DocumentId id);
|
||||
|
||||
void setEmojiStatus(const MTPEmojiStatus &status);
|
||||
void setEmojiStatus(DocumentId emojiStatusId, TimeId until = 0);
|
||||
[[nodiscard]] DocumentId emojiStatusId() const;
|
||||
|
||||
[[nodiscard]] bool isUser() const {
|
||||
return peerIsUser(id);
|
||||
}
|
||||
|
@ -466,6 +470,7 @@ private:
|
|||
base::flat_set<QString> _nameWords; // for filtering
|
||||
base::flat_set<QChar> _nameFirstLetters;
|
||||
|
||||
DocumentId _emojiStatusId = 0;
|
||||
uint64 _backgroundEmojiId = 0;
|
||||
crl::time _lastFullUpdate = 0;
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ private:
|
|||
// In case this is a problem the ~Gif code should be rewritten.
|
||||
const not_null<PhotoData*> _owner;
|
||||
mutable std::unique_ptr<Image> _inlineThumbnail;
|
||||
std::array<PhotoImage, kPhotoSizeCount> _images;
|
||||
std::array<PhotoImage, kPhotoSizeCount> _images;
|
||||
QByteArray _videoBytesSmall;
|
||||
QByteArray _videoBytesLarge;
|
||||
|
||||
|
|
|
@ -856,6 +856,7 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
|||
|
||||
const auto wasCallNotEmpty = Data::ChannelHasActiveCall(channel);
|
||||
|
||||
channel->updateLevelHint(data.vlevel().value_or_empty());
|
||||
if (const auto count = data.vparticipants_count()) {
|
||||
channel->setMembersCount(count->v);
|
||||
}
|
||||
|
@ -865,6 +866,11 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
|||
channel->setDefaultRestrictions(ChatRestrictions());
|
||||
}
|
||||
|
||||
if (const auto &status = data.vemoji_status()) {
|
||||
channel->setEmojiStatus(*status);
|
||||
} else {
|
||||
channel->setEmojiStatus(0);
|
||||
}
|
||||
if (minimal) {
|
||||
if (channel->input.type() == mtpc_inputPeerEmpty
|
||||
|| channel->inputChannel.type() == mtpc_inputChannelEmpty) {
|
||||
|
@ -3451,7 +3457,7 @@ void Session::webpageApplyFields(
|
|||
data.vid().v,
|
||||
};
|
||||
if (const auto embed = data.vstory()) {
|
||||
story = stories().applyFromWebpage(
|
||||
story = stories().applySingle(
|
||||
peerFromMTP(data.vpeer()),
|
||||
*embed);
|
||||
} else if (const auto maybe = stories().lookup(storyId)) {
|
||||
|
|
|
@ -148,11 +148,7 @@ struct RecentPostId final {
|
|||
};
|
||||
|
||||
struct PublicForwardsSlice final {
|
||||
struct OffsetToken final {
|
||||
int rate = 0;
|
||||
FullMsgId fullId;
|
||||
QString storyOffset;
|
||||
};
|
||||
using OffsetToken = QString;
|
||||
QVector<RecentPostId> list;
|
||||
int total = 0;
|
||||
bool allLoaded = false;
|
||||
|
|
|
@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_statistics_chart.h"
|
||||
|
||||
#include "statistics/statistics_format_values.h"
|
||||
#include "styles/style_basic.h"
|
||||
#include "styles/style_statistics.h"
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QLocale>
|
||||
|
@ -46,6 +48,11 @@ void StatisticalChart::measure() {
|
|||
const auto dateCount = int((end - start) / timeStep) + 10;
|
||||
daysLookup.reserve(dateCount);
|
||||
constexpr auto kOneDay = 3600 * 24 * 1000;
|
||||
|
||||
// View data.
|
||||
auto maxWidth = 0;
|
||||
const auto &defaultFont = st::statisticsDetailsBottomCaptionStyle.font;
|
||||
|
||||
for (auto i = 0; i < dateCount; i++) {
|
||||
const auto r = (start + (i * timeStep)) / 1000;
|
||||
if (timeStep == 1) {
|
||||
|
@ -59,7 +66,9 @@ void StatisticalChart::measure() {
|
|||
} else {
|
||||
daysLookup.push_back(Statistic::LangDayMonth(r));
|
||||
}
|
||||
maxWidth = std::max(maxWidth, defaultFont->width(daysLookup.back()));
|
||||
}
|
||||
dayStringMaxWidth = maxWidth;
|
||||
|
||||
oneDayPercentage = timeStep / float64(end - start);
|
||||
}
|
||||
|
|
|
@ -66,6 +66,9 @@ struct StatisticalChart {
|
|||
bool hasPercentages = false;
|
||||
bool weekFormat = false;
|
||||
|
||||
// View data.
|
||||
int dayStringMaxWidth = 0;
|
||||
|
||||
};
|
||||
|
||||
struct StatisticalGraph final {
|
||||
|
|
|
@ -245,14 +245,13 @@ void Stories::apply(not_null<PeerData*> peer, const MTPPeerStories *data) {
|
|||
}
|
||||
}
|
||||
|
||||
Story *Stories::applyFromWebpage(PeerId peerId, const MTPstoryItem &story) {
|
||||
Story *Stories::applySingle(PeerId peerId, const MTPstoryItem &story) {
|
||||
// AyuGram disableStories
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (settings->disableStories)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto idDates = parseAndApply(
|
||||
_owner->peer(peerId),
|
||||
story,
|
||||
|
@ -1424,6 +1423,96 @@ void Stories::loadViewsSlice(
|
|||
}
|
||||
}
|
||||
|
||||
void Stories::loadReactionsSlice(
|
||||
not_null<PeerData*> peer,
|
||||
StoryId id,
|
||||
QString offset,
|
||||
Fn<void(StoryViews)> done) {
|
||||
Expects(peer->isChannel());
|
||||
|
||||
if (_reactionsStoryPeer == peer
|
||||
&& _reactionsStoryId == id
|
||||
&& _reactionsOffset == offset) {
|
||||
if (_reactionsRequestId) {
|
||||
_reactionsDone = std::move(done);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_reactionsStoryPeer = peer;
|
||||
_reactionsStoryId = id;
|
||||
_reactionsOffset = offset;
|
||||
_reactionsDone = std::move(done);
|
||||
|
||||
using Flag = MTPstories_GetStoryReactionsList::Flag;
|
||||
const auto api = &_owner->session().api();
|
||||
_owner->session().api().request(_reactionsRequestId).cancel();
|
||||
_reactionsRequestId = api->request(MTPstories_GetStoryReactionsList(
|
||||
MTP_flags(offset.isEmpty() ? Flag() : Flag::f_offset),
|
||||
_reactionsStoryPeer->input,
|
||||
MTP_int(_reactionsStoryId),
|
||||
MTPReaction(),
|
||||
MTP_string(_reactionsOffset),
|
||||
MTP_int(kViewsPerPage)
|
||||
)).done([=](const MTPstories_StoryReactionsList &result) {
|
||||
_reactionsRequestId = 0;
|
||||
|
||||
const auto &data = result.data();
|
||||
auto slice = StoryViews{
|
||||
.nextOffset = data.vnext_offset().value_or_empty(),
|
||||
.reactions = data.vcount().v,
|
||||
.total = data.vcount().v,
|
||||
};
|
||||
_owner->processUsers(data.vusers());
|
||||
_owner->processChats(data.vchats());
|
||||
slice.list.reserve(data.vreactions().v.size());
|
||||
for (const auto &reaction : data.vreactions().v) {
|
||||
reaction.match([&](const MTPDstoryReaction &data) {
|
||||
slice.list.push_back({
|
||||
.peer = _owner->peer(peerFromMTP(data.vpeer_id())),
|
||||
.reaction = ReactionFromMTP(data.vreaction()),
|
||||
.date = data.vdate().v,
|
||||
});
|
||||
}, [&](const MTPDstoryReactionPublicRepost &data) {
|
||||
const auto story = applySingle(
|
||||
peerFromMTP(data.vpeer_id()),
|
||||
data.vstory());
|
||||
if (story) {
|
||||
slice.list.push_back({
|
||||
.peer = story->peer(),
|
||||
.repostId = story->id(),
|
||||
});
|
||||
}
|
||||
}, [&](const MTPDstoryReactionPublicForward &data) {
|
||||
const auto item = _owner->addNewMessage(
|
||||
data.vmessage(),
|
||||
{},
|
||||
NewMessageType::Existing);
|
||||
if (item) {
|
||||
slice.list.push_back({
|
||||
.peer = item->history()->peer,
|
||||
.forwardId = item->id,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
const auto fullId = FullStoryId{
|
||||
.peer = _reactionsStoryPeer->id,
|
||||
.story = _reactionsStoryId,
|
||||
};
|
||||
if (const auto story = lookup(fullId)) {
|
||||
(*story)->applyChannelReactionsSlice(_reactionsOffset, slice);
|
||||
}
|
||||
if (const auto done = base::take(_reactionsDone)) {
|
||||
done(std::move(slice));
|
||||
}
|
||||
}).fail([=] {
|
||||
_reactionsRequestId = 0;
|
||||
if (const auto done = base::take(_reactionsDone)) {
|
||||
done({});
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
void Stories::sendViewsSliceRequest() {
|
||||
Expects(_viewsStoryPeer != nullptr);
|
||||
Expects(_viewsStoryPeer->isSelf());
|
||||
|
@ -1445,17 +1534,43 @@ void Stories::sendViewsSliceRequest() {
|
|||
auto slice = StoryViews{
|
||||
.nextOffset = data.vnext_offset().value_or_empty(),
|
||||
.reactions = data.vreactions_count().v,
|
||||
.forwards = data.vforwards_count().v,
|
||||
.views = data.vviews_count().v,
|
||||
.total = data.vcount().v,
|
||||
};
|
||||
_owner->processUsers(data.vusers());
|
||||
_owner->processChats(data.vchats());
|
||||
slice.list.reserve(data.vviews().v.size());
|
||||
for (const auto &view : data.vviews().v) {
|
||||
slice.list.push_back({
|
||||
.peer = _owner->peer(peerFromUser(view.data().vuser_id())),
|
||||
.reaction = (view.data().vreaction()
|
||||
? ReactionFromMTP(*view.data().vreaction())
|
||||
: Data::ReactionId()),
|
||||
.date = view.data().vdate().v,
|
||||
view.match([&](const MTPDstoryView &data) {
|
||||
slice.list.push_back({
|
||||
.peer = _owner->peer(peerFromUser(data.vuser_id())),
|
||||
.reaction = (data.vreaction()
|
||||
? ReactionFromMTP(*data.vreaction())
|
||||
: Data::ReactionId()),
|
||||
.date = data.vdate().v,
|
||||
});
|
||||
}, [&](const MTPDstoryViewPublicRepost &data) {
|
||||
const auto story = applySingle(
|
||||
peerFromMTP(data.vpeer_id()),
|
||||
data.vstory());
|
||||
if (story) {
|
||||
slice.list.push_back({
|
||||
.peer = story->peer(),
|
||||
.repostId = story->id(),
|
||||
});
|
||||
}
|
||||
}, [&](const MTPDstoryViewPublicForward &data) {
|
||||
const auto item = _owner->addNewMessage(
|
||||
data.vmessage(),
|
||||
{},
|
||||
NewMessageType::Existing);
|
||||
if (item) {
|
||||
slice.list.push_back({
|
||||
.peer = item->history()->peer,
|
||||
.forwardId = item->id,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
const auto fullId = FullStoryId{
|
||||
|
|
|
@ -149,7 +149,7 @@ public:
|
|||
void apply(const MTPDupdateReadStories &data);
|
||||
void apply(const MTPStoriesStealthMode &stealthMode);
|
||||
void apply(not_null<PeerData*> peer, const MTPPeerStories *data);
|
||||
Story *applyFromWebpage(PeerId peerId, const MTPstoryItem &story);
|
||||
Story *applySingle(PeerId peerId, const MTPstoryItem &story);
|
||||
void loadAround(FullStoryId id, StoriesContext context);
|
||||
|
||||
const StoriesSource *source(PeerId id) const;
|
||||
|
@ -182,6 +182,11 @@ public:
|
|||
StoryId id,
|
||||
QString offset,
|
||||
Fn<void(StoryViews)> done);
|
||||
void loadReactionsSlice(
|
||||
not_null<PeerData*> peer,
|
||||
StoryId id,
|
||||
QString offset,
|
||||
Fn<void(StoryViews)> done);
|
||||
|
||||
[[nodiscard]] bool hasArchive(not_null<PeerData*> peer) const;
|
||||
|
||||
|
@ -379,6 +384,12 @@ private:
|
|||
Fn<void(StoryViews)> _viewsDone;
|
||||
mtpRequestId _viewsRequestId = 0;
|
||||
|
||||
PeerData *_reactionsStoryPeer = nullptr;
|
||||
StoryId _reactionsStoryId = 0;
|
||||
QString _reactionsOffset;
|
||||
Fn<void(StoryViews)> _reactionsDone;
|
||||
mtpRequestId _reactionsRequestId = 0;
|
||||
|
||||
base::flat_set<FullStoryId> _preloaded;
|
||||
std::vector<FullStoryId> _toPreloadSources[kStorySourcesListCount];
|
||||
std::vector<FullStoryId> _toPreloadViewer;
|
||||
|
|
|
@ -81,8 +81,11 @@ using UpdateFlag = StoryUpdate::Flag;
|
|||
}, [](const MTPDgeoPointEmpty &) {
|
||||
});
|
||||
}, [&](const MTPDmediaAreaSuggestedReaction &data) {
|
||||
}, [&](const MTPDmediaAreaChannelPost &data) {
|
||||
}, [&](const MTPDinputMediaAreaChannelPost &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
|
||||
}, [&](const MTPDinputMediaAreaVenue &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaVenue in API data."));
|
||||
LOG(("API Error: Unexpected inputMediaAreaVenue from API."));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
@ -99,8 +102,32 @@ using UpdateFlag = StoryUpdate::Flag;
|
|||
.flipped = data.is_flipped(),
|
||||
.dark = data.is_dark(),
|
||||
});
|
||||
}, [&](const MTPDmediaAreaChannelPost &data) {
|
||||
}, [&](const MTPDinputMediaAreaChannelPost &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
|
||||
}, [&](const MTPDinputMediaAreaVenue &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaVenue in API data."));
|
||||
LOG(("API Error: Unexpected inputMediaAreaVenue from API."));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto ParseChannelPost(const MTPMediaArea &area)
|
||||
-> std::optional<ChannelPost> {
|
||||
auto result = std::optional<ChannelPost>();
|
||||
area.match([&](const MTPDmediaAreaVenue &data) {
|
||||
}, [&](const MTPDmediaAreaGeoPoint &data) {
|
||||
}, [&](const MTPDmediaAreaSuggestedReaction &data) {
|
||||
}, [&](const MTPDmediaAreaChannelPost &data) {
|
||||
result.emplace(ChannelPost{
|
||||
.area = ParseArea(data.vcoordinates()),
|
||||
.itemId = FullMsgId(
|
||||
peerFromChannel(data.vchannel_id()),
|
||||
data.vmsg_id().v),
|
||||
});
|
||||
}, [&](const MTPDinputMediaAreaChannelPost &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
|
||||
}, [&](const MTPDinputMediaAreaVenue &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaVenue from API."));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
@ -130,6 +157,13 @@ using UpdateFlag = StoryUpdate::Flag;
|
|||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] bool RepostModified(const MTPDstoryItem &data) {
|
||||
if (const auto forwarded = data.vfwd_from()) {
|
||||
return forwarded->data().is_modified();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask {
|
||||
|
@ -151,7 +185,7 @@ private:
|
|||
Fn<void(QByteArray)> _done;
|
||||
base::flat_set<int> _requestedOffsets;
|
||||
int64 _full = 0;
|
||||
int _nextRequestOffset = 0;
|
||||
int _nextRequestOffset = 0;
|
||||
bool _finished = false;
|
||||
bool _failed = false;
|
||||
|
||||
|
@ -245,7 +279,8 @@ Story::Story(
|
|||
, _repostSourceName(RepostSourceName(data))
|
||||
, _repostSourceId(RepostSourceId(data))
|
||||
, _date(data.vdate().v)
|
||||
, _expires(data.vexpire_date().v) {
|
||||
, _expires(data.vexpire_date().v)
|
||||
, _repostModified(RepostModified(data)) {
|
||||
applyFields(std::move(media), data, now, true);
|
||||
}
|
||||
|
||||
|
@ -478,10 +513,22 @@ const StoryViews &Story::viewsList() const {
|
|||
return _views;
|
||||
}
|
||||
|
||||
int Story::views() const {
|
||||
const StoryViews &Story::channelReactionsList() const {
|
||||
return _channelReactions;
|
||||
}
|
||||
|
||||
int Story::interactions() const {
|
||||
return _views.total;
|
||||
}
|
||||
|
||||
int Story::views() const {
|
||||
return _views.views;
|
||||
}
|
||||
|
||||
int Story::forwards() const {
|
||||
return _views.forwards;
|
||||
}
|
||||
|
||||
int Story::reactions() const {
|
||||
return _views.reactions;
|
||||
}
|
||||
|
@ -490,12 +537,19 @@ void Story::applyViewsSlice(
|
|||
const QString &offset,
|
||||
const StoryViews &slice) {
|
||||
const auto changed = (_views.reactions != slice.reactions)
|
||||
|| (_views.views != slice.views)
|
||||
|| (_views.forwards != slice.forwards)
|
||||
|| (_views.total != slice.total);
|
||||
_views.reactions = slice.reactions;
|
||||
_views.forwards = slice.forwards;
|
||||
_views.views = slice.views;
|
||||
_views.total = slice.total;
|
||||
_views.known = true;
|
||||
if (offset.isEmpty()) {
|
||||
_views = slice;
|
||||
if (!_channelReactions.total) {
|
||||
_channelReactions.total = _views.reactions + _views.forwards;
|
||||
}
|
||||
} else if (_views.nextOffset == offset) {
|
||||
_views.list.insert(
|
||||
end(_views.list),
|
||||
|
@ -509,6 +563,15 @@ void Story::applyViewsSlice(
|
|||
_views.list,
|
||||
Data::ReactionId(),
|
||||
&StoryView::reaction);
|
||||
_views.forwards = _views.total
|
||||
- ranges::count(
|
||||
_views.list,
|
||||
0,
|
||||
[](const StoryView &view) {
|
||||
return view.repostId
|
||||
? view.repostId
|
||||
: view.forwardId.bare;
|
||||
});
|
||||
}
|
||||
}
|
||||
const auto known = int(_views.list.size());
|
||||
|
@ -535,6 +598,33 @@ void Story::applyViewsSlice(
|
|||
}
|
||||
}
|
||||
|
||||
void Story::applyChannelReactionsSlice(
|
||||
const QString &offset,
|
||||
const StoryViews &slice) {
|
||||
const auto changed = (_channelReactions.reactions != slice.reactions)
|
||||
|| (_channelReactions.total != slice.total);
|
||||
_channelReactions.reactions = slice.reactions;
|
||||
_channelReactions.total = slice.total;
|
||||
_channelReactions.known = true;
|
||||
if (offset.isEmpty()) {
|
||||
_channelReactions = slice;
|
||||
} else if (_channelReactions.nextOffset == offset) {
|
||||
_channelReactions.list.insert(
|
||||
end(_channelReactions.list),
|
||||
begin(slice.list),
|
||||
end(slice.list));
|
||||
_channelReactions.nextOffset = slice.nextOffset;
|
||||
if (_channelReactions.nextOffset.isEmpty()) {
|
||||
_channelReactions.total = int(_channelReactions.list.size());
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
_peer->session().changes().storyUpdated(
|
||||
this,
|
||||
UpdateFlag::ViewsChanged);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<StoryLocation> &Story::locations() const {
|
||||
return _locations;
|
||||
}
|
||||
|
@ -543,6 +633,10 @@ const std::vector<SuggestedReaction> &Story::suggestedReactions() const {
|
|||
return _suggestedReactions;
|
||||
}
|
||||
|
||||
const std::vector<ChannelPost> &Story::channelPosts() const {
|
||||
return _channelPosts;
|
||||
}
|
||||
|
||||
void Story::applyChanges(
|
||||
StoryMedia media,
|
||||
const MTPDstoryItem &data,
|
||||
|
@ -555,6 +649,7 @@ Story::ViewsCounts Story::parseViewsCounts(
|
|||
const Data::ReactionId &mine) {
|
||||
auto result = ViewsCounts{
|
||||
.views = data.vviews_count().v,
|
||||
.forwards = data.vforwards_count().value_or_empty(),
|
||||
.reactions = data.vreactions_count().value_or_empty(),
|
||||
};
|
||||
if (const auto list = data.vrecent_viewers()) {
|
||||
|
@ -633,6 +728,7 @@ void Story::applyFields(
|
|||
viewsKnown = true;
|
||||
} else {
|
||||
counts.views = _views.total;
|
||||
counts.forwards = _views.forwards;
|
||||
counts.reactions = _views.reactions;
|
||||
counts.viewers = _recentViewers;
|
||||
for (const auto &suggested : _suggestedReactions) {
|
||||
|
@ -643,9 +739,8 @@ void Story::applyFields(
|
|||
}
|
||||
auto locations = std::vector<StoryLocation>();
|
||||
auto suggestedReactions = std::vector<SuggestedReaction>();
|
||||
auto channelPosts = std::vector<ChannelPost>();
|
||||
if (const auto areas = data.vmedia_areas()) {
|
||||
locations.reserve(areas->v.size());
|
||||
suggestedReactions.reserve(areas->v.size());
|
||||
for (const auto &area : areas->v) {
|
||||
if (const auto location = ParseLocation(area)) {
|
||||
locations.push_back(*location);
|
||||
|
@ -656,6 +751,8 @@ void Story::applyFields(
|
|||
reaction->count = i->second;
|
||||
}
|
||||
suggestedReactions.push_back(*reaction);
|
||||
} else if (auto post = ParseChannelPost(area)) {
|
||||
channelPosts.push_back(*post);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -667,6 +764,7 @@ void Story::applyFields(
|
|||
const auto locationsChanged = (_locations != locations);
|
||||
const auto suggestedReactionsChanged
|
||||
= (_suggestedReactions != suggestedReactions);
|
||||
const auto channelPostsChanged = (_channelPosts != channelPosts);
|
||||
const auto reactionChanged = (_sentReactionId != reaction);
|
||||
|
||||
_out = out;
|
||||
|
@ -689,6 +787,9 @@ void Story::applyFields(
|
|||
if (suggestedReactionsChanged) {
|
||||
_suggestedReactions = std::move(suggestedReactions);
|
||||
}
|
||||
if (channelPostsChanged) {
|
||||
_channelPosts = std::move(channelPosts);
|
||||
}
|
||||
if (reactionChanged) {
|
||||
_sentReactionId = reaction;
|
||||
}
|
||||
|
@ -697,7 +798,8 @@ void Story::applyFields(
|
|||
const auto changed = editedChanged
|
||||
|| captionChanged
|
||||
|| mediaChanged
|
||||
|| locationsChanged;
|
||||
|| locationsChanged
|
||||
|| channelPostsChanged;
|
||||
const auto reactionsChanged = reactionChanged
|
||||
|| suggestedReactionsChanged;
|
||||
if (!initial && (changed || reactionsChanged)) {
|
||||
|
@ -717,17 +819,27 @@ void Story::applyFields(
|
|||
}
|
||||
|
||||
void Story::updateViewsCounts(ViewsCounts &&counts, bool known, bool initial) {
|
||||
const auto viewsChanged = (_views.total != counts.views)
|
||||
const auto total = _views.total
|
||||
? _views.total
|
||||
: (counts.views + counts.forwards);
|
||||
const auto viewsChanged = (_views.total != total)
|
||||
|| (_views.forwards != counts.forwards)
|
||||
|| (_views.reactions != counts.reactions)
|
||||
|| (_recentViewers != counts.viewers);
|
||||
if (_views.reactions != counts.reactions
|
||||
|| _views.total != counts.views
|
||||
|| _views.forwards != counts.forwards
|
||||
|| _views.total != total
|
||||
|| _views.known != known) {
|
||||
_views = StoryViews{
|
||||
.reactions = counts.reactions,
|
||||
.total = counts.views,
|
||||
.forwards = counts.forwards,
|
||||
.views = counts.views,
|
||||
.total = total,
|
||||
.known = known,
|
||||
};
|
||||
if (!_channelReactions.total) {
|
||||
_channelReactions.total = _views.reactions + _views.forwards;
|
||||
}
|
||||
}
|
||||
if (viewsChanged) {
|
||||
_recentViewers = std::move(counts.viewers);
|
||||
|
@ -762,6 +874,10 @@ bool Story::repost() const {
|
|||
return _repostSourcePeer || !_repostSourceName.isEmpty();
|
||||
}
|
||||
|
||||
bool Story::repostModified() const {
|
||||
return _repostModified;
|
||||
}
|
||||
|
||||
PeerData *Story::repostSourcePeer() const {
|
||||
return _repostSourcePeer;
|
||||
}
|
||||
|
|
|
@ -61,6 +61,8 @@ struct StoryMedia {
|
|||
struct StoryView {
|
||||
not_null<PeerData*> peer;
|
||||
Data::ReactionId reaction;
|
||||
StoryId repostId = 0;
|
||||
MsgId forwardId = 0;
|
||||
TimeId date = 0;
|
||||
|
||||
friend inline bool operator==(StoryView, StoryView) = default;
|
||||
|
@ -70,6 +72,8 @@ struct StoryViews {
|
|||
std::vector<StoryView> list;
|
||||
QString nextOffset;
|
||||
int reactions = 0;
|
||||
int forwards = 0;
|
||||
int views = 0;
|
||||
int total = 0;
|
||||
bool known = false;
|
||||
};
|
||||
|
@ -109,6 +113,15 @@ struct SuggestedReaction {
|
|||
const SuggestedReaction &) = default;
|
||||
};
|
||||
|
||||
struct ChannelPost {
|
||||
StoryArea area;
|
||||
FullMsgId itemId;
|
||||
|
||||
friend inline bool operator==(
|
||||
const ChannelPost &,
|
||||
const ChannelPost &) = default;
|
||||
};
|
||||
|
||||
class Story final {
|
||||
public:
|
||||
Story(
|
||||
|
@ -166,13 +179,21 @@ public:
|
|||
[[nodiscard]] auto recentViewers() const
|
||||
-> const std::vector<not_null<PeerData*>> &;
|
||||
[[nodiscard]] const StoryViews &viewsList() const;
|
||||
[[nodiscard]] const StoryViews &channelReactionsList() const;
|
||||
[[nodiscard]] int interactions() const;
|
||||
[[nodiscard]] int views() const;
|
||||
[[nodiscard]] int forwards() const;
|
||||
[[nodiscard]] int reactions() const;
|
||||
void applyViewsSlice(const QString &offset, const StoryViews &slice);
|
||||
void applyChannelReactionsSlice(
|
||||
const QString &offset,
|
||||
const StoryViews &slice);
|
||||
|
||||
[[nodiscard]] const std::vector<StoryLocation> &locations() const;
|
||||
[[nodiscard]] auto suggestedReactions() const
|
||||
-> const std::vector<SuggestedReaction> &;
|
||||
[[nodiscard]] auto channelPosts() const
|
||||
-> const std::vector<ChannelPost> &;
|
||||
|
||||
void applyChanges(
|
||||
StoryMedia media,
|
||||
|
@ -182,6 +203,7 @@ public:
|
|||
[[nodiscard]] TimeId lastUpdateTime() const;
|
||||
|
||||
[[nodiscard]] bool repost() const;
|
||||
[[nodiscard]] bool repostModified() const;
|
||||
[[nodiscard]] PeerData *repostSourcePeer() const;
|
||||
[[nodiscard]] QString repostSourceName() const;
|
||||
[[nodiscard]] StoryId repostSourceId() const;
|
||||
|
@ -189,6 +211,7 @@ public:
|
|||
private:
|
||||
struct ViewsCounts {
|
||||
int views = 0;
|
||||
int forwards = 0;
|
||||
int reactions = 0;
|
||||
base::flat_map<Data::ReactionId, int> reactionsCounts;
|
||||
std::vector<not_null<PeerData*>> viewers;
|
||||
|
@ -217,7 +240,9 @@ private:
|
|||
std::vector<not_null<PeerData*>> _recentViewers;
|
||||
std::vector<StoryLocation> _locations;
|
||||
std::vector<SuggestedReaction> _suggestedReactions;
|
||||
std::vector<ChannelPost> _channelPosts;
|
||||
StoryViews _views;
|
||||
StoryViews _channelReactions;
|
||||
const TimeId _date = 0;
|
||||
const TimeId _expires = 0;
|
||||
TimeId _lastUpdateTime = 0;
|
||||
|
@ -227,6 +252,7 @@ private:
|
|||
bool _privacyCloseFriends : 1 = false;
|
||||
bool _privacyContacts : 1 = false;
|
||||
bool _privacySelectedContacts : 1 = false;
|
||||
const bool _repostModified : 1 = false;
|
||||
bool _noForwards : 1 = false;
|
||||
bool _edited : 1 = false;
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_peer_bot_command.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "data/data_emoji_statuses.h"
|
||||
#include "data/data_wall_paper.h"
|
||||
#include "data/notify/data_notify_settings.h"
|
||||
#include "history/history.h"
|
||||
|
@ -78,23 +77,6 @@ void UserData::setPhoto(const MTPUserProfilePhoto &photo) {
|
|||
});
|
||||
}
|
||||
|
||||
void UserData::setEmojiStatus(const MTPEmojiStatus &status) {
|
||||
const auto parsed = Data::ParseEmojiStatus(status);
|
||||
setEmojiStatus(parsed.id, parsed.until);
|
||||
}
|
||||
|
||||
void UserData::setEmojiStatus(DocumentId emojiStatusId, TimeId until) {
|
||||
if (_emojiStatusId != emojiStatusId) {
|
||||
_emojiStatusId = emojiStatusId;
|
||||
session().changes().peerUpdated(this, UpdateFlag::EmojiStatus);
|
||||
}
|
||||
owner().emojiStatuses().registerAutomaticClear(this, until);
|
||||
}
|
||||
|
||||
DocumentId UserData::emojiStatusId() const {
|
||||
return _emojiStatusId;
|
||||
}
|
||||
|
||||
auto UserData::unavailableReasons() const
|
||||
-> const std::vector<Data::UnavailableReason> & {
|
||||
return _unavailableReasons;
|
||||
|
|
|
@ -76,7 +76,6 @@ public:
|
|||
|
||||
UserData(not_null<Data::Session*> owner, PeerId id);
|
||||
void setPhoto(const MTPUserProfilePhoto &photo);
|
||||
void setEmojiStatus(const MTPEmojiStatus &status);
|
||||
|
||||
void setName(
|
||||
const QString &newFirstName,
|
||||
|
@ -85,9 +84,6 @@ public:
|
|||
const QString &newUsername);
|
||||
void setUsernames(const Data::Usernames &newUsernames);
|
||||
|
||||
void setEmojiStatus(DocumentId emojiStatusId, TimeId until = 0);
|
||||
[[nodiscard]] DocumentId emojiStatusId() const;
|
||||
|
||||
void setUsername(const QString &username);
|
||||
void setPhone(const QString &newPhone);
|
||||
void setBotInfoVersion(int version);
|
||||
|
@ -199,8 +195,6 @@ private:
|
|||
static constexpr auto kInaccessibleAccessHashOld
|
||||
= 0xFFFFFFFFFFFFFFFFULL;
|
||||
|
||||
DocumentId _emojiStatusId = 0;
|
||||
|
||||
};
|
||||
|
||||
namespace Data {
|
||||
|
|
|
@ -198,9 +198,14 @@ WallPaperId WallPaper::id() const {
|
|||
return _id;
|
||||
}
|
||||
|
||||
QString WallPaper::emojiId() const {
|
||||
return _emojiId;
|
||||
}
|
||||
|
||||
bool WallPaper::equals(const WallPaper &paper) const {
|
||||
return (_flags == paper._flags)
|
||||
&& (_slug == paper._slug)
|
||||
&& (_emojiId == paper._emojiId)
|
||||
&& (_backgroundColors == paper._backgroundColors)
|
||||
&& (_rotation == paper._rotation)
|
||||
&& (_intensity == paper._intensity)
|
||||
|
@ -362,6 +367,7 @@ MTPWallPaperSettings WallPaper::mtpSettings() const {
|
|||
MTP_flags((_blurred ? Flag::f_blur : Flag(0))
|
||||
| Flag::f_intensity
|
||||
| Flag::f_rotation
|
||||
| (_emojiId.isEmpty() ? Flag() : Flag::f_emoticon)
|
||||
| flagForIndex(0)
|
||||
| flagForIndex(1)
|
||||
| flagForIndex(2)
|
||||
|
@ -371,7 +377,8 @@ MTPWallPaperSettings WallPaper::mtpSettings() const {
|
|||
serializeForIndex(2),
|
||||
serializeForIndex(3),
|
||||
MTP_int(_intensity),
|
||||
MTP_int(_rotation));
|
||||
MTP_int(_rotation),
|
||||
MTP_string(_emojiId));
|
||||
}
|
||||
|
||||
WallPaper WallPaper::withUrlParams(
|
||||
|
@ -519,6 +526,7 @@ std::optional<WallPaper> WallPaper::Create(const MTPDwallPaperNoFile &data) {
|
|||
if (const auto rotation = data.vrotation()) {
|
||||
result._rotation = rotation->v;
|
||||
}
|
||||
result._emojiId = qs(data.vemoticon().value_or_empty());
|
||||
});
|
||||
}
|
||||
return result;
|
||||
|
@ -690,6 +698,12 @@ std::optional<WallPaper> WallPaper::FromColorsSlug(const QString &slug) {
|
|||
return result;
|
||||
}
|
||||
|
||||
WallPaper WallPaper::FromEmojiId(const QString &emojiId) {
|
||||
auto result = WallPaper(0);
|
||||
result._emojiId = emojiId;
|
||||
return result;
|
||||
}
|
||||
|
||||
WallPaper WallPaper::ConstructDefault() {
|
||||
auto result = WallPaper(
|
||||
kDefaultBackground
|
||||
|
|
|
@ -45,6 +45,7 @@ public:
|
|||
[[nodiscard]] bool equals(const WallPaper &paper) const;
|
||||
|
||||
[[nodiscard]] WallPaperId id() const;
|
||||
[[nodiscard]] QString emojiId() const;
|
||||
[[nodiscard]] bool isNull() const;
|
||||
[[nodiscard]] QString key() const;
|
||||
[[nodiscard]] const std::vector<QColor> backgroundColors() const;
|
||||
|
@ -102,6 +103,7 @@ public:
|
|||
qint32 legacyId);
|
||||
[[nodiscard]] static std::optional<WallPaper> FromColorsSlug(
|
||||
const QString &slug);
|
||||
[[nodiscard]] static WallPaper FromEmojiId(const QString &emojiId);
|
||||
[[nodiscard]] static WallPaper ConstructDefault();
|
||||
|
||||
private:
|
||||
|
@ -114,6 +116,7 @@ private:
|
|||
UserId _ownerId = 0;
|
||||
WallPaperFlags _flags;
|
||||
QString _slug;
|
||||
QString _emojiId;
|
||||
|
||||
std::vector<QColor> _backgroundColors;
|
||||
int _rotation = 0;
|
||||
|
|
|
@ -141,7 +141,7 @@ WebPageType ParseWebPageType(
|
|||
const QString &type,
|
||||
const QString &embedUrl,
|
||||
bool hasIV) {
|
||||
if (type == u"video"_q || !embedUrl.isEmpty()) {
|
||||
if (type == u"video"_q || type == u"gif"_q || !embedUrl.isEmpty()) {
|
||||
return WebPageType::Video;
|
||||
} else if (type == u"photo"_q) {
|
||||
return WebPageType::Photo;
|
||||
|
|
|
@ -54,7 +54,8 @@ StickersSetFlags ParseStickersSetFlags(const MTPDstickerSet &data) {
|
|||
| (data.is_emojis() ? Flag::Emoji : Flag())
|
||||
| (data.vinstalled_date() ? Flag::Installed : Flag())
|
||||
| (data.is_videos() ? Flag::Webm : Flag())
|
||||
| (data.is_text_color() ? Flag::TextColor : Flag());
|
||||
| (data.is_text_color() ? Flag::TextColor : Flag())
|
||||
| (data.is_channel_emoji_status() ? Flag::ChannelStatus : Flag());
|
||||
}
|
||||
|
||||
StickersSet::StickersSet(
|
||||
|
@ -113,6 +114,10 @@ bool StickersSet::textColor() const {
|
|||
return flags & StickersSetFlag::TextColor;
|
||||
}
|
||||
|
||||
bool StickersSet::channelStatus() const {
|
||||
return flags & StickersSetFlag::ChannelStatus;
|
||||
}
|
||||
|
||||
void StickersSet::setThumbnail(const ImageWithLocation &data) {
|
||||
Data::UpdateCloudFile(
|
||||
_thumbnail,
|
||||
|
|
|
@ -58,6 +58,7 @@ enum class StickersSetFlag {
|
|||
Webm = (1 << 8),
|
||||
Emoji = (1 << 9),
|
||||
TextColor = (1 << 10),
|
||||
ChannelStatus = (1 << 11),
|
||||
};
|
||||
inline constexpr bool is_flag_type(StickersSetFlag) { return true; };
|
||||
using StickersSetFlags = base::flags<StickersSetFlag>;
|
||||
|
@ -86,6 +87,7 @@ public:
|
|||
[[nodiscard]] StickerSetIdentifier identifier() const;
|
||||
[[nodiscard]] StickersType type() const;
|
||||
[[nodiscard]] bool textColor() const;
|
||||
[[nodiscard]] bool channelStatus() const;
|
||||
|
||||
void setThumbnail(const ImageWithLocation &data);
|
||||
|
||||
|
|
|
@ -396,7 +396,7 @@ dialogsPremiumIcon: ThreeStateIcon {
|
|||
historySendingIcon: icon {{ "dialogs/dialogs_sending", historySendingOutIconFg, point(5px, 5px) }};
|
||||
historySendingInvertedIcon: icon {{ "dialogs/dialogs_sending", historySendingInvertedIconFg, point(5px, 5px) }};
|
||||
historyViewsSendingIcon: icon {{ "dialogs/dialogs_sending", historySendingInIconFg, point(3px, 0px) }};
|
||||
historyViewsSendingInvertedIcon: icon {{ "dialogs/dialogs_sending", historySendingInvertedIconFg, point(3px, 0px) }};
|
||||
historyViewsSendingInvertedIcon: icon {{ "dialogs/dialogs_sending", historySendingInvertedIconFg, point(3px, 0px) }};
|
||||
|
||||
dialogsUpdateButton: FlatButton {
|
||||
color: activeButtonFg;
|
||||
|
|
|
@ -2979,7 +2979,7 @@ RowDescriptor Widget::resolveChatNext(RowDescriptor from) const {
|
|||
}
|
||||
|
||||
RowDescriptor Widget::resolveChatPrevious(RowDescriptor from) const {
|
||||
return _inner->resolveChatPrevious(from);
|
||||
return _inner->resolveChatPrevious(from);
|
||||
}
|
||||
|
||||
void Widget::keyPressEvent(QKeyEvent *e) {
|
||||
|
|
|
@ -52,6 +52,112 @@ QString PrepareStoryFileName(
|
|||
+ extension;
|
||||
}
|
||||
|
||||
std::vector<std::vector<HistoryMessageMarkupButton>> ButtonRowsFromTL(
|
||||
const MTPDreplyInlineMarkup &data) {
|
||||
const auto &list = data.vrows().v;
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
using Type = HistoryMessageMarkupButton::Type;
|
||||
auto rows = std::vector<std::vector<HistoryMessageMarkupButton>>();
|
||||
rows.reserve(list.size());
|
||||
|
||||
for (const auto &tlRow : list) {
|
||||
auto row = std::vector<HistoryMessageMarkupButton>();
|
||||
row.reserve(tlRow.data().vbuttons().v.size());
|
||||
for (const auto &button : tlRow.data().vbuttons().v) {
|
||||
button.match([&](const MTPDkeyboardButton &data) {
|
||||
row.push_back({ Type::Default, qs(data.vtext()) });
|
||||
}, [&](const MTPDkeyboardButtonCallback &data) {
|
||||
row.push_back({
|
||||
(data.is_requires_password()
|
||||
? Type::CallbackWithPassword
|
||||
: Type::Callback),
|
||||
qs(data.vtext()),
|
||||
qba(data.vdata())
|
||||
});
|
||||
}, [&](const MTPDkeyboardButtonRequestGeoLocation &data) {
|
||||
row.push_back({ Type::RequestLocation, qs(data.vtext()) });
|
||||
}, [&](const MTPDkeyboardButtonRequestPhone &data) {
|
||||
row.push_back({ Type::RequestPhone, qs(data.vtext()) });
|
||||
}, [&](const MTPDkeyboardButtonRequestPeer &data) {
|
||||
row.push_back({
|
||||
Type::RequestPeer,
|
||||
qs(data.vtext()),
|
||||
QByteArray("unsupported"),
|
||||
QString(),
|
||||
int64(data.vbutton_id().v),
|
||||
});
|
||||
}, [&](const MTPDkeyboardButtonUrl &data) {
|
||||
row.push_back({
|
||||
Type::Url,
|
||||
qs(data.vtext()),
|
||||
qba(data.vurl())
|
||||
});
|
||||
}, [&](const MTPDkeyboardButtonSwitchInline &data) {
|
||||
const auto type = data.is_same_peer()
|
||||
? Type::SwitchInlineSame
|
||||
: Type::SwitchInline;
|
||||
row.push_back({ type, qs(data.vtext()), qba(data.vquery()) });
|
||||
}, [&](const MTPDkeyboardButtonGame &data) {
|
||||
row.push_back({ Type::Game, qs(data.vtext()) });
|
||||
}, [&](const MTPDkeyboardButtonBuy &data) {
|
||||
row.push_back({ Type::Buy, qs(data.vtext()) });
|
||||
}, [&](const MTPDkeyboardButtonUrlAuth &data) {
|
||||
row.push_back({
|
||||
Type::Auth,
|
||||
qs(data.vtext()),
|
||||
qba(data.vurl()),
|
||||
qs(data.vfwd_text().value_or_empty()),
|
||||
data.vbutton_id().v,
|
||||
});
|
||||
}, [&](const MTPDkeyboardButtonRequestPoll &data) {
|
||||
const auto quiz = [&] {
|
||||
if (!data.vquiz()) {
|
||||
return QByteArray();
|
||||
}
|
||||
return data.vquiz()->match([&](const MTPDboolTrue&) {
|
||||
return QByteArray(1, 1);
|
||||
}, [&](const MTPDboolFalse&) {
|
||||
return QByteArray(1, 0);
|
||||
});
|
||||
}();
|
||||
row.push_back({
|
||||
Type::RequestPoll,
|
||||
qs(data.vtext()),
|
||||
quiz
|
||||
});
|
||||
}, [&](const MTPDkeyboardButtonUserProfile &data) {
|
||||
row.push_back({
|
||||
Type::UserProfile,
|
||||
qs(data.vtext()),
|
||||
QByteArray::number(data.vuser_id().v)
|
||||
});
|
||||
}, [&](const MTPDinputKeyboardButtonUrlAuth &data) {
|
||||
}, [&](const MTPDinputKeyboardButtonUserProfile &data) {
|
||||
}, [&](const MTPDkeyboardButtonWebView &data) {
|
||||
row.push_back({
|
||||
Type::WebView,
|
||||
qs(data.vtext()),
|
||||
data.vurl().v
|
||||
});
|
||||
}, [&](const MTPDkeyboardButtonSimpleWebView &data) {
|
||||
row.push_back({
|
||||
Type::SimpleWebView,
|
||||
qs(data.vtext()),
|
||||
data.vurl().v
|
||||
});
|
||||
});
|
||||
}
|
||||
if (!row.empty()) {
|
||||
rows.push_back(std::move(row));
|
||||
}
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint8 PeerColorIndex(BareId bareId) {
|
||||
|
@ -575,8 +681,8 @@ Poll ParsePoll(const MTPDmessageMediaPoll &data) {
|
|||
return result;
|
||||
}
|
||||
|
||||
Giveaway ParseGiveaway(const MTPDmessageMediaGiveaway &data) {
|
||||
auto result = Giveaway{
|
||||
GiveawayStart ParseGiveaway(const MTPDmessageMediaGiveaway &data) {
|
||||
auto result = GiveawayStart{
|
||||
.untilDate = data.vuntil_date().v,
|
||||
.quantity = data.vquantity().v,
|
||||
.months = data.vmonths().v,
|
||||
|
@ -1090,6 +1196,8 @@ Media ParseMedia(
|
|||
// #TODO export stories
|
||||
}, [&](const MTPDmessageMediaGiveaway &data) {
|
||||
result.content = ParseGiveaway(data);
|
||||
}, [&](const MTPDmessageMediaGiveawayResults &data) {
|
||||
// #TODO export giveaway
|
||||
}, [](const MTPDmessageMediaEmpty &data) {});
|
||||
return result;
|
||||
}
|
||||
|
@ -1327,7 +1435,9 @@ ServiceAction ParseServiceAction(
|
|||
result.content = content;
|
||||
}, [&](const MTPDmessageActionRequestedPeer &data) {
|
||||
auto content = ActionRequestedPeer();
|
||||
content.peerId = ParsePeerId(data.vpeer());
|
||||
for (const auto &peer : data.vpeers().v) {
|
||||
content.peers.push_back(ParsePeerId(peer));
|
||||
}
|
||||
content.buttonId = data.vbutton_id().v;
|
||||
result.content = content;
|
||||
}, [&](const MTPDmessageActionGiftCode &data) {
|
||||
|
@ -1496,6 +1606,14 @@ Message ParseMessage(
|
|||
}
|
||||
context.botId = 0;
|
||||
}
|
||||
if (const auto replyMarkup = data.vreply_markup()) {
|
||||
replyMarkup->match([](const MTPDreplyKeyboardMarkup &) {
|
||||
}, [&](const MTPDreplyInlineMarkup &data) {
|
||||
result.inlineButtonRows = ButtonRowsFromTL(data);
|
||||
}, [](const MTPDreplyKeyboardHide &) {
|
||||
}, [](const MTPDreplyKeyboardForceReply &) {
|
||||
});
|
||||
}
|
||||
result.text = ParseText(
|
||||
data.vmessage(),
|
||||
data.ventities().value_or_empty());
|
||||
|
|
|
@ -197,7 +197,7 @@ struct Poll {
|
|||
bool closed = false;
|
||||
};
|
||||
|
||||
struct Giveaway {
|
||||
struct GiveawayStart {
|
||||
std::vector<ChannelId> channels;
|
||||
TimeId untilDate = 0;
|
||||
int quantity = 0;
|
||||
|
@ -336,7 +336,7 @@ struct Media {
|
|||
Game,
|
||||
Invoice,
|
||||
Poll,
|
||||
Giveaway,
|
||||
GiveawayStart,
|
||||
UnsupportedMedia> content;
|
||||
TimeId ttl = 0;
|
||||
|
||||
|
@ -547,7 +547,7 @@ struct ActionGiftCode {
|
|||
};
|
||||
|
||||
struct ActionRequestedPeer {
|
||||
PeerId peerId = 0;
|
||||
std::vector<PeerId> peers;
|
||||
int buttonId = 0;
|
||||
};
|
||||
|
||||
|
@ -665,6 +665,33 @@ inline bool operator>=(MessageId a, MessageId b) {
|
|||
return !(a < b);
|
||||
}
|
||||
|
||||
struct HistoryMessageMarkupButton {
|
||||
enum class Type {
|
||||
Default,
|
||||
Url,
|
||||
Callback,
|
||||
CallbackWithPassword,
|
||||
RequestPhone,
|
||||
RequestLocation,
|
||||
RequestPoll,
|
||||
RequestPeer,
|
||||
SwitchInline,
|
||||
SwitchInlineSame,
|
||||
Game,
|
||||
Buy,
|
||||
Auth,
|
||||
UserProfile,
|
||||
WebView,
|
||||
SimpleWebView,
|
||||
};
|
||||
|
||||
Type type;
|
||||
QString text;
|
||||
QByteArray data;
|
||||
QString forwardText;
|
||||
int64 buttonId = 0;
|
||||
};
|
||||
|
||||
struct Message {
|
||||
int32 id = 0;
|
||||
TimeId date = 0;
|
||||
|
@ -686,6 +713,7 @@ struct Message {
|
|||
Media media;
|
||||
ServiceAction action;
|
||||
bool out = false;
|
||||
std::vector<std::vector<HistoryMessageMarkupButton>> inlineButtonRows;
|
||||
|
||||
File &file();
|
||||
const File &file() const;
|
||||
|
|
|
@ -613,7 +613,7 @@ private:
|
|||
[[nodiscard]] QByteArray pushPoll(const Data::Poll &data);
|
||||
[[nodiscard]] QByteArray pushGiveaway(
|
||||
const PeersMap &peers,
|
||||
const Data::Giveaway &data);
|
||||
const Data::GiveawayStart &data);
|
||||
|
||||
File _file;
|
||||
QByteArray _composedStart;
|
||||
|
@ -1112,7 +1112,7 @@ auto HtmlWriter::Wrap::pushMessage(
|
|||
if (data.recurringUsed) {
|
||||
return "You were charged " + amount + " via recurring payment";
|
||||
}
|
||||
auto result = "You have successfully transferred "
|
||||
auto result = "You have successfully transferred "
|
||||
+ amount
|
||||
+ " for "
|
||||
+ wrapReplyToLink("this invoice");
|
||||
|
@ -1501,7 +1501,7 @@ QByteArray HtmlWriter::Wrap::pushMedia(
|
|||
return pushPhotoMedia(*photo, basePath);
|
||||
} else if (const auto poll = std::get_if<Poll>(&content)) {
|
||||
return pushPoll(*poll);
|
||||
} else if (const auto giveaway = std::get_if<Giveaway>(&content)) {
|
||||
} else if (const auto giveaway = std::get_if<GiveawayStart>(&content)) {
|
||||
return pushGiveaway(peers, *giveaway);
|
||||
}
|
||||
Assert(v::is_null(content));
|
||||
|
@ -1826,7 +1826,7 @@ QByteArray HtmlWriter::Wrap::pushPoll(const Data::Poll &data) {
|
|||
|
||||
QByteArray HtmlWriter::Wrap::pushGiveaway(
|
||||
const PeersMap &peers,
|
||||
const Data::Giveaway &data) {
|
||||
const Data::GiveawayStart &data) {
|
||||
auto result = pushDiv("media_wrap clearfix");
|
||||
result.append(pushDiv("media_giveaway"));
|
||||
|
||||
|
@ -2028,7 +2028,7 @@ MediaData HtmlWriter::Wrap::prepareMediaData(
|
|||
result.description = data.description;
|
||||
result.status = Data::FormatMoneyAmount(data.amount, data.currency);
|
||||
}, [](const Poll &data) {
|
||||
}, [](const Giveaway &data) {
|
||||
}, [](const GiveawayStart &data) {
|
||||
}, [](const UnsupportedMedia &data) {
|
||||
Unexpected("Unsupported message.");
|
||||
}, [](v::null_t) {});
|
||||
|
|
|
@ -592,7 +592,11 @@ QByteArray SerializeMessage(
|
|||
pushActor();
|
||||
pushAction("requested_peer");
|
||||
push("button_id", data.buttonId);
|
||||
push("peer_id", data.peerId.value);
|
||||
auto values = std::vector<QByteArray>();
|
||||
for (const auto &one : data.peers) {
|
||||
values.push_back(Data::NumberToString(one.value));
|
||||
}
|
||||
push("peers", SerializeArray(context, values));
|
||||
}, [&](const ActionGiftCode &data) {
|
||||
pushAction("gift_code_prize");
|
||||
push("gift_code", data.code);
|
||||
|
@ -753,7 +757,7 @@ QByteArray SerializeMessage(
|
|||
{ "total_voters", NumberToString(data.totalVotes) },
|
||||
{ "answers", serialized }
|
||||
}));
|
||||
}, [&](const Giveaway &data) {
|
||||
}, [&](const GiveawayStart &data) {
|
||||
context.nesting.push_back(Context::kObject);
|
||||
const auto channels = ranges::views::all(
|
||||
data.channels
|
||||
|
@ -776,6 +780,76 @@ QByteArray SerializeMessage(
|
|||
pushBare("text", SerializeText(context, message.text));
|
||||
pushBare("text_entities", SerializeText(context, message.text, true));
|
||||
|
||||
if (!message.inlineButtonRows.empty()) {
|
||||
const auto typeString = [](
|
||||
const HistoryMessageMarkupButton &entry) -> QByteArray {
|
||||
using Type = HistoryMessageMarkupButton::Type;
|
||||
switch (entry.type) {
|
||||
case Type::Default: return "default";
|
||||
case Type::Url: return "url";
|
||||
case Type::Callback: return "callback";
|
||||
case Type::CallbackWithPassword: return "callback_with_password";
|
||||
case Type::RequestPhone: return "request_phone";
|
||||
case Type::RequestLocation: return "request_location";
|
||||
case Type::RequestPoll: return "request_poll";
|
||||
case Type::RequestPeer: return "request_peer";
|
||||
case Type::SwitchInline: return "switch_inline";
|
||||
case Type::SwitchInlineSame: return "switch_inline_same";
|
||||
case Type::Game: return "game";
|
||||
case Type::Buy: return "buy";
|
||||
case Type::Auth: return "auth";
|
||||
case Type::UserProfile: return "user_profile";
|
||||
case Type::WebView: return "web_view";
|
||||
case Type::SimpleWebView: return "simple_web_view";
|
||||
}
|
||||
Unexpected("Type in HistoryMessageMarkupButton::Type.");
|
||||
};
|
||||
const auto serializeRow = [&](
|
||||
const std::vector<HistoryMessageMarkupButton> &row) {
|
||||
context.nesting.push_back(Context::kArray);
|
||||
const auto buttons = ranges::views::all(
|
||||
row
|
||||
) | ranges::views::transform([&](
|
||||
const HistoryMessageMarkupButton &entry) {
|
||||
auto pairs = std::vector<std::pair<QByteArray, QByteArray>>();
|
||||
pairs.push_back({
|
||||
"type",
|
||||
SerializeString(typeString(entry)),
|
||||
});
|
||||
if (!entry.text.isEmpty()) {
|
||||
pairs.push_back({
|
||||
"text",
|
||||
SerializeString(entry.text.toUtf8()),
|
||||
});
|
||||
}
|
||||
if (!entry.data.isEmpty()) {
|
||||
pairs.push_back({ "data", SerializeString(entry.data) });
|
||||
}
|
||||
if (!entry.forwardText.isEmpty()) {
|
||||
pairs.push_back({
|
||||
"forward_text",
|
||||
SerializeString(entry.forwardText.toUtf8()),
|
||||
});
|
||||
}
|
||||
if (entry.buttonId) {
|
||||
pairs.push_back({
|
||||
"button_id",
|
||||
NumberToString(entry.buttonId),
|
||||
});
|
||||
}
|
||||
return SerializeObject(context, pairs);
|
||||
}) | ranges::to_vector;
|
||||
context.nesting.pop_back();
|
||||
return SerializeArray(context, buttons);
|
||||
};
|
||||
context.nesting.push_back(Context::kArray);
|
||||
const auto rows = ranges::views::all(
|
||||
message.inlineButtonRows
|
||||
) | ranges::views::transform(serializeRow) | ranges::to_vector;
|
||||
context.nesting.pop_back();
|
||||
pushBare("inline_bot_buttons", SerializeArray(context, rows));
|
||||
}
|
||||
|
||||
return serialized();
|
||||
}
|
||||
|
||||
|
@ -1078,7 +1152,7 @@ Result JsonWriter::writeFrequentContacts(const Data::ContactsList &data) {
|
|||
{ "id", Data::NumberToString(Data::PeerToBareId(top.peer.id())) },
|
||||
{ "category", SerializeString(category) },
|
||||
{ "type", SerializeString(type) },
|
||||
{ "name", StringAllowNull(top.peer.name()) },
|
||||
{ "name", StringAllowNull(top.peer.name()) },
|
||||
{ "rating", Data::NumberToString(top.rating) },
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -1243,7 +1243,7 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
|||
showContextInFolder(lnkDocument);
|
||||
}, &st::menuIconShowInFolder);
|
||||
}
|
||||
_menu->addAction(lnkIsVideo ? tr::lng_context_save_video(tr::now) : (lnkIsVoice ? tr::lng_context_save_audio(tr::now) : (lnkIsAudio ? tr::lng_context_save_audio_file(tr::now) : tr::lng_context_save_file(tr::now))), base::fn_delayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, lnkDocument] {
|
||||
_menu->addAction(lnkIsVideo ? tr::lng_context_save_video(tr::now) : (lnkIsVoice ? tr::lng_context_save_audio(tr::now) : (lnkIsAudio ? tr::lng_context_save_audio_file(tr::now) : tr::lng_context_save_file(tr::now))), base::fn_delayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, lnkDocument] {
|
||||
saveDocumentToFile(lnkDocument);
|
||||
}), &st::menuIconDownload);
|
||||
if (lnkDocument->hasAttachedStickers()) {
|
||||
|
|
|
@ -767,8 +767,10 @@ void GenerateItems(
|
|||
using LogDeleteTopic = MTPDchannelAdminLogEventActionDeleteTopic;
|
||||
using LogPinTopic = MTPDchannelAdminLogEventActionPinTopic;
|
||||
using LogToggleAntiSpam = MTPDchannelAdminLogEventActionToggleAntiSpam;
|
||||
using LogChangeColor = MTPDchannelAdminLogEventActionChangeColor;
|
||||
using LogChangeBackgroundEmoji = MTPDchannelAdminLogEventActionChangeBackgroundEmoji;
|
||||
using LogChangePeerColor = MTPDchannelAdminLogEventActionChangePeerColor;
|
||||
using LogChangeProfilePeerColor = MTPDchannelAdminLogEventActionChangeProfilePeerColor;
|
||||
using LogChangeWallpaper = MTPDchannelAdminLogEventActionChangeWallpaper;
|
||||
using LogChangeEmojiStatus = MTPDchannelAdminLogEventActionChangeEmojiStatus;
|
||||
|
||||
const auto session = &history->session();
|
||||
const auto id = event.vid().v;
|
||||
|
@ -1817,51 +1819,170 @@ void GenerateItems(
|
|||
addSimpleServiceMessage(text);
|
||||
};
|
||||
|
||||
const auto createChangeColor = [&](const LogChangeColor &data) {
|
||||
const auto text = tr::lng_admin_log_change_color(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText,
|
||||
lt_previous,
|
||||
{ '#' + QString::number(data.vprev_value().v + 1) },
|
||||
lt_color,
|
||||
{ '#' + QString::number(data.vnew_value().v + 1) },
|
||||
Ui::Text::WithEntities);
|
||||
addSimpleServiceMessage(text);
|
||||
};
|
||||
|
||||
const auto createChangeBackgroundEmoji = [&](const LogChangeBackgroundEmoji &data) {
|
||||
const auto was = data.vprev_value().v;
|
||||
const auto now = data.vnew_value().v;
|
||||
const auto text = !was
|
||||
? tr::lng_admin_log_set_background_emoji(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText,
|
||||
lt_emoji,
|
||||
Ui::Text::SingleCustomEmoji(
|
||||
Data::SerializeCustomEmojiId(now)),
|
||||
Ui::Text::WithEntities)
|
||||
: !now
|
||||
? tr::lng_admin_log_removed_background_emoji(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText,
|
||||
lt_emoji,
|
||||
Ui::Text::SingleCustomEmoji(
|
||||
Data::SerializeCustomEmojiId(was)),
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_admin_log_change_background_emoji(
|
||||
const auto createColorChange = [&](
|
||||
const MTPPeerColor &was,
|
||||
const MTPPeerColor &now,
|
||||
const auto &colorPhrase,
|
||||
const auto &setEmoji,
|
||||
const auto &removeEmoji,
|
||||
const auto &changeEmoji) {
|
||||
const auto prevColor = was.data().vcolor();
|
||||
const auto nextColor = now.data().vcolor();
|
||||
if (prevColor != nextColor) {
|
||||
const auto wrap = [&](tl::conditional<MTPint> value) {
|
||||
return value
|
||||
? value->v
|
||||
: Data::DecideColorIndex(history->peer->id);
|
||||
};
|
||||
const auto text = colorPhrase(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText,
|
||||
lt_previous,
|
||||
Ui::Text::SingleCustomEmoji(
|
||||
Data::SerializeCustomEmojiId(was)),
|
||||
{ '#' + QString::number(wrap(prevColor) + 1) },
|
||||
lt_color,
|
||||
{ '#' + QString::number(wrap(nextColor) + 1) },
|
||||
Ui::Text::WithEntities);
|
||||
addSimpleServiceMessage(text);
|
||||
}
|
||||
const auto prevEmoji = was.data().vbackground_emoji_id().value_or_empty();
|
||||
const auto nextEmoji = now.data().vbackground_emoji_id().value_or_empty();
|
||||
if (prevEmoji != nextEmoji) {
|
||||
const auto text = !prevEmoji
|
||||
? setEmoji(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText,
|
||||
lt_emoji,
|
||||
Ui::Text::SingleCustomEmoji(
|
||||
Data::SerializeCustomEmojiId(nextEmoji)),
|
||||
Ui::Text::WithEntities)
|
||||
: !nextEmoji
|
||||
? removeEmoji(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText,
|
||||
lt_emoji,
|
||||
Ui::Text::SingleCustomEmoji(
|
||||
Data::SerializeCustomEmojiId(prevEmoji)),
|
||||
Ui::Text::WithEntities)
|
||||
: changeEmoji(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText,
|
||||
lt_previous,
|
||||
Ui::Text::SingleCustomEmoji(
|
||||
Data::SerializeCustomEmojiId(prevEmoji)),
|
||||
lt_emoji,
|
||||
Ui::Text::SingleCustomEmoji(
|
||||
Data::SerializeCustomEmojiId(nextEmoji)),
|
||||
Ui::Text::WithEntities);
|
||||
addSimpleServiceMessage(text);
|
||||
}
|
||||
};
|
||||
|
||||
const auto createChangePeerColor = [&](const LogChangePeerColor &data) {
|
||||
createColorChange(
|
||||
data.vprev_value(),
|
||||
data.vnew_value(),
|
||||
tr::lng_admin_log_change_color,
|
||||
tr::lng_admin_log_set_background_emoji,
|
||||
tr::lng_admin_log_removed_background_emoji,
|
||||
tr::lng_admin_log_change_background_emoji);
|
||||
};
|
||||
|
||||
const auto createChangeProfilePeerColor = [&](const LogChangeProfilePeerColor &data) {
|
||||
createColorChange(
|
||||
data.vprev_value(),
|
||||
data.vnew_value(),
|
||||
tr::lng_admin_log_change_profile_color,
|
||||
tr::lng_admin_log_set_profile_background_emoji,
|
||||
tr::lng_admin_log_removed_profile_background_emoji,
|
||||
tr::lng_admin_log_change_profile_background_emoji);
|
||||
};
|
||||
|
||||
const auto createChangeWallpaper = [&](const LogChangeWallpaper &data) {
|
||||
addSimpleServiceMessage(tr::lng_admin_log_change_wallpaper(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText,
|
||||
Ui::Text::WithEntities));
|
||||
};
|
||||
|
||||
const auto createChangeEmojiStatus = [&](const LogChangeEmojiStatus &data) {
|
||||
const auto parse = [](const MTPEmojiStatus &status) {
|
||||
return status.match([](
|
||||
const MTPDemojiStatus &data) {
|
||||
return data.vdocument_id().v;
|
||||
}, [](const MTPDemojiStatusEmpty &) {
|
||||
return DocumentId();
|
||||
}, [](const MTPDemojiStatusUntil &data) {
|
||||
return data.vdocument_id().v;
|
||||
});
|
||||
};
|
||||
const auto prevEmoji = parse(data.vprev_value());
|
||||
const auto nextEmoji = parse(data.vnew_value());
|
||||
const auto nextUntil = data.vnew_value().match([](
|
||||
const MTPDemojiStatusUntil &data) {
|
||||
return data.vuntil().v;
|
||||
}, [](const auto &) { return TimeId(); });
|
||||
|
||||
const auto text = !prevEmoji
|
||||
? (nextUntil
|
||||
? tr::lng_admin_log_set_status_until(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText,
|
||||
lt_emoji,
|
||||
Ui::Text::SingleCustomEmoji(
|
||||
Data::SerializeCustomEmojiId(nextEmoji)),
|
||||
lt_date,
|
||||
TextWithEntities{
|
||||
langDateTime(base::unixtime::parse(nextUntil)) },
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_admin_log_set_status(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText,
|
||||
lt_emoji,
|
||||
Ui::Text::SingleCustomEmoji(
|
||||
Data::SerializeCustomEmojiId(nextEmoji)),
|
||||
Ui::Text::WithEntities))
|
||||
: !nextEmoji
|
||||
? tr::lng_admin_log_removed_status(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText,
|
||||
lt_emoji,
|
||||
Ui::Text::SingleCustomEmoji(
|
||||
Data::SerializeCustomEmojiId(now)),
|
||||
Ui::Text::WithEntities);
|
||||
Data::SerializeCustomEmojiId(prevEmoji)),
|
||||
Ui::Text::WithEntities)
|
||||
: (nextUntil
|
||||
? tr::lng_admin_log_change_status_until(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText,
|
||||
lt_previous,
|
||||
Ui::Text::SingleCustomEmoji(
|
||||
Data::SerializeCustomEmojiId(prevEmoji)),
|
||||
lt_emoji,
|
||||
Ui::Text::SingleCustomEmoji(
|
||||
Data::SerializeCustomEmojiId(nextEmoji)),
|
||||
lt_date,
|
||||
TextWithEntities{
|
||||
langDateTime(base::unixtime::parse(nextUntil)) },
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_admin_log_change_status(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText,
|
||||
lt_previous,
|
||||
Ui::Text::SingleCustomEmoji(
|
||||
Data::SerializeCustomEmojiId(prevEmoji)),
|
||||
lt_emoji,
|
||||
Ui::Text::SingleCustomEmoji(
|
||||
Data::SerializeCustomEmojiId(nextEmoji)),
|
||||
Ui::Text::WithEntities));
|
||||
addSimpleServiceMessage(text);
|
||||
};
|
||||
|
||||
|
@ -1909,8 +2030,10 @@ void GenerateItems(
|
|||
createDeleteTopic,
|
||||
createPinTopic,
|
||||
createToggleAntiSpam,
|
||||
createChangeColor,
|
||||
createChangeBackgroundEmoji);
|
||||
createChangePeerColor,
|
||||
createChangeProfilePeerColor,
|
||||
createChangeWallpaper,
|
||||
createChangeEmojiStatus);
|
||||
}
|
||||
|
||||
} // namespace AdminLog
|
||||
|
|
|
@ -442,43 +442,6 @@ HistoryInner::HistoryInner(
|
|||
_migrated->translateTo(_history->translatedTo());
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (const auto channel = _history->peer->asBroadcast()) {
|
||||
if (channel->amCreator()) {
|
||||
const auto weak = base::make_weak(_controller);
|
||||
channel->session().api().request(MTPpayments_GetPremiumGiftCodeOptions(
|
||||
MTP_flags(MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer),
|
||||
channel->input
|
||||
)).done(crl::guard(weak, [=](const MTPVector<MTPPremiumGiftCodeOption> &result) {
|
||||
if (result.v.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
const auto &data = result.v.front().data();
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
Payments::CheckoutProcess::Start(
|
||||
Payments::InvoicePremiumGiftCode{
|
||||
.purpose = Payments::InvoicePremiumGiftCodeGiveaway{
|
||||
.boostPeer = channel,
|
||||
//.additionalChannels = ,
|
||||
.untilDate = (base::unixtime::now() + 300),
|
||||
.onlyNewSubscribers = true,
|
||||
},
|
||||
.randomId = randomId,
|
||||
.currency = qs(data.vcurrency()),
|
||||
.amount = data.vamount().v,
|
||||
.storeProduct = qs(data.vstore_product().value_or_empty()),
|
||||
.storeQuantity = data.vstore_quantity().value_or_empty(),
|
||||
.users = data.vusers().v,
|
||||
.months = data.vmonths().v,
|
||||
},
|
||||
crl::guard(weak, [=](auto) { weak->window().activate(); }));
|
||||
})).fail(crl::guard(weak, [=](const MTP::Error &error) {
|
||||
weak.get()->showToast(error.type());
|
||||
})).send();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Window::ChatThemeValueFromPeer(
|
||||
controller,
|
||||
_peer
|
||||
|
@ -2573,6 +2536,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
|||
? link->copyToClipboardContextItemText()
|
||||
: QString();
|
||||
|
||||
if (item && item->isSponsored()) {
|
||||
FillSponsoredMessagesMenu(controller, item->fullId(), _menu);
|
||||
}
|
||||
if (isUponSelected > 0) {
|
||||
addReplyAction(item);
|
||||
const auto selectedText = getSelectedText();
|
||||
|
@ -2635,10 +2601,6 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
|||
}, &st::menuIconCopy);
|
||||
}
|
||||
}
|
||||
if (item->isSponsored()) {
|
||||
const auto itemId = item->fullId();
|
||||
FillSponsoredMessagesMenu(controller, itemId, _menu);
|
||||
}
|
||||
if (!item->isService() && view && actionText.isEmpty()) {
|
||||
if (!hasCopyRestriction(item)
|
||||
&& (view->hasVisibleText() || mediaHasTextForCopy)) {
|
||||
|
|
|
@ -316,9 +316,13 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
|
|||
media.vid().v,
|
||||
}, media.is_via_mention());
|
||||
}, [&](const MTPDmessageMediaGiveaway &media) -> Result {
|
||||
return std::make_unique<Data::MediaGiveaway>(
|
||||
return std::make_unique<Data::MediaGiveawayStart>(
|
||||
item,
|
||||
Data::ComputeGiveawayData(item, media));
|
||||
Data::ComputeGiveawayStartData(item, media));
|
||||
}, [&](const MTPDmessageMediaGiveawayResults &media) -> Result {
|
||||
return std::make_unique<Data::MediaGiveawayResults>(
|
||||
item,
|
||||
Data::ComputeGiveawayResultsData(item, media));
|
||||
}, [](const MTPDmessageMediaEmpty &) -> Result {
|
||||
return nullptr;
|
||||
}, [](const MTPDmessageMediaUnsupported &) -> Result {
|
||||
|
@ -809,6 +813,8 @@ HistoryServiceDependentData *HistoryItem::GetServiceDependentData() {
|
|||
return info;
|
||||
} else if (const auto same = Get<HistoryServiceSameBackground>()) {
|
||||
return same;
|
||||
} else if (const auto results = Get<HistoryServiceGiveawayResults>()) {
|
||||
return results;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -3757,6 +3763,8 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) {
|
|||
} else {
|
||||
RemoveComponents(HistoryServiceSameBackground::Bit());
|
||||
}
|
||||
} else if (type == mtpc_messageActionGiveawayResults) {
|
||||
UpdateComponents(HistoryServiceGiveawayResults::Bit());
|
||||
}
|
||||
if (const auto replyTo = message.vreply_to()) {
|
||||
replyTo->match([&](const MTPDmessageReplyHeader &data) {
|
||||
|
@ -3971,7 +3979,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
|||
lt_from,
|
||||
fromLinkText(), // Link 1.
|
||||
lt_user,
|
||||
Ui::Text::Link(user->name(), 2), // Link 2.
|
||||
Ui::Text::Link(user->name(), 2), // Link 2.
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
return result;
|
||||
|
@ -4520,18 +4528,45 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
|||
|
||||
auto prepareRequestedPeer = [&](
|
||||
const MTPDmessageActionRequestedPeer &action) {
|
||||
const auto peerId = peerFromMTP(action.vpeer());
|
||||
const auto peer = history()->owner().peer(peerId);
|
||||
auto result = PreparedServiceText{};
|
||||
result.links.push_back(fromLink());
|
||||
|
||||
const auto &list = action.vpeers().v;
|
||||
for (auto i = 0, count = int(list.size()); i != count; ++i) {
|
||||
const auto id = peerFromMTP(list[i]);
|
||||
|
||||
auto user = _history->owner().peer(id);
|
||||
result.links.push_back(user->createOpenLink());
|
||||
|
||||
auto linkText = Ui::Text::Link(user->name(), 2 + i);
|
||||
if (i == 0) {
|
||||
result.text = linkText;
|
||||
} else if (i + 1 == count) {
|
||||
result.text = tr::lng_action_add_users_and_last(
|
||||
tr::now,
|
||||
lt_accumulated,
|
||||
result.text,
|
||||
lt_user,
|
||||
linkText,
|
||||
Ui::Text::WithEntities);
|
||||
} else {
|
||||
result.text = tr::lng_action_add_users_and_one(
|
||||
tr::now,
|
||||
lt_accumulated,
|
||||
result.text,
|
||||
lt_user,
|
||||
linkText,
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
}
|
||||
|
||||
result.text = tr::lng_action_shared_chat_with_bot(
|
||||
tr::now,
|
||||
lt_chat,
|
||||
Ui::Text::Link(peer->name(), 1),
|
||||
result.text,
|
||||
lt_bot,
|
||||
Ui::Text::Link(history()->peer->name(), 2),
|
||||
Ui::Text::WithEntities);
|
||||
result.links.push_back(peer->createOpenLink());
|
||||
result.links.push_back(history()->peer->createOpenLink());
|
||||
return result;
|
||||
};
|
||||
|
||||
|
@ -4574,19 +4609,35 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
|||
auto prepareGiftCode = [&](const MTPDmessageActionGiftCode &action) {
|
||||
auto result = PreparedServiceText();
|
||||
_history->session().giftBoxStickersPacks().load();
|
||||
result.text = {
|
||||
(action.is_unclaimed()
|
||||
? tr::lng_prize_unclaimed_about
|
||||
: action.is_via_giveaway()
|
||||
? tr::lng_prize_about
|
||||
: tr::lng_prize_gift_about)(
|
||||
if (const auto boosted = action.vboost_peer()) {
|
||||
result.text = {
|
||||
(action.is_unclaimed()
|
||||
? tr::lng_prize_unclaimed_about
|
||||
: action.is_via_giveaway()
|
||||
? tr::lng_prize_about
|
||||
: tr::lng_prize_gift_about)(
|
||||
tr::now,
|
||||
lt_channel,
|
||||
_from->owner().peer(
|
||||
peerFromMTP(*action.vboost_peer()))->name()),
|
||||
};
|
||||
} else {
|
||||
const auto isSelf = (_from->id == _from->session().userPeerId());
|
||||
const auto peer = isSelf ? _history->peer : _from;
|
||||
result.links.push_back(peer->createOpenLink());
|
||||
result.text = (isSelf
|
||||
? tr::lng_action_gift_received_me
|
||||
: tr::lng_action_gift_received)(
|
||||
tr::now,
|
||||
lt_channel,
|
||||
(action.vboost_peer()
|
||||
? _from->owner().peer(
|
||||
peerFromMTP(*action.vboost_peer()))->name()
|
||||
: "a channel")),
|
||||
};
|
||||
lt_user,
|
||||
Ui::Text::Link(peer->name(), 1), // Link 1.
|
||||
lt_cost,
|
||||
{ Ui::FillAmountAndCurrency(
|
||||
action.vamount().value_or_empty(),
|
||||
qs(action.vcurrency().value_or_empty())) },
|
||||
Ui::Text::WithEntities);
|
||||
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
@ -4741,7 +4792,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
|
|||
_from,
|
||||
Data::GiftCode{
|
||||
.slug = qs(data.vslug()),
|
||||
.channel = (peerIsChannel(boostedId)
|
||||
.channel = (boostedId
|
||||
? history()->owner().channel(boostedId).get()
|
||||
: nullptr),
|
||||
.months = data.vmonths().v,
|
||||
|
|
|
@ -216,7 +216,7 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
|
|||
lt_channel,
|
||||
Ui::Text::Link(phrase.text, 1), // Link 1.
|
||||
lt_inline_bot,
|
||||
Ui::Text::Link('@' + via->bot->username(), 2), // Link 2.
|
||||
Ui::Text::Link('@' + via->bot->username(), 2), // Link 2.
|
||||
Ui::Text::WithEntities);
|
||||
} else {
|
||||
phrase = tr::lng_forwarded_via(
|
||||
|
@ -224,7 +224,7 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
|
|||
lt_user,
|
||||
Ui::Text::Link(phrase.text, 1), // Link 1.
|
||||
lt_inline_bot,
|
||||
Ui::Text::Link('@' + via->bot->username(), 2), // Link 2.
|
||||
Ui::Text::Link('@' + via->bot->username(), 2), // Link 2.
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -599,6 +599,11 @@ struct HistoryServiceSameBackground
|
|||
, public HistoryServiceDependentData {
|
||||
};
|
||||
|
||||
struct HistoryServiceGiveawayResults
|
||||
: public RuntimeComponent<HistoryServiceGiveawayResults, HistoryItem>
|
||||
, public HistoryServiceDependentData {
|
||||
};
|
||||
|
||||
enum class HistorySelfDestructType {
|
||||
Photo,
|
||||
Video,
|
||||
|
|
|
@ -499,6 +499,8 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
|
|||
: Result::Good;
|
||||
}, [](const MTPDmessageMediaGiveaway &) {
|
||||
return Result::Good;
|
||||
}, [](const MTPDmessageMediaGiveawayResults &) {
|
||||
return Result::Good;
|
||||
}, [](const MTPDmessageMediaUnsupported &) {
|
||||
return Result::Unsupported;
|
||||
});
|
||||
|
|
|
@ -37,10 +37,11 @@ namespace {
|
|||
}
|
||||
|
||||
[[nodiscard]] RequestPeerQuery RequestPeerQueryFromTL(
|
||||
const MTPRequestPeerType &query) {
|
||||
const MTPDkeyboardButtonRequestPeer &query) {
|
||||
using Type = RequestPeerQuery::Type;
|
||||
using Restriction = RequestPeerQuery::Restriction;
|
||||
auto result = RequestPeerQuery();
|
||||
result.maxQuantity = query.vmax_quantity().v;
|
||||
const auto restriction = [](const MTPBool *value) {
|
||||
return !value
|
||||
? Restriction::Any
|
||||
|
@ -51,7 +52,7 @@ namespace {
|
|||
const auto rights = [](const MTPChatAdminRights *value) {
|
||||
return value ? ChatAdminRightsInfo(*value).flags : ChatAdminRights();
|
||||
};
|
||||
query.match([&](const MTPDrequestPeerTypeUser &data) {
|
||||
query.vpeer_type().match([&](const MTPDrequestPeerTypeUser &data) {
|
||||
result.type = Type::User;
|
||||
result.userIsBot = restriction(data.vbot());
|
||||
result.userIsPremium = restriction(data.vpremium());
|
||||
|
@ -134,7 +135,7 @@ void HistoryMessageMarkupData::fillRows(
|
|||
}, [&](const MTPDkeyboardButtonRequestPhone &data) {
|
||||
row.emplace_back(Type::RequestPhone, qs(data.vtext()));
|
||||
}, [&](const MTPDkeyboardButtonRequestPeer &data) {
|
||||
const auto query = RequestPeerQueryFromTL(data.vpeer_type());
|
||||
const auto query = RequestPeerQueryFromTL(data);
|
||||
row.emplace_back(
|
||||
Type::RequestPeer,
|
||||
qs(data.vtext()),
|
||||
|
|
|
@ -45,6 +45,8 @@ struct RequestPeerQuery {
|
|||
Yes,
|
||||
No,
|
||||
};
|
||||
|
||||
int maxQuantity = 0;
|
||||
Type type = Type::User;
|
||||
Restriction userIsBot = Restriction::Any;
|
||||
Restriction userIsPremium = Restriction::Any;
|
||||
|
|
|
@ -4590,7 +4590,7 @@ bool HistoryWidget::eventFilter(QObject *obj, QEvent *e) {
|
|||
if (k->key() == Qt::Key_Up) {
|
||||
#ifdef Q_OS_MAC
|
||||
// Cmd + Up is used instead of Home.
|
||||
if (!_field->textCursor().atStart()) {
|
||||
if (HasSendText(_field)) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
@ -4598,7 +4598,7 @@ bool HistoryWidget::eventFilter(QObject *obj, QEvent *e) {
|
|||
} else if (k->key() == Qt::Key_Down) {
|
||||
#ifdef Q_OS_MAC
|
||||
// Cmd + Down is used instead of End.
|
||||
if (!_field->textCursor().atEnd()) {
|
||||
if (HasSendText(_field)) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -98,16 +98,15 @@ namespace {
|
|||
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> PeerCustomStatus(
|
||||
not_null<PeerData*> peer) {
|
||||
const auto user = peer->asUser();
|
||||
if (!user) {
|
||||
if (peer->isChat()) {
|
||||
return rpl::single(TextWithEntities());
|
||||
}
|
||||
const auto owner = &user->owner();
|
||||
return user->session().changes().peerFlagsValue(
|
||||
user,
|
||||
const auto owner = &peer->owner();
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::EmojiStatus
|
||||
) | rpl::map([=] {
|
||||
const auto id = user->emojiStatusId();
|
||||
const auto id = peer->emojiStatusId();
|
||||
return id
|
||||
? ResolveIsCustom(owner, id)
|
||||
: rpl::single(TextWithEntities());
|
||||
|
@ -715,8 +714,9 @@ void ContactStatus::setupShareHandler(not_null<UserData*> user) {
|
|||
|
||||
void ContactStatus::setupUnarchiveHandler(not_null<PeerData*> peer) {
|
||||
_inner->unarchiveClicks(
|
||||
) | rpl::start_with_next([=] {
|
||||
Window::ToggleHistoryArchived(peer->owner().history(peer), false);
|
||||
) | rpl::start_with_next([=, show = _controller->uiShow()] {
|
||||
using namespace Window;
|
||||
ToggleHistoryArchived(show, peer->owner().history(peer), false);
|
||||
peer->owner().notifySettings().resetToDefault(peer);
|
||||
if (const auto settings = peer->settings()) {
|
||||
const auto flags = PeerSetting::AutoArchived
|
||||
|
|
|
@ -809,7 +809,7 @@ auto Element::contextDependentServiceText() -> TextWithLinks {
|
|||
return {};
|
||||
}
|
||||
const auto from = item->from();
|
||||
const auto topicUrl = u"internal:url:https://t.me/c/%1/%2"_q
|
||||
const auto topicUrl = u"internal:url:https://t.me/c/%1/%2"_q
|
||||
.arg(peerToChannel(peerId).bare)
|
||||
.arg(topicRootId.bare);
|
||||
const auto fromLink = [&](int index) {
|
||||
|
@ -1392,6 +1392,14 @@ bool Element::allowTextSelectionByHandler(
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Element::usesBubblePattern(const PaintContext &context) const {
|
||||
return (context.selection != FullSelection)
|
||||
&& hasOutLayout()
|
||||
&& context.bubblesPattern
|
||||
&& !context.viewport.isEmpty()
|
||||
&& !context.bubblesPattern->pixmap.size().isEmpty();
|
||||
}
|
||||
|
||||
bool Element::hasVisibleText() const {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -465,6 +465,8 @@ public:
|
|||
[[nodiscard]] virtual bool allowTextSelectionByHandler(
|
||||
const ClickHandlerPtr &handler) const;
|
||||
|
||||
[[nodiscard]] bool usesBubblePattern(const PaintContext &context) const;
|
||||
|
||||
struct VerticalRepaintRange {
|
||||
int top = 0;
|
||||
int height = 0;
|
||||
|
|
|
@ -182,7 +182,7 @@ void KeyboardStyle::paintButtonBg(
|
|||
const auto &small = sti->msgServiceBgCornersSmall;
|
||||
const auto &large = sti->msgServiceBgCornersLarge;
|
||||
auto corners = Ui::CornersPixmaps();
|
||||
int radiuses[4];
|
||||
int radiuses[4];
|
||||
for (auto i = 0; i != 4; ++i) {
|
||||
const auto isLarge = (rounding[i] == Corner::Large);
|
||||
corners.p[i] = (isLarge ? large : small).p[i];
|
||||
|
@ -403,6 +403,11 @@ Message::Message(
|
|||
, _bottomInfo(
|
||||
&data->history()->owner().reactions(),
|
||||
BottomInfoDataFromMessage(this)) {
|
||||
if (const auto media = data->media()) {
|
||||
if (media->giveawayResults()) {
|
||||
_hideReply = 1;
|
||||
}
|
||||
}
|
||||
initLogEntryOriginal();
|
||||
initPsa();
|
||||
refreshReactions();
|
||||
|
@ -1405,13 +1410,12 @@ void Message::paintFromName(
|
|||
const auto y = trect.top();
|
||||
auto color = nameFg;
|
||||
color.setAlpha(115);
|
||||
const auto user = from->asUser();
|
||||
const auto id = user ? user->emojiStatusId() : 0;
|
||||
const auto id = from ? from->emojiStatusId() : 0;
|
||||
if (_fromNameStatus->id != id) {
|
||||
const auto that = const_cast<Message*>(this);
|
||||
_fromNameStatus->custom = id
|
||||
? std::make_unique<Ui::Text::LimitedLoopsEmoji>(
|
||||
user->owner().customEmojiManager().create(
|
||||
history()->owner().customEmojiManager().create(
|
||||
id,
|
||||
[=] { that->customEmojiRepaint(); }),
|
||||
kPlayStatusLimit)
|
||||
|
@ -3012,7 +3016,8 @@ void Message::validateFromNameText(PeerData *from) const {
|
|||
from->name(),
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
if (from->isPremium()) {
|
||||
if (from->isPremium()
|
||||
|| (from->isChannel() && from != history()->peer)) {
|
||||
if (!_fromNameStatus) {
|
||||
_fromNameStatus = std::make_unique<FromNameStatus>();
|
||||
const auto size = st::emojiSize;
|
||||
|
|
|
@ -673,6 +673,8 @@ TextState Service::textState(QPoint point, StateRequest request) const {
|
|||
}
|
||||
} else if (const auto same = item->Get<HistoryServiceSameBackground>()) {
|
||||
result.link = same->lnk;
|
||||
} else if (const auto results = item->Get<HistoryServiceGiveawayResults>()) {
|
||||
result.link = results->lnk;
|
||||
} else if (media && data()->showSimilarChannels()) {
|
||||
result = media->textState(mediaPoint, request);
|
||||
}
|
||||
|
|
|
@ -23,14 +23,21 @@ namespace {
|
|||
|
||||
[[nodiscard]] ClickHandlerPtr MakeMediaButtonClickHandler(
|
||||
not_null<Data::Media*> media) {
|
||||
const auto giveaway = media->giveaway();
|
||||
Assert(giveaway != nullptr);
|
||||
const auto start = media->giveawayStart();
|
||||
const auto results = media->giveawayResults();
|
||||
Assert(start || results);
|
||||
|
||||
const auto peer = media->parent()->history()->peer;
|
||||
const auto messageId = media->parent()->id;
|
||||
if (media->parent()->isSending() || media->parent()->hasFailed()) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto info = *giveaway;
|
||||
const auto maybeStart = start
|
||||
? *start
|
||||
: std::optional<Data::GiveawayStart>();
|
||||
const auto maybeResults = results
|
||||
? *results
|
||||
: std::optional<Data::GiveawayResults>();
|
||||
return std::make_shared<LambdaClickHandler>([=](
|
||||
ClickContext context) {
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
|
@ -38,13 +45,18 @@ namespace {
|
|||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
ResolveGiveawayInfo(controller, peer, messageId, info);
|
||||
ResolveGiveawayInfo(
|
||||
controller,
|
||||
peer,
|
||||
messageId,
|
||||
maybeStart,
|
||||
maybeResults);
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] QString MakeMediaButtonText(not_null<Data::Media*> media) {
|
||||
const auto giveaway = media->giveaway();
|
||||
Assert(giveaway != nullptr);
|
||||
Expects(media->giveawayStart() || media->giveawayResults());
|
||||
|
||||
return Ui::Text::Upper(tr::lng_prizes_how_works(tr::now));
|
||||
}
|
||||
|
||||
|
@ -72,7 +84,7 @@ struct ViewButton::Inner {
|
|||
};
|
||||
|
||||
bool ViewButton::MediaHasViewButton(not_null<Data::Media*> media) {
|
||||
return (media->giveaway() != nullptr);
|
||||
return media->giveawayStart() || media->giveawayResults();
|
||||
}
|
||||
|
||||
ViewButton::Inner::Inner(
|
||||
|
|
|
@ -942,13 +942,13 @@ void Gif::drawCornerStatus(
|
|||
const auto statusX = position.x() + st::msgDateImgDelta + padding.x();
|
||||
const auto statusY = position.y() + st::msgDateImgDelta + padding.y();
|
||||
const auto around = style::rtlrect(statusX - padding.x(), statusY - padding.y(), statusW, statusH, width());
|
||||
const auto statusTextTop = statusY + (cornerDownload ? (((statusH - 2 * st::normalFont->height) / 3) - padding.y()) : 0);
|
||||
const auto statusTextTop = statusY + (cornerDownload ? (((statusH - 2 * st::normalFont->height) / 3) - padding.y()) : 0);
|
||||
Ui::FillRoundRect(p, around, sti->msgDateImgBg, sti->msgDateImgBgCorners);
|
||||
p.setFont(st::normalFont);
|
||||
p.setPen(st->msgDateImgFg());
|
||||
p.drawTextLeft(statusX + addLeft, statusTextTop, width(), text, statusW - 2 * padding.x());
|
||||
if (cornerDownload) {
|
||||
const auto downloadTextTop = statusY + st::normalFont->height + (2 * (statusH - 2 * st::normalFont->height) / 3) - padding.y();
|
||||
const auto downloadTextTop = statusY + st::normalFont->height + (2 * (statusH - 2 * st::normalFont->height) / 3) - padding.y();
|
||||
p.drawTextLeft(statusX + addLeft, downloadTextTop, width(), _downloadSize, statusW - 2 * padding.x());
|
||||
const auto inner = QRect(statusX + padding.y() - padding.x(), statusY, st::historyVideoDownloadSize, st::historyVideoDownloadSize);
|
||||
const auto &icon = _data->loading()
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,7 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/media/history_view_sticker.h"
|
||||
|
||||
namespace Data {
|
||||
struct Giveaway;
|
||||
struct GiveawayStart;
|
||||
struct GiveawayResults;
|
||||
} // namespace Data
|
||||
|
||||
namespace Dialogs::Stories {
|
||||
|
@ -24,12 +25,31 @@ class RippleAnimation;
|
|||
|
||||
namespace HistoryView {
|
||||
|
||||
class Giveaway final : public Media {
|
||||
class MediaInBubble final : public Media {
|
||||
public:
|
||||
Giveaway(
|
||||
class Part : public Object {
|
||||
public:
|
||||
virtual ~Part() = default;
|
||||
|
||||
virtual void draw(
|
||||
Painter &p,
|
||||
const PaintContext &context,
|
||||
int outerWidth) const = 0;
|
||||
[[nodiscard]] virtual TextState textState(
|
||||
QPoint point,
|
||||
StateRequest request,
|
||||
int outerWidth) const;
|
||||
virtual void clickHandlerPressedChanged(
|
||||
const ClickHandlerPtr &p,
|
||||
bool pressed);
|
||||
[[nodiscard]] virtual bool hasHeavyPart();
|
||||
virtual void unloadHeavyPart();
|
||||
};
|
||||
|
||||
MediaInBubble(
|
||||
not_null<Element*> parent,
|
||||
not_null<Data::Giveaway*> giveaway);
|
||||
~Giveaway();
|
||||
Fn<void(Fn<void(std::unique_ptr<Part>)>)> generate);
|
||||
~MediaInBubble();
|
||||
|
||||
void draw(Painter &p, const PaintContext &context) const override;
|
||||
TextState textState(QPoint point, StateRequest request) const override;
|
||||
|
@ -49,7 +69,7 @@ public:
|
|||
}
|
||||
|
||||
bool toggleSelectionByHandlerClick(
|
||||
const ClickHandlerPtr &p) const override {
|
||||
const ClickHandlerPtr &p) const override {
|
||||
return true;
|
||||
}
|
||||
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
|
||||
|
@ -62,8 +82,134 @@ public:
|
|||
bool hasHeavyPart() const override;
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
std::unique_ptr<Part> object;
|
||||
};
|
||||
|
||||
QSize countOptimalSize() override;
|
||||
QSize countCurrentSize(int newWidth) override;
|
||||
|
||||
[[nodiscard]] QMargins inBubblePadding() const;
|
||||
|
||||
std::vector<Entry> _entries;
|
||||
|
||||
};
|
||||
|
||||
class TextMediaInBubblePart final : public MediaInBubble::Part {
|
||||
public:
|
||||
TextMediaInBubblePart(
|
||||
TextWithEntities text,
|
||||
QMargins margins,
|
||||
const base::flat_map<uint16, ClickHandlerPtr> &links = {});
|
||||
|
||||
void draw(
|
||||
Painter &p,
|
||||
const PaintContext &context,
|
||||
int outerWidth) const override;
|
||||
TextState textState(
|
||||
QPoint point,
|
||||
StateRequest request,
|
||||
int outerWidth) const override;
|
||||
|
||||
QSize countOptimalSize() override;
|
||||
QSize countCurrentSize(int newWidth) override;
|
||||
|
||||
private:
|
||||
Ui::Text::String _text;
|
||||
QMargins _margins;
|
||||
|
||||
};
|
||||
|
||||
class TextDelimeterPart final : public MediaInBubble::Part {
|
||||
public:
|
||||
TextDelimeterPart(const QString &text, QMargins margins);
|
||||
|
||||
void draw(
|
||||
Painter &p,
|
||||
const PaintContext &context,
|
||||
int outerWidth) const override;
|
||||
|
||||
QSize countOptimalSize() override;
|
||||
QSize countCurrentSize(int newWidth) override;
|
||||
|
||||
private:
|
||||
Ui::Text::String _text;
|
||||
QMargins _margins;
|
||||
|
||||
};
|
||||
|
||||
class StickerWithBadgePart final : public MediaInBubble::Part {
|
||||
public:
|
||||
struct Data {
|
||||
DocumentData *sticker = nullptr;
|
||||
int skipTop = 0;
|
||||
bool isGiftBoxSticker = false;
|
||||
|
||||
explicit operator bool() const {
|
||||
return sticker != nullptr;
|
||||
}
|
||||
};
|
||||
StickerWithBadgePart(
|
||||
not_null<Element*> parent,
|
||||
Fn<Data()> lookup,
|
||||
QString badge);
|
||||
|
||||
void draw(
|
||||
Painter &p,
|
||||
const PaintContext &context,
|
||||
int outerWidth) const override;
|
||||
bool hasHeavyPart() override;
|
||||
void unloadHeavyPart() override;
|
||||
|
||||
QSize countOptimalSize() override;
|
||||
QSize countCurrentSize(int newWidth) override;
|
||||
|
||||
private:
|
||||
void ensureCreated() const;
|
||||
void validateBadge(const PaintContext &context) const;
|
||||
void paintBadge(Painter &p, const PaintContext &context) const;
|
||||
|
||||
const not_null<Element*> _parent;
|
||||
Fn<Data()> _lookup;
|
||||
QString _badgeText;
|
||||
mutable int _skipTop = 0;
|
||||
mutable std::optional<Sticker> _sticker;
|
||||
mutable QColor _badgeFg;
|
||||
mutable QColor _badgeBorder;
|
||||
mutable QImage _badge;
|
||||
mutable QImage _badgeCache;
|
||||
|
||||
};
|
||||
|
||||
class PeerBubbleListPart final : public MediaInBubble::Part {
|
||||
public:
|
||||
PeerBubbleListPart(
|
||||
not_null<Element*> parent,
|
||||
const std::vector<not_null<PeerData*>> &list);
|
||||
~PeerBubbleListPart();
|
||||
|
||||
void draw(
|
||||
Painter &p,
|
||||
const PaintContext &context,
|
||||
int outerWidth) const override;
|
||||
TextState textState(
|
||||
QPoint point,
|
||||
StateRequest request,
|
||||
int outerWidth) const override;
|
||||
void clickHandlerPressedChanged(
|
||||
const ClickHandlerPtr &p,
|
||||
bool pressed) override;
|
||||
bool hasHeavyPart() override;
|
||||
void unloadHeavyPart() override;
|
||||
|
||||
QSize countOptimalSize() override;
|
||||
QSize countCurrentSize(int newWidth) override;
|
||||
|
||||
private:
|
||||
int layout(int x, int y, int available);
|
||||
|
||||
using Thumbnail = Dialogs::Stories::Thumbnail;
|
||||
struct Channel {
|
||||
struct Peer {
|
||||
Ui::Text::String name;
|
||||
std::shared_ptr<Thumbnail> thumbnail;
|
||||
QRect geometry;
|
||||
|
@ -74,50 +220,21 @@ private:
|
|||
uint8 colorIndex = 0;
|
||||
};
|
||||
|
||||
void paintBadge(Painter &p, const PaintContext &context) const;
|
||||
void paintChannels(Painter &p, const PaintContext &context) const;
|
||||
int layoutChannels(int x, int y, int available);
|
||||
QSize countOptimalSize() override;
|
||||
QSize countCurrentSize(int newWidth) override;
|
||||
|
||||
void fillFromData(not_null<Data::Giveaway*> giveaway);
|
||||
void ensureStickerCreated() const;
|
||||
void validateBadge(const PaintContext &context) const;
|
||||
|
||||
[[nodiscard]] QMargins inBubblePadding() const;
|
||||
|
||||
mutable std::optional<Sticker> _sticker;
|
||||
|
||||
Ui::Text::String _prizesTitle;
|
||||
Ui::Text::String _prizes;
|
||||
Ui::Text::String _participantsTitle;
|
||||
Ui::Text::String _participants;
|
||||
std::vector<Channel> _channels;
|
||||
Ui::Text::String _countries;
|
||||
Ui::Text::String _winnersTitle;
|
||||
Ui::Text::String _winners;
|
||||
|
||||
mutable QColor _badgeFg;
|
||||
mutable QColor _badgeBorder;
|
||||
mutable QImage _badge;
|
||||
mutable QImage _badgeCache;
|
||||
|
||||
const not_null<Element*> _parent;
|
||||
std::vector<Peer> _peers;
|
||||
mutable QPoint _lastPoint;
|
||||
int _months = 0;
|
||||
int _quantity = 0;
|
||||
int _stickerTop = 0;
|
||||
int _prizesTitleTop = 0;
|
||||
int _prizesTop = 0;
|
||||
int _prizesWidth = 0;
|
||||
int _participantsTitleTop = 0;
|
||||
int _participantsTop = 0;
|
||||
int _participantsWidth = 0;
|
||||
int _countriesTop = 0;
|
||||
int _countriesWidth = 0;
|
||||
int _winnersTitleTop = 0;
|
||||
int _winnersTop = 0;
|
||||
mutable uint8 _subscribedToThumbnails : 1 = 0;
|
||||
mutable bool _subscribed = false;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] auto GenerateGiveawayStart(
|
||||
not_null<Element*> parent,
|
||||
not_null<Data::GiveawayStart*> data)
|
||||
-> Fn<void(Fn<void(std::unique_ptr<MediaInBubble::Part>)>)>;
|
||||
|
||||
[[nodiscard]] auto GenerateGiveawayResults(
|
||||
not_null<Element*> parent,
|
||||
not_null<Data::GiveawayResults*> data)
|
||||
-> Fn<void(Fn<void(std::unique_ptr<MediaInBubble::Part>)>)>;
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
|
@ -341,11 +341,7 @@ auto Media::getBubbleSelectionIntervals(
|
|||
}
|
||||
|
||||
bool Media::usesBubblePattern(const PaintContext &context) const {
|
||||
return (context.selection != FullSelection)
|
||||
&& _parent->hasOutLayout()
|
||||
&& context.bubblesPattern
|
||||
&& !context.viewport.isEmpty()
|
||||
&& !context.bubblesPattern->pixmap.size().isEmpty();
|
||||
return _parent->usesBubblePattern(context);
|
||||
}
|
||||
|
||||
PointState Media::pointState(QPoint point) const {
|
||||
|
|
|
@ -459,7 +459,7 @@ PointState GroupedMedia::pointState(QPoint point) const {
|
|||
return PointState::Outside;
|
||||
}
|
||||
const auto groupPadding = groupedPadding();
|
||||
point -= QPoint(0, groupPadding.top());
|
||||
point -= QPoint(0, groupPadding.top());
|
||||
for (const auto &part : _parts) {
|
||||
if (part.geometry.contains(point)) {
|
||||
return PointState::GroupPart;
|
||||
|
|
|
@ -662,10 +662,10 @@ QRect Photo::enlargeRect() const {
|
|||
const auto enlargeInner = st::historyPageEnlargeSize;
|
||||
const auto enlargeOuter = 2 * skip + enlargeInner;
|
||||
return {
|
||||
width() - enlargeOuter + skip,
|
||||
skip,
|
||||
enlargeInner,
|
||||
enlargeInner,
|
||||
width() - enlargeOuter + skip,
|
||||
skip,
|
||||
enlargeInner,
|
||||
enlargeInner,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ QSize PremiumGift::size() {
|
|||
}
|
||||
|
||||
QString PremiumGift::title() {
|
||||
return _data.slug.isEmpty()
|
||||
return gift()
|
||||
? tr::lng_premium_summary_title(tr::now)
|
||||
: _data.unclaimed
|
||||
? tr::lng_prize_unclaimed_title(tr::now)
|
||||
|
@ -51,7 +51,7 @@ QString PremiumGift::title() {
|
|||
}
|
||||
|
||||
TextWithEntities PremiumGift::subtitle() {
|
||||
if (_data.slug.isEmpty()) {
|
||||
if (gift()) {
|
||||
return { GiftDuration(_data.months) };
|
||||
}
|
||||
const auto name = _data.channel ? _data.channel->name() : "channel";
|
||||
|
@ -78,7 +78,7 @@ TextWithEntities PremiumGift::subtitle() {
|
|||
}
|
||||
|
||||
rpl::producer<QString> PremiumGift::button() {
|
||||
return _data.slug.isEmpty()
|
||||
return (gift() && (outgoingGift() || !_data.unclaimed))
|
||||
? tr::lng_sticker_premium_view()
|
||||
: tr::lng_prize_open();
|
||||
}
|
||||
|
@ -90,14 +90,16 @@ ClickHandlerPtr PremiumGift::createViewLink() {
|
|||
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
if (const auto controller = my.sessionWindow.get()) {
|
||||
const auto selfId = controller->session().userPeerId();
|
||||
const auto self = (from->id == selfId);
|
||||
if (data.slug.isEmpty()) {
|
||||
const auto selfId = controller->session().userPeerId();
|
||||
const auto self = (from->id == selfId);
|
||||
const auto peer = self ? to : from;
|
||||
const auto months = data.months;
|
||||
Settings::ShowGiftPremium(controller, peer, months, self);
|
||||
} else {
|
||||
ResolveGiftCode(controller, data.slug);
|
||||
const auto fromId = from->id;
|
||||
const auto toId = self ? to->id : selfId;
|
||||
ResolveGiftCode(controller, data.slug, fromId, toId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -119,7 +121,7 @@ void PremiumGift::draw(
|
|||
}
|
||||
|
||||
bool PremiumGift::hideServiceText() {
|
||||
return !_data.slug.isEmpty();
|
||||
return !gift();
|
||||
}
|
||||
|
||||
void PremiumGift::stickerClearLoopPlayed() {
|
||||
|
@ -146,6 +148,18 @@ void PremiumGift::unloadHeavyPart() {
|
|||
}
|
||||
}
|
||||
|
||||
bool PremiumGift::incomingGift() const {
|
||||
return gift() && !_parent->data()->out();
|
||||
}
|
||||
|
||||
bool PremiumGift::outgoingGift() const {
|
||||
return gift() && _parent->data()->out();
|
||||
}
|
||||
|
||||
bool PremiumGift::gift() const {
|
||||
return _data.slug.isEmpty() || !_data.channel;
|
||||
}
|
||||
|
||||
void PremiumGift::ensureStickerCreated() const {
|
||||
if (_sticker) {
|
||||
return;
|
||||
|
|
|
@ -46,6 +46,9 @@ public:
|
|||
void unloadHeavyPart() override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] bool incomingGift() const;
|
||||
[[nodiscard]] bool outgoingGift() const;
|
||||
[[nodiscard]] bool gift() const;
|
||||
void ensureStickerCreated() const;
|
||||
|
||||
const not_null<Element*> _parent;
|
||||
|
|
|
@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/cached_round_corners.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "window/section_widget.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
@ -471,13 +472,28 @@ ThemeDocumentBox::ThemeDocumentBox(
|
|||
not_null<Element*> parent,
|
||||
const Data::WallPaper &paper)
|
||||
: _parent(parent)
|
||||
, _preview(
|
||||
parent,
|
||||
, _emojiId(paper.emojiId()) {
|
||||
Window::WallPaperResolved(
|
||||
&_parent->history()->owner(),
|
||||
&paper
|
||||
) | rpl::start_with_next([=](const Data::WallPaper *paper) {
|
||||
_parent->repaint();
|
||||
if (!paper) {
|
||||
_preview.reset();
|
||||
} else {
|
||||
createPreview(*paper);
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void ThemeDocumentBox::createPreview(const Data::WallPaper &paper) {
|
||||
_preview.emplace(
|
||||
_parent,
|
||||
paper.document(),
|
||||
paper,
|
||||
st::msgServicePhotoWidth) {
|
||||
_preview.initDimensions();
|
||||
_preview.resizeGetHeight(_preview.maxWidth());
|
||||
st::msgServicePhotoWidth);
|
||||
_preview->initDimensions();
|
||||
_preview->resizeGetHeight(_preview->maxWidth());
|
||||
}
|
||||
|
||||
ThemeDocumentBox::~ThemeDocumentBox() = default;
|
||||
|
@ -487,7 +503,9 @@ int ThemeDocumentBox::top() {
|
|||
}
|
||||
|
||||
QSize ThemeDocumentBox::size() {
|
||||
return { _preview.maxWidth(), _preview.minHeight() };
|
||||
return _preview
|
||||
? QSize(_preview->maxWidth(), _preview->minHeight())
|
||||
: QSize(st::msgServicePhotoWidth, st::msgServicePhotoWidth);
|
||||
}
|
||||
|
||||
QString ThemeDocumentBox::title() {
|
||||
|
@ -509,8 +527,11 @@ rpl::producer<QString> ThemeDocumentBox::button() {
|
|||
}
|
||||
|
||||
ClickHandlerPtr ThemeDocumentBox::createViewLink() {
|
||||
const auto out = _parent->data()->out();
|
||||
const auto to = _parent->history()->peer;
|
||||
if (to->isChannel()) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto out = _parent->data()->out();
|
||||
const auto media = _parent->data()->media();
|
||||
const auto weak = base::make_weak(_parent);
|
||||
const auto paper = media ? media->paper() : nullptr;
|
||||
|
@ -557,9 +578,11 @@ void ThemeDocumentBox::draw(
|
|||
Painter &p,
|
||||
const PaintContext &context,
|
||||
const QRect &geometry) {
|
||||
p.translate(geometry.topLeft());
|
||||
_preview.draw(p, context);
|
||||
p.translate(-geometry.topLeft());
|
||||
if (_preview) {
|
||||
p.translate(geometry.topLeft());
|
||||
_preview->draw(p, context);
|
||||
p.translate(-geometry.topLeft());
|
||||
}
|
||||
}
|
||||
|
||||
void ThemeDocumentBox::stickerClearLoopPlayed() {
|
||||
|
@ -572,11 +595,13 @@ std::unique_ptr<StickerPlayer> ThemeDocumentBox::stickerTakePlayer(
|
|||
}
|
||||
|
||||
bool ThemeDocumentBox::hasHeavyPart() {
|
||||
return _preview.hasHeavyPart();
|
||||
return _preview && _preview->hasHeavyPart();
|
||||
}
|
||||
|
||||
void ThemeDocumentBox::unloadHeavyPart() {
|
||||
_preview.unloadHeavyPart();
|
||||
if (_preview) {
|
||||
_preview->unloadHeavyPart();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
|
@ -119,8 +119,12 @@ public:
|
|||
void unloadHeavyPart() override;
|
||||
|
||||
private:
|
||||
void createPreview(const Data::WallPaper &paper);
|
||||
|
||||
const not_null<Element*> _parent;
|
||||
ThemeDocument _preview;
|
||||
QString _emojiId;
|
||||
std::optional<ThemeDocument> _preview;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -9,34 +9,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "core/click_handler_types.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/history_item.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_sponsored_messages.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/history_view_reply.h"
|
||||
#include "history/view/history_view_sponsored_click_handler.h"
|
||||
#include "history/view/media/history_view_media_common.h"
|
||||
#include "history/view/media/history_view_theme_document.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/power_saving.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/power_saving.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_wall_paper.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_sponsored_messages.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
namespace HistoryView {
|
||||
|
@ -44,27 +36,29 @@ namespace {
|
|||
|
||||
constexpr auto kMaxOriginalEntryLines = 8192;
|
||||
|
||||
int articleThumbWidth(not_null<PhotoData*> thumb, int height) {
|
||||
[[nodiscard]] int ArticleThumbWidth(not_null<PhotoData*> thumb, int height) {
|
||||
const auto size = thumb->location(Data::PhotoSize::Thumbnail);
|
||||
return size.height()
|
||||
? qMax(qMin(height * size.width() / size.height(), height), 1)
|
||||
? std::max(std::min(height * size.width() / size.height(), height), 1)
|
||||
: 1;
|
||||
}
|
||||
|
||||
int articleThumbHeight(not_null<Data::PhotoMedia*> thumb, int width) {
|
||||
[[nodiscard]] int ArticleThumbHeight(
|
||||
not_null<Data::PhotoMedia*> thumb,
|
||||
int width) {
|
||||
const auto size = thumb->size(Data::PhotoSize::Thumbnail);
|
||||
return size.width()
|
||||
? std::max(size.height() * width / size.width(), 1)
|
||||
: 1;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Data::Media>> PrepareCollageMedia(
|
||||
[[nodiscard]] std::vector<std::unique_ptr<Data::Media>> PrepareCollageMedia(
|
||||
not_null<HistoryItem*> parent,
|
||||
const WebPageCollage &data) {
|
||||
auto result = std::vector<std::unique_ptr<Data::Media>>();
|
||||
result.reserve(data.items.size());
|
||||
const auto spoiler = false;
|
||||
for (const auto &item : data.items) {
|
||||
const auto spoiler = false;
|
||||
if (const auto document = std::get_if<DocumentData*>(&item)) {
|
||||
const auto skipPremiumEffect = false;
|
||||
result.push_back(std::make_unique<Data::MediaFile>(
|
||||
|
@ -205,14 +199,12 @@ QSize WebPage::countOptimalSize() {
|
|||
_openl = nullptr;
|
||||
_attach = nullptr;
|
||||
_collage = PrepareCollageMedia(_parent->data(), _data->collage);
|
||||
const auto min = st::msgMinWidth
|
||||
- _st.padding.left()
|
||||
- _st.padding.right();
|
||||
const auto min = st::msgMinWidth - rect::m::sum::h(_st.padding);
|
||||
_siteName = Ui::Text::String(min);
|
||||
_title = Ui::Text::String(min);
|
||||
_description = Ui::Text::String(min);
|
||||
}
|
||||
auto lineHeight = UnitedLineHeight();
|
||||
const auto lineHeight = UnitedLineHeight();
|
||||
|
||||
if (!_openl && (!_data->url.isEmpty() || _sponsoredData)) {
|
||||
const auto previewOfHiddenUrl = [&] {
|
||||
|
@ -272,7 +264,7 @@ QSize WebPage::countOptimalSize() {
|
|||
}
|
||||
|
||||
// init layout
|
||||
auto title = TextUtilities::SingleLine(_data->title.isEmpty()
|
||||
const auto title = TextUtilities::SingleLine(_data->title.isEmpty()
|
||||
? _data->author
|
||||
: _data->title);
|
||||
using Flag = MediaWebPageFlag;
|
||||
|
@ -296,13 +288,13 @@ QSize WebPage::countOptimalSize() {
|
|||
|
||||
// init strings
|
||||
if (_description.isEmpty() && !_data->description.text.isEmpty()) {
|
||||
auto text = _data->description;
|
||||
const auto &text = _data->description;
|
||||
|
||||
if (isLogEntryOriginal()) {
|
||||
// Fix layout for small bubbles (narrow media caption edit log entries).
|
||||
// Fix layout for small bubbles
|
||||
// (narrow media caption edit log entries).
|
||||
_description = Ui::Text::String(st::minPhotoSize
|
||||
- padding.left()
|
||||
- padding.right());
|
||||
- rect::m::sum::h(padding));
|
||||
}
|
||||
using MarkedTextContext = Core::MarkedTextContext;
|
||||
auto context = MarkedTextContext{
|
||||
|
@ -329,11 +321,10 @@ QSize WebPage::countOptimalSize() {
|
|||
Ui::WebpageTextTitleOptions());
|
||||
}
|
||||
if (_title.isEmpty() && !title.isEmpty()) {
|
||||
auto titleWithEntities = Ui::Text::Link(title, _data->url);
|
||||
if (!_siteNameLines && !_data->url.isEmpty()) {
|
||||
_title.setMarkedText(
|
||||
st::webPageTitleStyle,
|
||||
std::move(titleWithEntities),
|
||||
Ui::Text::Link(title, _data->url),
|
||||
Ui::WebpageTextTitleOptions());
|
||||
|
||||
} else {
|
||||
|
@ -345,19 +336,27 @@ QSize WebPage::countOptimalSize() {
|
|||
}
|
||||
|
||||
// init dimensions
|
||||
auto skipBlockWidth = _parent->skipBlockWidth();
|
||||
const auto skipBlockWidth = _parent->skipBlockWidth();
|
||||
auto maxWidth = skipBlockWidth;
|
||||
auto minHeight = 0;
|
||||
|
||||
auto siteNameHeight = _siteName.isEmpty() ? 0 : lineHeight;
|
||||
auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight;
|
||||
auto descMaxLines = isLogEntryOriginal() ? kMaxOriginalEntryLines : (3 + (siteNameHeight ? 0 : 1) + (titleMinHeight ? 0 : 1));
|
||||
auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight);
|
||||
auto articleMinHeight = siteNameHeight + titleMinHeight + descriptionMinHeight;
|
||||
auto articlePhotoMaxWidth = 0;
|
||||
if (_asArticle) {
|
||||
articlePhotoMaxWidth = st::webPagePhotoDelta + qMax(articleThumbWidth(_data->photo, articleMinHeight), lineHeight);
|
||||
}
|
||||
const auto siteNameHeight = _siteName.isEmpty() ? 0 : lineHeight;
|
||||
const auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight;
|
||||
const auto descMaxLines = isLogEntryOriginal()
|
||||
? kMaxOriginalEntryLines
|
||||
: (3 + (siteNameHeight ? 0 : 1) + (titleMinHeight ? 0 : 1));
|
||||
const auto descriptionMinHeight = _description.isEmpty()
|
||||
? 0
|
||||
: std::min(_description.minHeight(), descMaxLines * lineHeight);
|
||||
const auto articleMinHeight = siteNameHeight
|
||||
+ titleMinHeight
|
||||
+ descriptionMinHeight;
|
||||
const auto articlePhotoMaxWidth = _asArticle
|
||||
? st::webPagePhotoDelta
|
||||
+ std::max(
|
||||
ArticleThumbWidth(_data->photo, articleMinHeight),
|
||||
lineHeight)
|
||||
: 0;
|
||||
|
||||
if (!_siteName.isEmpty()) {
|
||||
accumulate_max(maxWidth, _siteName.maxWidth() + articlePhotoMaxWidth);
|
||||
|
@ -368,32 +367,38 @@ QSize WebPage::countOptimalSize() {
|
|||
minHeight += titleMinHeight;
|
||||
}
|
||||
if (!_description.isEmpty()) {
|
||||
accumulate_max(maxWidth, _description.maxWidth() + articlePhotoMaxWidth);
|
||||
accumulate_max(
|
||||
maxWidth,
|
||||
_description.maxWidth() + articlePhotoMaxWidth);
|
||||
minHeight += descriptionMinHeight;
|
||||
}
|
||||
if (_attach) {
|
||||
auto attachAtTop = _siteName.isEmpty() && _title.isEmpty() && _description.isEmpty();
|
||||
if (!attachAtTop) minHeight += st::mediaInBubbleSkip;
|
||||
const auto attachAtTop = _siteName.isEmpty()
|
||||
&& _title.isEmpty()
|
||||
&& _description.isEmpty();
|
||||
if (!attachAtTop) {
|
||||
minHeight += st::mediaInBubbleSkip;
|
||||
}
|
||||
|
||||
_attach->initDimensions();
|
||||
auto bubble = _attach->bubbleMargins();
|
||||
auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right();
|
||||
const auto bubble = _attach->bubbleMargins();
|
||||
auto maxMediaWidth = _attach->maxWidth() - rect::m::sum::h(bubble);
|
||||
if (isBubbleBottom() && _attach->customInfoLayout()) {
|
||||
maxMediaWidth += skipBlockWidth;
|
||||
}
|
||||
accumulate_max(maxWidth, maxMediaWidth);
|
||||
minHeight += _attach->minHeight() - bubble.top() - bubble.bottom();
|
||||
minHeight += _attach->minHeight() - rect::m::sum::v(bubble);
|
||||
}
|
||||
if (_data->type == WebPageType::Video && _data->duration) {
|
||||
_duration = Ui::FormatDurationText(_data->duration);
|
||||
_durationWidth = st::msgDateFont->width(_duration);
|
||||
}
|
||||
if (_openButtonWidth) {
|
||||
const auto &margins = st::historyPageButtonPadding;
|
||||
maxWidth += margins.left() + _openButtonWidth + margins.right();
|
||||
maxWidth += rect::m::sum::h(st::historyPageButtonPadding)
|
||||
+ _openButtonWidth;
|
||||
}
|
||||
maxWidth += padding.left() + padding.right();
|
||||
minHeight += padding.top() + padding.bottom();
|
||||
maxWidth += rect::m::sum::h(padding);
|
||||
minHeight += rect::m::sum::v(padding);
|
||||
|
||||
if (_asArticle) {
|
||||
minHeight = resizeGetHeight(maxWidth);
|
||||
|
@ -406,45 +411,51 @@ QSize WebPage::countCurrentSize(int newWidth) {
|
|||
return { newWidth, minHeight() };
|
||||
}
|
||||
|
||||
auto padding = inBubblePadding() + innerMargin();
|
||||
auto innerWidth = newWidth - padding.left() - padding.right();
|
||||
const auto padding = inBubblePadding() + innerMargin();
|
||||
const auto innerWidth = newWidth - rect::m::sum::h(padding);
|
||||
auto newHeight = 0;
|
||||
|
||||
auto lineHeight = UnitedLineHeight();
|
||||
auto linesMax = (_sponsoredData || isLogEntryOriginal())
|
||||
const auto lineHeight = UnitedLineHeight();
|
||||
const auto linesMax = (_sponsoredData || isLogEntryOriginal())
|
||||
? kMaxOriginalEntryLines
|
||||
: 5;
|
||||
auto siteNameHeight = _siteNameLines ? lineHeight : 0;
|
||||
const auto siteNameHeight = _siteNameLines ? lineHeight : 0;
|
||||
const auto twoTitleLines = 2 * st::webPageTitleFont->height;
|
||||
const auto descriptionLineHeight = st::webPageDescriptionFont->height;
|
||||
const auto asSponsored = (!!_sponsoredData);
|
||||
if (asArticle() || asSponsored) {
|
||||
const auto sponsoredUserpic = (asSponsored && _sponsoredData->peer);
|
||||
constexpr auto kSponsoredUserpicLines = 2;
|
||||
_pixh = (asSponsored ? kSponsoredUserpicLines : linesMax) * lineHeight;
|
||||
_pixh = lineHeight
|
||||
* (asSponsored ? kSponsoredUserpicLines : linesMax);
|
||||
do {
|
||||
_pixw = asSponsored ? _pixh : articleThumbWidth(_data->photo, _pixh);
|
||||
auto wleft = asSponsored
|
||||
? innerWidth - st::webPagePhotoDelta - qMax(_pixw, lineHeight)
|
||||
: innerWidth;
|
||||
_pixw = asSponsored
|
||||
? _pixh
|
||||
: ArticleThumbWidth(_data->photo, _pixh);
|
||||
const auto wleft = innerWidth
|
||||
- st::webPagePhotoDelta
|
||||
- std::max(_pixw, lineHeight);
|
||||
|
||||
newHeight = siteNameHeight;
|
||||
|
||||
if (_title.isEmpty()) {
|
||||
_titleLines = 0;
|
||||
} else {
|
||||
if (_title.countHeight(wleft) < 2 * st::webPageTitleFont->height) {
|
||||
_titleLines = 1;
|
||||
} else {
|
||||
_titleLines = 2;
|
||||
}
|
||||
_titleLines = (_title.countHeight(wleft) < twoTitleLines)
|
||||
? 1
|
||||
: 2;
|
||||
newHeight += _titleLines * lineHeight;
|
||||
}
|
||||
|
||||
auto descriptionHeight = _description.countHeight(wleft);
|
||||
if (descriptionHeight < (linesMax - _siteNameLines - _titleLines) * st::webPageDescriptionFont->height) {
|
||||
const auto descriptionHeight = _description.countHeight(
|
||||
sponsoredUserpic ? innerWidth : wleft);
|
||||
const auto restLines = (linesMax - _siteNameLines - _titleLines);
|
||||
if (descriptionHeight < restLines * descriptionLineHeight) {
|
||||
// We have height for all the lines.
|
||||
_descriptionLines = -1;
|
||||
newHeight += descriptionHeight;
|
||||
} else {
|
||||
_descriptionLines = (linesMax - _siteNameLines - _titleLines);
|
||||
_descriptionLines = restLines;
|
||||
newHeight += _descriptionLines * lineHeight;
|
||||
}
|
||||
|
||||
|
@ -460,55 +471,55 @@ QSize WebPage::countCurrentSize(int newWidth) {
|
|||
if (_title.isEmpty()) {
|
||||
_titleLines = 0;
|
||||
} else {
|
||||
if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) {
|
||||
_titleLines = 1;
|
||||
} else {
|
||||
_titleLines = 2;
|
||||
}
|
||||
_titleLines = (_title.countHeight(innerWidth) < twoTitleLines)
|
||||
? 1
|
||||
: 2;
|
||||
newHeight += _titleLines * lineHeight;
|
||||
}
|
||||
|
||||
if (_description.isEmpty()) {
|
||||
_descriptionLines = 0;
|
||||
} else {
|
||||
auto descriptionHeight = _description.countHeight(innerWidth);
|
||||
if (descriptionHeight < (linesMax - _siteNameLines - _titleLines) * st::webPageDescriptionFont->height) {
|
||||
const auto restLines = (linesMax - _siteNameLines - _titleLines);
|
||||
const auto descriptionHeight = _description.countHeight(
|
||||
innerWidth);
|
||||
if (descriptionHeight < restLines * descriptionLineHeight) {
|
||||
// We have height for all the lines.
|
||||
_descriptionLines = -1;
|
||||
newHeight += descriptionHeight;
|
||||
} else {
|
||||
_descriptionLines = (linesMax - _siteNameLines - _titleLines);
|
||||
_descriptionLines = restLines;
|
||||
newHeight += _descriptionLines * lineHeight;
|
||||
}
|
||||
}
|
||||
|
||||
if (_attach) {
|
||||
auto attachAtTop = !_siteNameLines && !_titleLines && !_descriptionLines;
|
||||
if (!attachAtTop) newHeight += st::mediaInBubbleSkip;
|
||||
const auto attachAtTop = !_siteNameLines
|
||||
&& !_titleLines
|
||||
&& !_descriptionLines;
|
||||
if (!attachAtTop) {
|
||||
newHeight += st::mediaInBubbleSkip;
|
||||
}
|
||||
|
||||
auto bubble = _attach->bubbleMargins();
|
||||
|
||||
_attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right());
|
||||
newHeight += _attach->height() - bubble.top() - bubble.bottom();
|
||||
const auto bubble = _attach->bubbleMargins();
|
||||
_attach->resizeGetHeight(innerWidth + rect::m::sum::h(bubble));
|
||||
newHeight += _attach->height() - rect::m::sum::v(bubble);
|
||||
}
|
||||
}
|
||||
newHeight += padding.top() + padding.bottom();
|
||||
newHeight += rect::m::sum::v(padding);
|
||||
|
||||
return { newWidth, newHeight };
|
||||
}
|
||||
|
||||
TextSelection WebPage::toTitleSelection(
|
||||
TextSelection selection) const {
|
||||
TextSelection WebPage::toTitleSelection(TextSelection selection) const {
|
||||
return UnshiftItemSelection(selection, _siteName);
|
||||
}
|
||||
|
||||
TextSelection WebPage::fromTitleSelection(
|
||||
TextSelection selection) const {
|
||||
TextSelection WebPage::fromTitleSelection(TextSelection selection) const {
|
||||
return ShiftItemSelection(selection, _siteName);
|
||||
}
|
||||
|
||||
TextSelection WebPage::toDescriptionSelection(
|
||||
TextSelection selection) const {
|
||||
TextSelection WebPage::toDescriptionSelection(TextSelection selection) const {
|
||||
return UnshiftItemSelection(toTitleSelection(selection), _title);
|
||||
}
|
||||
|
||||
|
@ -553,7 +564,7 @@ void WebPage::unloadHeavyPart() {
|
|||
}
|
||||
|
||||
void WebPage::draw(Painter &p, const PaintContext &context) const {
|
||||
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
|
||||
if (width() < rect::m::sum::h(st::msgPadding) + 1) {
|
||||
return;
|
||||
}
|
||||
const auto st = context.st;
|
||||
|
@ -561,20 +572,48 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
|
|||
const auto stm = context.messageStyle();
|
||||
|
||||
const auto bubble = _attach ? _attach->bubbleMargins() : QMargins();
|
||||
const auto full = QRect(0, 0, width(), height());
|
||||
auto outer = full.marginsRemoved(inBubblePadding());
|
||||
auto inner = outer.marginsRemoved(innerMargin());
|
||||
const auto full = Rect(currentSize());
|
||||
const auto outer = full - inBubblePadding();
|
||||
const auto inner = outer - innerMargin();
|
||||
const auto attachAdditionalInfoText = _attach
|
||||
? _attach->additionalInfoString()
|
||||
: QString();
|
||||
auto tshift = inner.top();
|
||||
auto paintw = inner.width();
|
||||
auto attachAdditionalInfoText = _attach ? _attach->additionalInfoString() : QString();
|
||||
|
||||
const auto selected = context.selected();
|
||||
const auto colorIndex = parent()->colorIndex();
|
||||
const auto view = parent();
|
||||
const auto colorIndex = view->colorIndex();
|
||||
const auto cache = context.outbg
|
||||
? stm->replyCache[st->colorPatternIndex(colorIndex)].get()
|
||||
: st->coloredReplyCache(selected, colorIndex).get();
|
||||
const auto from = view->data()->displayFrom();
|
||||
const auto backgroundEmojiId = from
|
||||
? from->backgroundEmojiId()
|
||||
: DocumentId();
|
||||
const auto backgroundEmoji = backgroundEmojiId
|
||||
? st->backgroundEmojiData(backgroundEmojiId).get()
|
||||
: nullptr;
|
||||
const auto backgroundEmojiCache = backgroundEmoji
|
||||
? &backgroundEmoji->caches[Ui::BackgroundEmojiData::CacheIndex(
|
||||
selected,
|
||||
context.outbg,
|
||||
true,
|
||||
colorIndex + 1)]
|
||||
: nullptr;
|
||||
Ui::Text::ValidateQuotePaintCache(*cache, _st);
|
||||
Ui::Text::FillQuotePaint(p, outer, *cache, _st);
|
||||
if (backgroundEmoji) {
|
||||
ValidateBackgroundEmoji(
|
||||
backgroundEmojiId,
|
||||
backgroundEmoji,
|
||||
backgroundEmojiCache,
|
||||
cache,
|
||||
view);
|
||||
if (!backgroundEmojiCache->frames[0].isNull()) {
|
||||
FillBackgroundEmoji(p, outer, false, *backgroundEmojiCache);
|
||||
}
|
||||
}
|
||||
|
||||
if (_ripple) {
|
||||
_ripple->paint(p, outer.x(), outer.y(), width(), &cache->bg2);
|
||||
|
@ -583,32 +622,36 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
|
|||
}
|
||||
}
|
||||
|
||||
const auto asSponsored = (!!_sponsoredData);
|
||||
|
||||
auto lineHeight = UnitedLineHeight();
|
||||
if (asArticle()) {
|
||||
ensurePhotoMediaCreated();
|
||||
|
||||
QPixmap pix;
|
||||
auto pw = qMax(_pixw, lineHeight);
|
||||
auto ph = _pixh;
|
||||
auto pixw = _pixw, pixh = articleThumbHeight(_photoMedia.get(), _pixw);
|
||||
auto pix = QPixmap();
|
||||
const auto pw = qMax(_pixw, lineHeight);
|
||||
const auto ph = _pixh;
|
||||
auto pixw = _pixw;
|
||||
auto pixh = ArticleThumbHeight(_photoMedia.get(), _pixw);
|
||||
const auto maxsize = _photoMedia->size(Data::PhotoSize::Thumbnail);
|
||||
const auto maxw = style::ConvertScale(maxsize.width());
|
||||
const auto maxh = style::ConvertScale(maxsize.height());
|
||||
if (pixw * ph != pixh * pw) {
|
||||
float64 coef = (pixw * ph > pixh * pw) ? qMin(ph / float64(pixh), maxh / float64(pixh)) : qMin(pw / float64(pixw), maxw / float64(pixw));
|
||||
pixh = qRound(pixh * coef);
|
||||
pixw = qRound(pixw * coef);
|
||||
const auto coef = (pixw * ph > pixh * pw)
|
||||
? std::min(ph / float64(pixh), maxh / float64(pixh))
|
||||
: std::min(pw / float64(pixw), maxw / float64(pixw));
|
||||
pixh = std::round(pixh * coef);
|
||||
pixw = std::round(pixw * coef);
|
||||
}
|
||||
const auto size = QSize(pixw, pixh);
|
||||
const auto args = Images::PrepareArgs{
|
||||
.options = Images::Option::RoundSmall,
|
||||
.outer = { pw, ph },
|
||||
};
|
||||
if (const auto thumbnail = _photoMedia->image(
|
||||
Data::PhotoSize::Thumbnail)) {
|
||||
using namespace Data;
|
||||
if (const auto thumbnail = _photoMedia->image(PhotoSize::Thumbnail)) {
|
||||
pix = thumbnail->pixSingle(size, args);
|
||||
} else if (const auto small = _photoMedia->image(
|
||||
Data::PhotoSize::Small)) {
|
||||
} else if (const auto small = _photoMedia->image(PhotoSize::Small)) {
|
||||
pix = small->pixSingle(size, args.blurred());
|
||||
} else if (const auto blurred = _photoMedia->thumbnailInline()) {
|
||||
pix = blurred->pixSingle(size, args.blurred());
|
||||
|
@ -618,12 +661,21 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
|
|||
const auto st = context.st;
|
||||
Ui::FillRoundRect(
|
||||
p,
|
||||
style::rtlrect(inner.left() + paintw - pw, tshift, pw, _pixh, width()),
|
||||
style::rtlrect(
|
||||
inner.left() + paintw - pw,
|
||||
tshift,
|
||||
pw,
|
||||
_pixh,
|
||||
width()),
|
||||
st->msgSelectOverlay(),
|
||||
st->msgSelectOverlayCorners(Ui::CachedCornerRadius::Small));
|
||||
}
|
||||
paintw -= pw + st::webPagePhotoDelta;
|
||||
} else if (_sponsoredData && _sponsoredData->peer) {
|
||||
if (!asSponsored) {
|
||||
// Ignore photo width in sponsored messages,
|
||||
// as its width only affects the title.
|
||||
paintw -= pw + st::webPagePhotoDelta;
|
||||
}
|
||||
} else if (asSponsored && _sponsoredData->peer) {
|
||||
const auto size = _pixh;
|
||||
const auto sizeHq = size * style::DevicePixelRatio();
|
||||
const auto userpicPos = QPoint(inner.left() + paintw - size, tshift);
|
||||
|
@ -645,29 +697,53 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
|
|||
? stm->semiboldPalette
|
||||
: st->coloredTextPalette(selected, colorIndex));
|
||||
|
||||
auto endskip = 0;
|
||||
if (_siteName.hasSkipBlock()) {
|
||||
endskip = _parent->skipBlockWidth();
|
||||
}
|
||||
_siteName.drawLeftElided(p, inner.left(), tshift, paintw, width(), _siteNameLines, style::al_left, 0, -1, endskip, false, context.selection);
|
||||
const auto endskip = _siteName.hasSkipBlock()
|
||||
? _parent->skipBlockWidth()
|
||||
: 0;
|
||||
_siteName.drawLeftElided(
|
||||
p,
|
||||
inner.left(),
|
||||
tshift,
|
||||
paintw,
|
||||
width(),
|
||||
_siteNameLines,
|
||||
style::al_left,
|
||||
0,
|
||||
-1,
|
||||
endskip,
|
||||
false,
|
||||
context.selection);
|
||||
tshift += lineHeight;
|
||||
|
||||
p.setTextPalette(stm->textPalette);
|
||||
}
|
||||
p.setPen(stm->historyTextFg);
|
||||
if (_titleLines) {
|
||||
auto endskip = 0;
|
||||
if (_title.hasSkipBlock()) {
|
||||
endskip = _parent->skipBlockWidth();
|
||||
}
|
||||
_title.drawLeftElided(p, inner.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, toTitleSelection(context.selection));
|
||||
const auto endskip = _title.hasSkipBlock()
|
||||
? _parent->skipBlockWidth()
|
||||
: 0;
|
||||
const auto titleWidth = asSponsored
|
||||
? (paintw - _pixh - st::webPagePhotoDelta)
|
||||
: paintw;
|
||||
_title.drawLeftElided(
|
||||
p,
|
||||
inner.left(),
|
||||
tshift,
|
||||
titleWidth,
|
||||
width(),
|
||||
_titleLines,
|
||||
style::al_left,
|
||||
0,
|
||||
-1,
|
||||
endskip,
|
||||
false,
|
||||
toTitleSelection(context.selection));
|
||||
tshift += _titleLines * lineHeight;
|
||||
}
|
||||
if (_descriptionLines) {
|
||||
auto endskip = 0;
|
||||
if (_description.hasSkipBlock()) {
|
||||
endskip = _parent->skipBlockWidth();
|
||||
}
|
||||
const auto endskip = _description.hasSkipBlock()
|
||||
? _parent->skipBlockWidth()
|
||||
: 0;
|
||||
_parent->prepareCustomEmojiPaint(p, context, _description);
|
||||
_description.draw(p, {
|
||||
.position = { inner.left(), tshift },
|
||||
|
@ -688,12 +764,17 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
|
|||
: _description.countHeight(paintw);
|
||||
}
|
||||
if (_attach) {
|
||||
auto attachAtTop = !_siteNameLines && !_titleLines && !_descriptionLines;
|
||||
if (!attachAtTop) tshift += st::mediaInBubbleSkip;
|
||||
const auto attachAtTop = !_siteNameLines
|
||||
&& !_titleLines
|
||||
&& !_descriptionLines;
|
||||
if (!attachAtTop) {
|
||||
tshift += st::mediaInBubbleSkip;
|
||||
}
|
||||
|
||||
auto attachLeft = inner.left() - bubble.left();
|
||||
auto attachTop = tshift - bubble.top();
|
||||
if (rtl()) attachLeft = width() - attachLeft - _attach->width();
|
||||
const auto attachLeft = rtl()
|
||||
? (width() - (inner.left() - bubble.left()) - _attach->width())
|
||||
: (inner.left() - bubble.left());
|
||||
const auto attachTop = tshift - bubble.top();
|
||||
|
||||
p.translate(attachLeft, attachTop);
|
||||
|
||||
|
@ -703,8 +784,8 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
|
|||
).withSelection(context.selected()
|
||||
? FullSelection
|
||||
: TextSelection()));
|
||||
auto pixwidth = _attach->width();
|
||||
auto pixheight = _attach->height();
|
||||
const auto pixwidth = _attach->width();
|
||||
const auto pixheight = _attach->height();
|
||||
|
||||
if (_data->type == WebPageType::Video
|
||||
&& _collage.empty()
|
||||
|
@ -712,22 +793,47 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
|
|||
&& !_data->document) {
|
||||
if (_attach->isReadyForOpen()) {
|
||||
if (_data->siteName == u"YouTube"_q) {
|
||||
st->youtubeIcon().paint(p, (pixwidth - st::youtubeIcon.width()) / 2, (pixheight - st::youtubeIcon.height()) / 2, width());
|
||||
st->youtubeIcon().paint(
|
||||
p,
|
||||
(pixwidth - st::youtubeIcon.width()) / 2,
|
||||
(pixheight - st::youtubeIcon.height()) / 2,
|
||||
width());
|
||||
} else {
|
||||
st->videoIcon().paint(p, (pixwidth - st::videoIcon.width()) / 2, (pixheight - st::videoIcon.height()) / 2, width());
|
||||
st->videoIcon().paint(
|
||||
p,
|
||||
(pixwidth - st::videoIcon.width()) / 2,
|
||||
(pixheight - st::videoIcon.height()) / 2,
|
||||
width());
|
||||
}
|
||||
}
|
||||
if (_durationWidth) {
|
||||
auto dateX = pixwidth - _durationWidth - st::msgDateImgDelta - 2 * st::msgDateImgPadding.x();
|
||||
auto dateY = pixheight - st::msgDateFont->height - 2 * st::msgDateImgPadding.y() - st::msgDateImgDelta;
|
||||
auto dateW = pixwidth - dateX - st::msgDateImgDelta;
|
||||
auto dateH = pixheight - dateY - st::msgDateImgDelta;
|
||||
const auto dateX = pixwidth
|
||||
- _durationWidth
|
||||
- st::msgDateImgDelta
|
||||
- 2 * st::msgDateImgPadding.x();
|
||||
const auto dateY = pixheight
|
||||
- st::msgDateFont->height
|
||||
- 2 * st::msgDateImgPadding.y()
|
||||
- st::msgDateImgDelta;
|
||||
const auto dateW = pixwidth - dateX - st::msgDateImgDelta;
|
||||
const auto dateH = pixheight - dateY - st::msgDateImgDelta;
|
||||
|
||||
Ui::FillRoundRect(p, dateX, dateY, dateW, dateH, sti->msgDateImgBg, sti->msgDateImgBgCorners);
|
||||
Ui::FillRoundRect(
|
||||
p,
|
||||
dateX,
|
||||
dateY,
|
||||
dateW,
|
||||
dateH,
|
||||
sti->msgDateImgBg,
|
||||
sti->msgDateImgBgCorners);
|
||||
|
||||
p.setFont(st::msgDateFont);
|
||||
p.setPen(st->msgDateImgFg());
|
||||
p.drawTextLeft(dateX + st::msgDateImgPadding.x(), dateY + st::msgDateImgPadding.y(), pixwidth, _duration);
|
||||
p.drawTextLeft(
|
||||
dateX + st::msgDateImgPadding.x(),
|
||||
dateY + st::msgDateImgPadding.y(),
|
||||
pixwidth,
|
||||
_duration);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -736,7 +842,11 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
|
|||
if (!attachAdditionalInfoText.isEmpty()) {
|
||||
p.setFont(st::msgDateFont);
|
||||
p.setPen(stm->msgDateFg);
|
||||
p.drawTextLeft(st::msgPadding.left(), outer.y() + outer.height() + st::mediaInBubbleSkip, width(), attachAdditionalInfoText);
|
||||
p.drawTextLeft(
|
||||
st::msgPadding.left(),
|
||||
outer.y() + outer.height() + st::mediaInBubbleSkip,
|
||||
width(),
|
||||
attachAdditionalInfoText);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -763,74 +873,91 @@ bool WebPage::asArticle() const {
|
|||
TextState WebPage::textState(QPoint point, StateRequest request) const {
|
||||
auto result = TextState(_parent);
|
||||
|
||||
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
|
||||
if (width() < rect::m::sum::h(st::msgPadding) + 1) {
|
||||
return result;
|
||||
}
|
||||
const auto bubble = _attach ? _attach->bubbleMargins() : QMargins();
|
||||
const auto full = QRect(0, 0, width(), height());
|
||||
auto outer = full.marginsRemoved(inBubblePadding());
|
||||
const auto full = Rect(currentSize());
|
||||
auto outer = full - inBubblePadding();
|
||||
if (_sponsoredData) {
|
||||
outer.translate(0, st::msgDateFont->height);
|
||||
}
|
||||
auto inner = outer.marginsRemoved(innerMargin());
|
||||
const auto inner = outer - innerMargin();
|
||||
auto tshift = inner.top();
|
||||
auto paintw = inner.width();
|
||||
|
||||
auto lineHeight = UnitedLineHeight();
|
||||
const auto lineHeight = UnitedLineHeight();
|
||||
auto inThumb = false;
|
||||
if (asArticle()) {
|
||||
auto pw = qMax(_pixw, lineHeight);
|
||||
if (style::rtlrect(inner.left() + paintw - pw, tshift, pw, _pixh, width()).contains(point)) {
|
||||
inThumb = true;
|
||||
}
|
||||
const auto pw = std::max(_pixw, lineHeight);
|
||||
inThumb = style::rtlrect(
|
||||
inner.left() + paintw - pw,
|
||||
tshift,
|
||||
pw,
|
||||
_pixh,
|
||||
width()).contains(point);
|
||||
paintw -= pw + st::webPagePhotoDelta;
|
||||
}
|
||||
int symbolAdd = 0;
|
||||
auto symbolAdd = int(0);
|
||||
if (_siteNameLines) {
|
||||
if (point.y() >= tshift && point.y() < tshift + lineHeight) {
|
||||
Ui::Text::StateRequestElided siteNameRequest = request.forText();
|
||||
auto siteNameRequest = Ui::Text::StateRequestElided(
|
||||
request.forText());
|
||||
siteNameRequest.lines = _siteNameLines;
|
||||
result = TextState(_parent, _siteName.getStateElidedLeft(
|
||||
point - QPoint(inner.left(), tshift),
|
||||
paintw,
|
||||
width(),
|
||||
siteNameRequest));
|
||||
result = TextState(
|
||||
_parent,
|
||||
_siteName.getStateElidedLeft(
|
||||
point - QPoint(inner.left(), tshift),
|
||||
paintw,
|
||||
width(),
|
||||
siteNameRequest));
|
||||
} else if (point.y() >= tshift + lineHeight) {
|
||||
symbolAdd += _siteName.length();
|
||||
}
|
||||
tshift += lineHeight;
|
||||
}
|
||||
if (_titleLines) {
|
||||
if (point.y() >= tshift && point.y() < tshift + _titleLines * lineHeight) {
|
||||
Ui::Text::StateRequestElided titleRequest = request.forText();
|
||||
if (point.y() >= tshift
|
||||
&& point.y() < tshift + _titleLines * lineHeight) {
|
||||
auto titleRequest = Ui::Text::StateRequestElided(
|
||||
request.forText());
|
||||
titleRequest.lines = _titleLines;
|
||||
result = TextState(_parent, _title.getStateElidedLeft(
|
||||
point - QPoint(inner.left(), tshift),
|
||||
paintw,
|
||||
width(),
|
||||
titleRequest));
|
||||
result = TextState(
|
||||
_parent,
|
||||
_title.getStateElidedLeft(
|
||||
point - QPoint(inner.left(), tshift),
|
||||
paintw,
|
||||
width(),
|
||||
titleRequest));
|
||||
} else if (point.y() >= tshift + _titleLines * lineHeight) {
|
||||
symbolAdd += _title.length();
|
||||
}
|
||||
tshift += _titleLines * lineHeight;
|
||||
}
|
||||
if (_descriptionLines) {
|
||||
auto descriptionHeight = (_descriptionLines > 0) ? _descriptionLines * lineHeight : _description.countHeight(paintw);
|
||||
const auto descriptionHeight = (_descriptionLines > 0)
|
||||
? _descriptionLines * lineHeight
|
||||
: _description.countHeight(paintw);
|
||||
if (point.y() >= tshift && point.y() < tshift + descriptionHeight) {
|
||||
if (_descriptionLines > 0) {
|
||||
Ui::Text::StateRequestElided descriptionRequest = request.forText();
|
||||
auto descriptionRequest = Ui::Text::StateRequestElided(
|
||||
request.forText());
|
||||
descriptionRequest.lines = _descriptionLines;
|
||||
result = TextState(_parent, _description.getStateElidedLeft(
|
||||
point - QPoint(inner.left(), tshift),
|
||||
paintw,
|
||||
width(),
|
||||
descriptionRequest));
|
||||
result = TextState(
|
||||
_parent,
|
||||
_description.getStateElidedLeft(
|
||||
point - QPoint(inner.left(), tshift),
|
||||
paintw,
|
||||
width(),
|
||||
descriptionRequest));
|
||||
} else {
|
||||
result = TextState(_parent, _description.getStateLeft(
|
||||
point - QPoint(inner.left(), tshift),
|
||||
paintw,
|
||||
width(),
|
||||
request.forText()));
|
||||
result = TextState(
|
||||
_parent,
|
||||
_description.getStateLeft(
|
||||
point - QPoint(inner.left(), tshift),
|
||||
paintw,
|
||||
width(),
|
||||
request.forText()));
|
||||
}
|
||||
} else if (point.y() >= tshift + descriptionHeight) {
|
||||
symbolAdd += _description.length();
|
||||
|
@ -840,14 +967,26 @@ TextState WebPage::textState(QPoint point, StateRequest request) const {
|
|||
if (inThumb) {
|
||||
result.link = _openl;
|
||||
} else if (_attach) {
|
||||
auto attachAtTop = !_siteNameLines && !_titleLines && !_descriptionLines;
|
||||
if (!attachAtTop) tshift += st::mediaInBubbleSkip;
|
||||
const auto attachAtTop = !_siteNameLines
|
||||
&& !_titleLines
|
||||
&& !_descriptionLines;
|
||||
if (!attachAtTop) {
|
||||
tshift += st::mediaInBubbleSkip;
|
||||
}
|
||||
|
||||
if (QRect(inner.left(), tshift, paintw, inner.top() + inner.height() - tshift).contains(point)) {
|
||||
auto attachLeft = inner.left() - bubble.left();
|
||||
auto attachTop = tshift - bubble.top();
|
||||
if (rtl()) attachLeft = width() - attachLeft - _attach->width();
|
||||
result = _attach->textState(point - QPoint(attachLeft, attachTop), request);
|
||||
const auto rect = QRect(
|
||||
inner.left(),
|
||||
tshift,
|
||||
paintw,
|
||||
inner.top() + inner.height() - tshift);
|
||||
if (rect.contains(point)) {
|
||||
const auto attachLeft = rtl()
|
||||
? width() - (inner.left() - bubble.left()) - _attach->width()
|
||||
: (inner.left() - bubble.left());
|
||||
const auto attachTop = tshift - bubble.top();
|
||||
result = _attach->textState(
|
||||
point - QPoint(attachLeft, attachTop),
|
||||
request);
|
||||
if (result.cursor == CursorState::Enlarge) {
|
||||
result.cursor = CursorState::None;
|
||||
} else {
|
||||
|
@ -879,27 +1018,37 @@ ClickHandlerPtr WebPage::replaceAttachLink(
|
|||
return _openl;
|
||||
}
|
||||
|
||||
TextSelection WebPage::adjustSelection(TextSelection selection, TextSelectType type) const {
|
||||
if ((!_titleLines && !_descriptionLines) || selection.to <= _siteName.length()) {
|
||||
TextSelection WebPage::adjustSelection(
|
||||
TextSelection selection,
|
||||
TextSelectType type) const {
|
||||
if ((!_titleLines && !_descriptionLines)
|
||||
|| selection.to <= _siteName.length()) {
|
||||
return _siteName.adjustSelection(selection, type);
|
||||
}
|
||||
|
||||
auto titlesLength = _siteName.length() + _title.length();
|
||||
auto titleSelection = _title.adjustSelection(toTitleSelection(selection), type);
|
||||
if ((!_siteNameLines && !_descriptionLines) || (selection.from >= _siteName.length() && selection.to <= titlesLength)) {
|
||||
const auto titlesLength = _siteName.length() + _title.length();
|
||||
const auto titleSelection = _title.adjustSelection(
|
||||
toTitleSelection(selection),
|
||||
type);
|
||||
if ((!_siteNameLines && !_descriptionLines)
|
||||
|| (selection.from >= _siteName.length()
|
||||
&& selection.to <= titlesLength)) {
|
||||
return fromTitleSelection(titleSelection);
|
||||
}
|
||||
|
||||
auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type);
|
||||
const auto descriptionSelection = _description.adjustSelection(
|
||||
toDescriptionSelection(selection),
|
||||
type);
|
||||
if ((!_siteNameLines && !_titleLines) || selection.from >= titlesLength) {
|
||||
return fromDescriptionSelection(descriptionSelection);
|
||||
}
|
||||
|
||||
auto siteNameSelection = _siteName.adjustSelection(selection, type);
|
||||
if (!_descriptionLines || selection.to <= titlesLength) {
|
||||
return { siteNameSelection.from, fromTitleSelection(titleSelection).to };
|
||||
}
|
||||
return { siteNameSelection.from, fromDescriptionSelection(descriptionSelection).to };
|
||||
return {
|
||||
_siteName.adjustSelection(selection, type).from,
|
||||
(!_descriptionLines || selection.to <= titlesLength)
|
||||
? fromTitleSelection(titleSelection).to
|
||||
: fromDescriptionSelection(descriptionSelection).to,
|
||||
};
|
||||
}
|
||||
|
||||
uint16 WebPage::fullSelectionLength() const {
|
||||
|
@ -920,8 +1069,8 @@ void WebPage::clickHandlerPressedChanged(
|
|||
if (p == _openl) {
|
||||
if (pressed) {
|
||||
if (!_ripple) {
|
||||
const auto full = QRect(0, 0, width(), height());
|
||||
const auto outer = full.marginsRemoved(inBubblePadding());
|
||||
const auto full = Rect(currentSize());
|
||||
const auto outer = full - inBubblePadding();
|
||||
const auto owner = &parent()->history()->owner();
|
||||
_ripple = std::make_unique<Ui::RippleAnimation>(
|
||||
st::defaultRippleAnimation,
|
||||
|
@ -957,23 +1106,20 @@ void WebPage::playAnimation(bool autoplay) {
|
|||
}
|
||||
|
||||
bool WebPage::isDisplayed() const {
|
||||
const auto item = _parent->data();
|
||||
return !_data->pendingTill
|
||||
&& !_data->failed
|
||||
&& !item->Has<HistoryMessageLogEntryOriginal>();
|
||||
&& !_parent->data()->Has<HistoryMessageLogEntryOriginal>();
|
||||
}
|
||||
|
||||
QString WebPage::additionalInfoString() const {
|
||||
return _attach ? _attach->additionalInfoString() : QString();
|
||||
}
|
||||
|
||||
bool WebPage::toggleSelectionByHandlerClick(
|
||||
const ClickHandlerPtr &p) const {
|
||||
bool WebPage::toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const {
|
||||
return _attach && _attach->toggleSelectionByHandlerClick(p);
|
||||
}
|
||||
|
||||
bool WebPage::allowTextSelectionByHandler(
|
||||
const ClickHandlerPtr &p) const {
|
||||
bool WebPage::allowTextSelectionByHandler(const ClickHandlerPtr &p) const {
|
||||
return (p == _openl);
|
||||
}
|
||||
|
||||
|
@ -983,8 +1129,7 @@ bool WebPage::dragItemByHandler(const ClickHandlerPtr &p) const {
|
|||
|
||||
TextForMimeData WebPage::selectedText(TextSelection selection) const {
|
||||
auto siteNameResult = _siteName.toTextForMimeData(selection);
|
||||
auto titleResult = _title.toTextForMimeData(
|
||||
toTitleSelection(selection));
|
||||
auto titleResult = _title.toTextForMimeData(toTitleSelection(selection));
|
||||
auto descriptionResult = _description.toTextForMimeData(
|
||||
toDescriptionSelection(selection));
|
||||
if (titleResult.empty() && descriptionResult.empty()) {
|
||||
|
@ -996,12 +1141,18 @@ TextForMimeData WebPage::selectedText(TextSelection selection) const {
|
|||
} else if (siteNameResult.empty()) {
|
||||
return titleResult.append('\n').append(std::move(descriptionResult));
|
||||
} else if (titleResult.empty()) {
|
||||
return siteNameResult.append('\n').append(std::move(descriptionResult));
|
||||
return siteNameResult
|
||||
.append('\n')
|
||||
.append(std::move(descriptionResult));
|
||||
} else if (descriptionResult.empty()) {
|
||||
return siteNameResult.append('\n').append(std::move(titleResult));
|
||||
}
|
||||
|
||||
return siteNameResult.append('\n').append(std::move(titleResult)).append('\n').append(std::move(descriptionResult));
|
||||
return siteNameResult
|
||||
.append('\n')
|
||||
.append(std::move(titleResult))
|
||||
.append('\n')
|
||||
.append(std::move(descriptionResult));
|
||||
}
|
||||
|
||||
QMargins WebPage::inBubblePadding() const {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue