Merge tag 'v4.10.4' into dev
# Conflicts: # Telegram/Resources/winrc/Telegram.rc # Telegram/Resources/winrc/Updater.rc # Telegram/SourceFiles/core/version.h # Telegram/lib_ui # snap/snapcraft.yaml
2
.github/workflows/docker.yml
vendored
|
@ -30,7 +30,7 @@ jobs:
|
||||||
curl -sSL https://install.python-poetry.org | python3 -
|
curl -sSL https://install.python-poetry.org | python3 -
|
||||||
|
|
||||||
- name: Free up some disk space.
|
- name: Free up some disk space.
|
||||||
uses: jlumbroso/free-disk-space@76866dbe54312617f00798d1762df7f43def6e5c
|
uses: jlumbroso/free-disk-space@f68fdb76e2ea636224182cfb7377ff9a1708f9b8
|
||||||
|
|
||||||
- name: Docker image build.
|
- name: Docker image build.
|
||||||
run: |
|
run: |
|
||||||
|
|
4
.github/workflows/master_updater.yml
vendored
|
@ -11,7 +11,9 @@ jobs:
|
||||||
SKIP: "0"
|
SKIP: "0"
|
||||||
to_branch: "master"
|
to_branch: "master"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.1.0
|
- uses: actions/checkout@v4.1.0
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
if: env.SKIP == '0'
|
if: env.SKIP == '0'
|
||||||
- name: Push the code to the master branch.
|
- name: Push the code to the master branch.
|
||||||
if: env.SKIP == '0'
|
if: env.SKIP == '0'
|
||||||
|
|
3
.gitmodules
vendored
|
@ -100,3 +100,6 @@
|
||||||
[submodule "Telegram/ThirdParty/wayland"]
|
[submodule "Telegram/ThirdParty/wayland"]
|
||||||
path = Telegram/ThirdParty/wayland
|
path = Telegram/ThirdParty/wayland
|
||||||
url = https://github.com/gitlab-freedesktop-mirrors/wayland.git
|
url = https://github.com/gitlab-freedesktop-mirrors/wayland.git
|
||||||
|
[submodule "Telegram/ThirdParty/libprisma"]
|
||||||
|
path = Telegram/ThirdParty/libprisma
|
||||||
|
url = https://github.com/desktop-app/libprisma.git
|
||||||
|
|
|
@ -28,6 +28,7 @@ include(cmake/lib_ffmpeg.cmake)
|
||||||
include(cmake/lib_stripe.cmake)
|
include(cmake/lib_stripe.cmake)
|
||||||
include(cmake/lib_tgvoip.cmake)
|
include(cmake/lib_tgvoip.cmake)
|
||||||
include(cmake/lib_tgcalls.cmake)
|
include(cmake/lib_tgcalls.cmake)
|
||||||
|
include(cmake/lib_prisma.cmake)
|
||||||
include(cmake/td_export.cmake)
|
include(cmake/td_export.cmake)
|
||||||
include(cmake/td_mtproto.cmake)
|
include(cmake/td_mtproto.cmake)
|
||||||
include(cmake/td_lang.cmake)
|
include(cmake/td_lang.cmake)
|
||||||
|
@ -218,6 +219,8 @@ PRIVATE
|
||||||
api/api_sensitive_content.h
|
api/api_sensitive_content.h
|
||||||
api/api_single_message_search.cpp
|
api/api_single_message_search.cpp
|
||||||
api/api_single_message_search.h
|
api/api_single_message_search.h
|
||||||
|
api/api_statistics.cpp
|
||||||
|
api/api_statistics.h
|
||||||
api/api_text_entities.cpp
|
api/api_text_entities.cpp
|
||||||
api/api_text_entities.h
|
api/api_text_entities.h
|
||||||
api/api_toggling_media.cpp
|
api/api_toggling_media.cpp
|
||||||
|
@ -517,6 +520,7 @@ PRIVATE
|
||||||
data/data_audio_msg_id.h
|
data/data_audio_msg_id.h
|
||||||
data/data_auto_download.cpp
|
data/data_auto_download.cpp
|
||||||
data/data_auto_download.h
|
data/data_auto_download.h
|
||||||
|
data/data_boosts.h
|
||||||
data/data_bot_app.cpp
|
data/data_bot_app.cpp
|
||||||
data/data_bot_app.h
|
data/data_bot_app.h
|
||||||
data/data_chat.cpp
|
data/data_chat.cpp
|
||||||
|
@ -619,6 +623,7 @@ PRIVATE
|
||||||
data/data_sparse_ids.h
|
data/data_sparse_ids.h
|
||||||
data/data_sponsored_messages.cpp
|
data/data_sponsored_messages.cpp
|
||||||
data/data_sponsored_messages.h
|
data/data_sponsored_messages.h
|
||||||
|
data/data_statistics.h
|
||||||
data/data_stories.cpp
|
data/data_stories.cpp
|
||||||
data/data_stories.h
|
data/data_stories.h
|
||||||
data/data_stories_ids.cpp
|
data/data_stories_ids.cpp
|
||||||
|
@ -892,6 +897,10 @@ PRIVATE
|
||||||
info/info_top_bar.h
|
info/info_top_bar.h
|
||||||
info/info_wrap_widget.cpp
|
info/info_wrap_widget.cpp
|
||||||
info/info_wrap_widget.h
|
info/info_wrap_widget.h
|
||||||
|
info/boosts/info_boosts_inner_widget.cpp
|
||||||
|
info/boosts/info_boosts_inner_widget.h
|
||||||
|
info/boosts/info_boosts_widget.cpp
|
||||||
|
info/boosts/info_boosts_widget.h
|
||||||
info/common_groups/info_common_groups_inner_widget.cpp
|
info/common_groups/info_common_groups_inner_widget.cpp
|
||||||
info/common_groups/info_common_groups_inner_widget.h
|
info/common_groups/info_common_groups_inner_widget.h
|
||||||
info/common_groups/info_common_groups_widget.cpp
|
info/common_groups/info_common_groups_widget.cpp
|
||||||
|
@ -947,6 +956,15 @@ PRIVATE
|
||||||
info/profile/info_profile_widget.h
|
info/profile/info_profile_widget.h
|
||||||
info/settings/info_settings_widget.cpp
|
info/settings/info_settings_widget.cpp
|
||||||
info/settings/info_settings_widget.h
|
info/settings/info_settings_widget.h
|
||||||
|
info/statistics/info_statistics_common.h
|
||||||
|
info/statistics/info_statistics_inner_widget.cpp
|
||||||
|
info/statistics/info_statistics_inner_widget.h
|
||||||
|
info/statistics/info_statistics_list_controllers.cpp
|
||||||
|
info/statistics/info_statistics_list_controllers.h
|
||||||
|
info/statistics/info_statistics_recent_message.cpp
|
||||||
|
info/statistics/info_statistics_recent_message.h
|
||||||
|
info/statistics/info_statistics_widget.cpp
|
||||||
|
info/statistics/info_statistics_widget.h
|
||||||
info/stories/info_stories_inner_widget.cpp
|
info/stories/info_stories_inner_widget.cpp
|
||||||
info/stories/info_stories_inner_widget.h
|
info/stories/info_stories_inner_widget.h
|
||||||
info/stories/info_stories_provider.cpp
|
info/stories/info_stories_provider.cpp
|
||||||
|
@ -1608,6 +1626,7 @@ elseif (APPLE)
|
||||||
PRE_LINK
|
PRE_LINK
|
||||||
COMMAND mkdir -p $<TARGET_FILE_DIR:Telegram>/../Resources
|
COMMAND mkdir -p $<TARGET_FILE_DIR:Telegram>/../Resources
|
||||||
COMMAND cp ${CMAKE_BINARY_DIR}/lib_ui.rcc $<TARGET_FILE_DIR:Telegram>/../Resources
|
COMMAND cp ${CMAKE_BINARY_DIR}/lib_ui.rcc $<TARGET_FILE_DIR:Telegram>/../Resources
|
||||||
|
COMMAND cp ${CMAKE_BINARY_DIR}/lib_spellcheck.rcc $<TARGET_FILE_DIR:Telegram>/../Resources
|
||||||
)
|
)
|
||||||
if (NOT build_macstore)
|
if (NOT build_macstore)
|
||||||
add_custom_command(TARGET Telegram
|
add_custom_command(TARGET Telegram
|
||||||
|
|
BIN
Telegram/Resources/animations/stats.tgs
Normal file
BIN
Telegram/Resources/icons/chat/mini_copy.png
Normal file
After Width: | Height: | Size: 324 B |
BIN
Telegram/Resources/icons/chat/mini_copy@2x.png
Normal file
After Width: | Height: | Size: 484 B |
BIN
Telegram/Resources/icons/chat/mini_copy@3x.png
Normal file
After Width: | Height: | Size: 623 B |
BIN
Telegram/Resources/icons/chat/mini_quote.png
Normal file
After Width: | Height: | Size: 374 B |
BIN
Telegram/Resources/icons/chat/mini_quote@2x.png
Normal file
After Width: | Height: | Size: 570 B |
BIN
Telegram/Resources/icons/chat/mini_quote@3x.png
Normal file
After Width: | Height: | Size: 849 B |
BIN
Telegram/Resources/icons/menu/stats.png
Normal file
After Width: | Height: | Size: 548 B |
BIN
Telegram/Resources/icons/menu/stats@2x.png
Normal file
After Width: | Height: | Size: 938 B |
BIN
Telegram/Resources/icons/menu/stats@3x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
|
@ -2686,6 +2686,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_formatting_link_create" = "Create";
|
"lng_formatting_link_create" = "Create";
|
||||||
|
|
||||||
"lng_text_copied" = "Text copied to clipboard.";
|
"lng_text_copied" = "Text copied to clipboard.";
|
||||||
|
"lng_code_copied" = "Code copied to clipboard.";
|
||||||
|
|
||||||
"lng_spellchecker_submenu" = "Spelling";
|
"lng_spellchecker_submenu" = "Spelling";
|
||||||
"lng_spellchecker_add" = "Add to Dictionary";
|
"lng_spellchecker_add" = "Add to Dictionary";
|
||||||
|
@ -4065,6 +4066,80 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
"lng_stories_link_invalid" = "This link is broken or has expired.";
|
"lng_stories_link_invalid" = "This link is broken or has expired.";
|
||||||
|
|
||||||
|
"lng_stats_title" = "Statistics";
|
||||||
|
"lng_stats_message_title" = "Message Statistic";
|
||||||
|
"lng_stats_zoom_out" = "Zoom Out";
|
||||||
|
|
||||||
|
"lng_stats_overview_title" = "Overview";
|
||||||
|
"lng_stats_overview_member_count" = "Followers";
|
||||||
|
"lng_stats_overview_mean_view_count" = "Views Per Post";
|
||||||
|
"lng_stats_overview_mean_share_count" = "Shared Per Post";
|
||||||
|
"lng_stats_overview_enabled_notifications" = "Enabled Notifications";
|
||||||
|
"lng_stats_overview_messages" = "Messages";
|
||||||
|
"lng_stats_overview_group_mean_view_count" = "Viewing Members";
|
||||||
|
"lng_stats_overview_group_mean_post_count" = "Posting Members";
|
||||||
|
"lng_stats_overview_message_private_shares" = "Private Shares";
|
||||||
|
"lng_stats_overview_message_public_shares" = "Public Shares";
|
||||||
|
"lng_stats_overview_message_views" = "Views";
|
||||||
|
"lng_stats_overview_message_public_share#one" = "{count} public share";
|
||||||
|
"lng_stats_overview_message_public_share#other" = "{count} public shares";
|
||||||
|
|
||||||
|
"lng_stats_members_title" = "Top members";
|
||||||
|
"lng_stats_admins_title" = "Top admins";
|
||||||
|
"lng_stats_inviters_title" = "Top inviters";
|
||||||
|
"lng_stats_member_messages#one" = "{count} message";
|
||||||
|
"lng_stats_member_messages#other" = "{count} messages";
|
||||||
|
"lng_stats_member_characters#one" = "{count} symbol per message";
|
||||||
|
"lng_stats_member_characters#other" = "{count} symbols per message";
|
||||||
|
"lng_stats_member_deletions#one" = "{count} deletions";
|
||||||
|
"lng_stats_member_deletions#other" = "{count} deletions";
|
||||||
|
"lng_stats_member_bans#one" = "{count} ban";
|
||||||
|
"lng_stats_member_bans#other" = "{count} bans";
|
||||||
|
"lng_stats_member_restrictions#one" = "{count} restriction";
|
||||||
|
"lng_stats_member_restrictions#other" = "{count} restrictions";
|
||||||
|
"lng_stats_member_invitations#one" = "{count} invitation";
|
||||||
|
"lng_stats_member_invitations#other" = "{count} invitations";
|
||||||
|
|
||||||
|
"lng_stats_recent_messages_title" = "Recent posts";
|
||||||
|
"lng_stats_recent_messages_views#one" = "{count} view";
|
||||||
|
"lng_stats_recent_messages_views#other" = "{count} views";
|
||||||
|
"lng_stats_recent_messages_shares#one" = "{count} share";
|
||||||
|
"lng_stats_recent_messages_shares#other" = "{count} shares";
|
||||||
|
|
||||||
|
"lng_stats_loading" = "Loading stats...";
|
||||||
|
"lng_stats_loading_subtext" = "Please wait a few moments while we generate your stats.";
|
||||||
|
|
||||||
|
"lng_chart_title_member_count" = "Growth";
|
||||||
|
"lng_chart_title_join" = "Followers";
|
||||||
|
"lng_chart_title_mute" = "Notifications";
|
||||||
|
"lng_chart_title_view_count_by_hour" = "Views by hours";
|
||||||
|
"lng_chart_title_view_count_by_source" = "Views by source";
|
||||||
|
"lng_chart_title_join_by_source" = "New followers by source";
|
||||||
|
"lng_chart_title_language" = "Languages";
|
||||||
|
"lng_chart_title_message_interaction" = "Interactions";
|
||||||
|
"lng_chart_title_instant_view_interaction" = "IV Interactions";
|
||||||
|
|
||||||
|
"lng_chart_title_group_join" = "Group members";
|
||||||
|
"lng_chart_title_group_join_by_source" = "New members by source";
|
||||||
|
"lng_chart_title_group_language" = "Members's primary language";
|
||||||
|
"lng_chart_title_group_message_content" = "Messages";
|
||||||
|
"lng_chart_title_group_action" = "Actions";
|
||||||
|
"lng_chart_title_group_day" = "Views by hours";
|
||||||
|
"lng_chart_title_group_week" = "Top days of week";
|
||||||
|
|
||||||
|
"lng_boosts_title" = "Boosts";
|
||||||
|
"lng_boosts_level" = "Level";
|
||||||
|
"lng_boosts_existing" = "Existing boosts";
|
||||||
|
"lng_boosts_premium_audience" = "Premium subscribers";
|
||||||
|
"lng_boosts_next_level" = "Boosts to level up";
|
||||||
|
"lng_boosts_list_title#one" = "{count} booster";
|
||||||
|
"lng_boosts_list_title#other" = "{count} boosters";
|
||||||
|
"lng_boosts_list_subtext" = "Your channel is currently boosted by these users.";
|
||||||
|
"lng_boosts_show_more" = "Show More Boosts";
|
||||||
|
"lng_boosts_list_status" = "boost expires on {date}";
|
||||||
|
"lng_boosts_link_title" = "Link for boosting";
|
||||||
|
"lng_boosts_link_subtext" = "Share this link with your subscribers to get more boosts.";
|
||||||
|
|
||||||
// Wnd specific
|
// Wnd specific
|
||||||
|
|
||||||
"lng_wnd_choose_program_menu" = "Choose Default Program...";
|
"lng_wnd_choose_program_menu" = "Choose Default Program...";
|
||||||
|
|
|
@ -10,5 +10,6 @@
|
||||||
<file alias="cloud_password/email.tgs">../../animations/cloud_password/email.tgs</file>
|
<file alias="cloud_password/email.tgs">../../animations/cloud_password/email.tgs</file>
|
||||||
<file alias="ttl.tgs">../../animations/ttl.tgs</file>
|
<file alias="ttl.tgs">../../animations/ttl.tgs</file>
|
||||||
<file alias="discussion.tgs">../../animations/discussion.tgs</file>
|
<file alias="discussion.tgs">../../animations/discussion.tgs</file>
|
||||||
|
<file alias="stats.tgs">../../animations/stats.tgs</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||||
ProcessorArchitecture="ARCHITECTURE"
|
ProcessorArchitecture="ARCHITECTURE"
|
||||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||||
Version="4.10.2.0" />
|
Version="4.10.4.0" />
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Telegram Desktop</DisplayName>
|
<DisplayName>Telegram Desktop</DisplayName>
|
||||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||||
|
|
|
@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 4,10,2,0
|
FILEVERSION 4,10,4,0
|
||||||
PRODUCTVERSION 4,10,2,0
|
PRODUCTVERSION 4,10,4,0
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -62,10 +62,10 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Radolyn Labs"
|
VALUE "CompanyName", "Radolyn Labs"
|
||||||
VALUE "FileDescription", "AyuGram Desktop"
|
VALUE "FileDescription", "AyuGram Desktop"
|
||||||
VALUE "FileVersion", "4.10.2.0"
|
VALUE "FileVersion", "4.10.4.0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||||
VALUE "ProductName", "AyuGram Desktop"
|
VALUE "ProductName", "AyuGram Desktop"
|
||||||
VALUE "ProductVersion", "4.10.2.0"
|
VALUE "ProductVersion", "4.10.4.0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
|
@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 4,10,2,0
|
FILEVERSION 4,10,4,0
|
||||||
PRODUCTVERSION 4,10,2,0
|
PRODUCTVERSION 4,10,4,0
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -53,10 +53,10 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Radolyn Labs"
|
VALUE "CompanyName", "Radolyn Labs"
|
||||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||||
VALUE "FileVersion", "4.10.2.0"
|
VALUE "FileVersion", "4.10.4.0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||||
VALUE "ProductName", "AyuGram Desktop"
|
VALUE "ProductName", "AyuGram Desktop"
|
||||||
VALUE "ProductVersion", "4.10.2.0"
|
VALUE "ProductVersion", "4.10.4.0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
570
Telegram/SourceFiles/api/api_statistics.cpp
Normal file
|
@ -0,0 +1,570 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "api/api_statistics.h"
|
||||||
|
|
||||||
|
#include "apiwrap.h"
|
||||||
|
#include "data/data_channel.h"
|
||||||
|
#include "data/data_peer.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "history/history.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "statistics/statistics_data_deserialize.h"
|
||||||
|
|
||||||
|
namespace Api {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
[[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL(
|
||||||
|
const MTPStatsGraph &tl) {
|
||||||
|
return tl.match([&](const MTPDstatsGraph &d) {
|
||||||
|
using namespace Statistic;
|
||||||
|
const auto zoomToken = d.vzoom_token().has_value()
|
||||||
|
? qs(*d.vzoom_token()).toUtf8()
|
||||||
|
: QByteArray();
|
||||||
|
return Data::StatisticalGraph{
|
||||||
|
StatisticalChartFromJSON(qs(d.vjson().data().vdata()).toUtf8()),
|
||||||
|
zoomToken,
|
||||||
|
};
|
||||||
|
}, [&](const MTPDstatsGraphAsync &data) {
|
||||||
|
return Data::StatisticalGraph{
|
||||||
|
.zoomToken = qs(data.vtoken()).toUtf8(),
|
||||||
|
};
|
||||||
|
}, [&](const MTPDstatsGraphError &data) {
|
||||||
|
return Data::StatisticalGraph{ .error = qs(data.verror()) };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Data::StatisticalValue StatisticalValueFromTL(
|
||||||
|
const MTPStatsAbsValueAndPrev &tl) {
|
||||||
|
const auto current = tl.data().vcurrent().v;
|
||||||
|
const auto previous = tl.data().vprevious().v;
|
||||||
|
return Data::StatisticalValue{
|
||||||
|
.value = current,
|
||||||
|
.previousValue = previous,
|
||||||
|
.growthRatePercentage = previous
|
||||||
|
? std::abs((current - previous) / float64(previous) * 100.)
|
||||||
|
: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Data::ChannelStatistics ChannelStatisticsFromTL(
|
||||||
|
const MTPDstats_broadcastStats &data) {
|
||||||
|
const auto &tlUnmuted = data.venabled_notifications().data();
|
||||||
|
const auto unmuted = (!tlUnmuted.vtotal().v)
|
||||||
|
? 0.
|
||||||
|
: std::clamp(
|
||||||
|
tlUnmuted.vpart().v / tlUnmuted.vtotal().v * 100.,
|
||||||
|
0.,
|
||||||
|
100.);
|
||||||
|
using Recent = MTPMessageInteractionCounters;
|
||||||
|
auto recentMessages = ranges::views::all(
|
||||||
|
data.vrecent_message_interactions().v
|
||||||
|
) | ranges::views::transform([&](const Recent &tl) {
|
||||||
|
return Data::StatisticsMessageInteractionInfo{
|
||||||
|
.messageId = tl.data().vmsg_id().v,
|
||||||
|
.viewsCount = tl.data().vviews().v,
|
||||||
|
.forwardsCount = tl.data().vforwards().v,
|
||||||
|
};
|
||||||
|
}) | ranges::to_vector;
|
||||||
|
|
||||||
|
return {
|
||||||
|
.startDate = data.vperiod().data().vmin_date().v,
|
||||||
|
.endDate = data.vperiod().data().vmax_date().v,
|
||||||
|
|
||||||
|
.memberCount = StatisticalValueFromTL(data.vfollowers()),
|
||||||
|
.meanViewCount = StatisticalValueFromTL(data.vviews_per_post()),
|
||||||
|
.meanShareCount = StatisticalValueFromTL(data.vshares_per_post()),
|
||||||
|
|
||||||
|
.enabledNotificationsPercentage = unmuted,
|
||||||
|
|
||||||
|
.memberCountGraph = StatisticalGraphFromTL(
|
||||||
|
data.vgrowth_graph()),
|
||||||
|
|
||||||
|
.joinGraph = StatisticalGraphFromTL(
|
||||||
|
data.vfollowers_graph()),
|
||||||
|
|
||||||
|
.muteGraph = StatisticalGraphFromTL(
|
||||||
|
data.vmute_graph()),
|
||||||
|
|
||||||
|
.viewCountByHourGraph = StatisticalGraphFromTL(
|
||||||
|
data.vtop_hours_graph()),
|
||||||
|
|
||||||
|
.viewCountBySourceGraph = StatisticalGraphFromTL(
|
||||||
|
data.vviews_by_source_graph()),
|
||||||
|
|
||||||
|
.joinBySourceGraph = StatisticalGraphFromTL(
|
||||||
|
data.vnew_followers_by_source_graph()),
|
||||||
|
|
||||||
|
.languageGraph = StatisticalGraphFromTL(
|
||||||
|
data.vlanguages_graph()),
|
||||||
|
|
||||||
|
.messageInteractionGraph = StatisticalGraphFromTL(
|
||||||
|
data.vinteractions_graph()),
|
||||||
|
|
||||||
|
.instantViewInteractionGraph = StatisticalGraphFromTL(
|
||||||
|
data.viv_interactions_graph()),
|
||||||
|
|
||||||
|
.recentMessageInteractions = std::move(recentMessages),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Data::SupergroupStatistics SupergroupStatisticsFromTL(
|
||||||
|
const MTPDstats_megagroupStats &data) {
|
||||||
|
using Senders = MTPStatsGroupTopPoster;
|
||||||
|
using Administrators = MTPStatsGroupTopAdmin;
|
||||||
|
using Inviters = MTPStatsGroupTopInviter;
|
||||||
|
|
||||||
|
auto topSenders = ranges::views::all(
|
||||||
|
data.vtop_posters().v
|
||||||
|
) | ranges::views::transform([&](const Senders &tl) {
|
||||||
|
return Data::StatisticsMessageSenderInfo{
|
||||||
|
.userId = UserId(tl.data().vuser_id().v),
|
||||||
|
.sentMessageCount = tl.data().vmessages().v,
|
||||||
|
.averageCharacterCount = tl.data().vavg_chars().v,
|
||||||
|
};
|
||||||
|
}) | ranges::to_vector;
|
||||||
|
auto topAdministrators = ranges::views::all(
|
||||||
|
data.vtop_admins().v
|
||||||
|
) | ranges::views::transform([&](const Administrators &tl) {
|
||||||
|
return Data::StatisticsAdministratorActionsInfo{
|
||||||
|
.userId = UserId(tl.data().vuser_id().v),
|
||||||
|
.deletedMessageCount = tl.data().vdeleted().v,
|
||||||
|
.bannedUserCount = tl.data().vkicked().v,
|
||||||
|
.restrictedUserCount = tl.data().vbanned().v,
|
||||||
|
};
|
||||||
|
}) | ranges::to_vector;
|
||||||
|
auto topInviters = ranges::views::all(
|
||||||
|
data.vtop_inviters().v
|
||||||
|
) | ranges::views::transform([&](const Inviters &tl) {
|
||||||
|
return Data::StatisticsInviterInfo{
|
||||||
|
.userId = UserId(tl.data().vuser_id().v),
|
||||||
|
.addedMemberCount = tl.data().vinvitations().v,
|
||||||
|
};
|
||||||
|
}) | ranges::to_vector;
|
||||||
|
|
||||||
|
return {
|
||||||
|
.startDate = data.vperiod().data().vmin_date().v,
|
||||||
|
.endDate = data.vperiod().data().vmax_date().v,
|
||||||
|
|
||||||
|
.memberCount = StatisticalValueFromTL(data.vmembers()),
|
||||||
|
.messageCount = StatisticalValueFromTL(data.vmessages()),
|
||||||
|
.viewerCount = StatisticalValueFromTL(data.vviewers()),
|
||||||
|
.senderCount = StatisticalValueFromTL(data.vposters()),
|
||||||
|
|
||||||
|
.memberCountGraph = StatisticalGraphFromTL(
|
||||||
|
data.vgrowth_graph()),
|
||||||
|
|
||||||
|
.joinGraph = StatisticalGraphFromTL(
|
||||||
|
data.vmembers_graph()),
|
||||||
|
|
||||||
|
.joinBySourceGraph = StatisticalGraphFromTL(
|
||||||
|
data.vnew_members_by_source_graph()),
|
||||||
|
|
||||||
|
.languageGraph = StatisticalGraphFromTL(
|
||||||
|
data.vlanguages_graph()),
|
||||||
|
|
||||||
|
.messageContentGraph = StatisticalGraphFromTL(
|
||||||
|
data.vmessages_graph()),
|
||||||
|
|
||||||
|
.actionGraph = StatisticalGraphFromTL(
|
||||||
|
data.vactions_graph()),
|
||||||
|
|
||||||
|
.dayGraph = StatisticalGraphFromTL(
|
||||||
|
data.vtop_hours_graph()),
|
||||||
|
|
||||||
|
.weekGraph = StatisticalGraphFromTL(
|
||||||
|
data.vweekdays_graph()),
|
||||||
|
|
||||||
|
.topSenders = std::move(topSenders),
|
||||||
|
.topAdministrators = std::move(topAdministrators),
|
||||||
|
.topInviters = std::move(topInviters),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Statistics::Statistics(not_null<ApiWrap*> api)
|
||||||
|
: _api(&api->instance()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<rpl::no_value, QString> Statistics::request(
|
||||||
|
not_null<PeerData*> peer) {
|
||||||
|
return [=](auto consumer) {
|
||||||
|
auto lifetime = rpl::lifetime();
|
||||||
|
const auto channel = peer->asChannel();
|
||||||
|
if (!channel) {
|
||||||
|
return lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!channel->isMegagroup()) {
|
||||||
|
_api.request(MTPstats_GetBroadcastStats(
|
||||||
|
MTP_flags(MTPstats_GetBroadcastStats::Flags(0)),
|
||||||
|
channel->inputChannel
|
||||||
|
)).done([=](const MTPstats_BroadcastStats &result) {
|
||||||
|
_channelStats = ChannelStatisticsFromTL(result.data());
|
||||||
|
consumer.put_done();
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
consumer.put_error_copy(error.type());
|
||||||
|
}).send();
|
||||||
|
} else {
|
||||||
|
_api.request(MTPstats_GetMegagroupStats(
|
||||||
|
MTP_flags(MTPstats_GetMegagroupStats::Flags(0)),
|
||||||
|
channel->inputChannel
|
||||||
|
)).done([=](const MTPstats_MegagroupStats &result) {
|
||||||
|
_supergroupStats = SupergroupStatisticsFromTL(result.data());
|
||||||
|
peer->owner().processUsers(result.data().vusers());
|
||||||
|
consumer.put_done();
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
consumer.put_error_copy(error.type());
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
return lifetime;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Statistics::GraphResult Statistics::requestZoom(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
const QString &token,
|
||||||
|
float64 x) {
|
||||||
|
return [=](auto consumer) {
|
||||||
|
auto lifetime = rpl::lifetime();
|
||||||
|
const auto channel = peer->asChannel();
|
||||||
|
if (!channel) {
|
||||||
|
return lifetime;
|
||||||
|
}
|
||||||
|
const auto wasEmpty = _zoomDeque.empty();
|
||||||
|
_zoomDeque.push_back([=] {
|
||||||
|
_api.request(MTPstats_LoadAsyncGraph(
|
||||||
|
MTP_flags(x
|
||||||
|
? MTPstats_LoadAsyncGraph::Flag::f_x
|
||||||
|
: MTPstats_LoadAsyncGraph::Flag(0)),
|
||||||
|
MTP_string(token),
|
||||||
|
MTP_long(x)
|
||||||
|
)).done([=](const MTPStatsGraph &result) {
|
||||||
|
consumer.put_next(StatisticalGraphFromTL(result));
|
||||||
|
consumer.put_done();
|
||||||
|
if (!_zoomDeque.empty()) {
|
||||||
|
_zoomDeque.pop_front();
|
||||||
|
if (!_zoomDeque.empty()) {
|
||||||
|
_zoomDeque.front()();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
consumer.put_error_copy(error.type());
|
||||||
|
}).send();
|
||||||
|
});
|
||||||
|
if (wasEmpty) {
|
||||||
|
_zoomDeque.front()();
|
||||||
|
}
|
||||||
|
|
||||||
|
return lifetime;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Statistics::GraphResult Statistics::requestMessage(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
MsgId msgId) {
|
||||||
|
return [=](auto consumer) {
|
||||||
|
auto lifetime = rpl::lifetime();
|
||||||
|
const auto channel = peer->asChannel();
|
||||||
|
if (!channel) {
|
||||||
|
return lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
_api.request(MTPstats_GetMessageStats(
|
||||||
|
MTP_flags(MTPstats_GetMessageStats::Flags(0)),
|
||||||
|
channel->inputChannel,
|
||||||
|
MTP_int(msgId.bare)
|
||||||
|
)).done([=](const MTPstats_MessageStats &result) {
|
||||||
|
consumer.put_next(
|
||||||
|
StatisticalGraphFromTL(result.data().vviews_graph()));
|
||||||
|
consumer.put_done();
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
consumer.put_error_copy(error.type());
|
||||||
|
}).send();
|
||||||
|
|
||||||
|
return lifetime;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Data::ChannelStatistics Statistics::channelStats() const {
|
||||||
|
return _channelStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
Data::SupergroupStatistics Statistics::supergroupStats() const {
|
||||||
|
return _supergroupStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
PublicForwards::PublicForwards(
|
||||||
|
not_null<ChannelData*> channel,
|
||||||
|
FullMsgId fullId)
|
||||||
|
: _channel(channel)
|
||||||
|
, _fullId(fullId)
|
||||||
|
, _api(&channel->session().api().instance()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void PublicForwards::request(
|
||||||
|
const Data::PublicForwardsSlice::OffsetToken &token,
|
||||||
|
Fn<void(Data::PublicForwardsSlice)> done) {
|
||||||
|
if (_requestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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 = _api.request(MTPstats_GetMessagePublicForwards(
|
||||||
|
_channel->inputChannel,
|
||||||
|
MTP_int(_fullId.msg),
|
||||||
|
MTP_int(token.rate),
|
||||||
|
tlOffsetPeer,
|
||||||
|
MTP_int(token.fullId.msg),
|
||||||
|
kLimit
|
||||||
|
)).done([=, channel = _channel](const MTPmessages_Messages &result) {
|
||||||
|
using Messages = QVector<FullMsgId>;
|
||||||
|
_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(nextToken.fullId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
_lastTotal = std::max(_lastTotal, fullCount);
|
||||||
|
done({
|
||||||
|
.list = std::move(messages),
|
||||||
|
.total = _lastTotal,
|
||||||
|
.allLoaded = allLoaded,
|
||||||
|
.token = nextToken,
|
||||||
|
});
|
||||||
|
}).fail([=] {
|
||||||
|
_requestId = 0;
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageStatistics::MessageStatistics(
|
||||||
|
not_null<ChannelData*> channel,
|
||||||
|
FullMsgId fullId)
|
||||||
|
: _publicForwards(channel, fullId)
|
||||||
|
, _channel(channel)
|
||||||
|
, _fullId(fullId)
|
||||||
|
, _api(&channel->session().api().instance()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Data::PublicForwardsSlice MessageStatistics::firstSlice() const {
|
||||||
|
return _firstSlice;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageStatistics::request(Fn<void(Data::MessageStatistics)> done) {
|
||||||
|
if (_channel->isMegagroup()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto requestFirstPublicForwards = [=](
|
||||||
|
const Data::StatisticalGraph &messageGraph,
|
||||||
|
const Data::StatisticsMessageInteractionInfo &info) {
|
||||||
|
_publicForwards.request({}, [=](Data::PublicForwardsSlice slice) {
|
||||||
|
const auto total = slice.total;
|
||||||
|
_firstSlice = std::move(slice);
|
||||||
|
done({
|
||||||
|
.messageInteractionGraph = messageGraph,
|
||||||
|
.publicForwards = total,
|
||||||
|
.privateForwards = info.forwardsCount - total,
|
||||||
|
.views = info.viewsCount,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto requestPrivateForwards = [=](
|
||||||
|
const Data::StatisticalGraph &messageGraph) {
|
||||||
|
_api.request(MTPstats_GetBroadcastStats(
|
||||||
|
MTP_flags(MTPstats_GetBroadcastStats::Flags(0)),
|
||||||
|
_channel->inputChannel
|
||||||
|
)).done([=](const MTPstats_BroadcastStats &result) {
|
||||||
|
const auto channelStats = ChannelStatisticsFromTL(result.data());
|
||||||
|
auto info = Data::StatisticsMessageInteractionInfo();
|
||||||
|
for (const auto &r : channelStats.recentMessageInteractions) {
|
||||||
|
if (r.messageId == _fullId.msg) {
|
||||||
|
info = r;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
requestFirstPublicForwards(messageGraph, info);
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
requestFirstPublicForwards(messageGraph, {});
|
||||||
|
}).send();
|
||||||
|
};
|
||||||
|
|
||||||
|
_api.request(MTPstats_GetMessageStats(
|
||||||
|
MTP_flags(MTPstats_GetMessageStats::Flags(0)),
|
||||||
|
_channel->inputChannel,
|
||||||
|
MTP_int(_fullId.msg.bare)
|
||||||
|
)).done([=](const MTPstats_MessageStats &result) {
|
||||||
|
requestPrivateForwards(
|
||||||
|
StatisticalGraphFromTL(result.data().vviews_graph()));
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
requestPrivateForwards({});
|
||||||
|
}).send();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Boosts::Boosts(not_null<PeerData*> peer)
|
||||||
|
: _peer(peer)
|
||||||
|
, _api(&peer->session().api().instance()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<rpl::no_value, QString> Boosts::request() {
|
||||||
|
return [=](auto consumer) {
|
||||||
|
auto lifetime = rpl::lifetime();
|
||||||
|
const auto channel = _peer->asChannel();
|
||||||
|
if (!channel || channel->isMegagroup()) {
|
||||||
|
return lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
_api.request(MTPstories_GetBoostsStatus(
|
||||||
|
_peer->input
|
||||||
|
)).done([=](const MTPstories_BoostsStatus &result) {
|
||||||
|
const auto &data = result.data();
|
||||||
|
const auto hasPremium = !!data.vpremium_audience();
|
||||||
|
const auto premiumMemberCount = hasPremium
|
||||||
|
? std::max(0, int(data.vpremium_audience()->data().vpart().v))
|
||||||
|
: 0;
|
||||||
|
const auto participantCount = hasPremium
|
||||||
|
? std::max(
|
||||||
|
int(data.vpremium_audience()->data().vtotal().v),
|
||||||
|
premiumMemberCount)
|
||||||
|
: 0;
|
||||||
|
const auto premiumMemberPercentage = (participantCount > 0)
|
||||||
|
? (100. * premiumMemberCount / participantCount)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
_boostStatus.overview = Data::BoostsOverview{
|
||||||
|
.isBoosted = data.is_my_boost(),
|
||||||
|
.level = std::max(data.vlevel().v, 0),
|
||||||
|
.boostCount = std::max(
|
||||||
|
data.vboosts().v,
|
||||||
|
data.vcurrent_level_boosts().v),
|
||||||
|
.currentLevelBoostCount = data.vcurrent_level_boosts().v,
|
||||||
|
.nextLevelBoostCount = data.vnext_level_boosts()
|
||||||
|
? data.vnext_level_boosts()->v
|
||||||
|
: 0,
|
||||||
|
.premiumMemberCount = premiumMemberCount,
|
||||||
|
.premiumMemberPercentage = premiumMemberPercentage,
|
||||||
|
};
|
||||||
|
_boostStatus.link = qs(data.vboost_url());
|
||||||
|
|
||||||
|
requestBoosts({}, [=](Data::BoostsListSlice &&slice) {
|
||||||
|
_boostStatus.firstSlice = std::move(slice);
|
||||||
|
consumer.put_done();
|
||||||
|
});
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
consumer.put_error_copy(error.type());
|
||||||
|
}).send();
|
||||||
|
|
||||||
|
return lifetime;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Boosts::requestBoosts(
|
||||||
|
const Data::BoostsListSlice::OffsetToken &token,
|
||||||
|
Fn<void(Data::BoostsListSlice)> done) {
|
||||||
|
if (_requestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
|
||||||
|
constexpr auto kTlLimit = tl::make_int(kLimit);
|
||||||
|
_requestId = _api.request(MTPstories_GetBoostersList(
|
||||||
|
_peer->input,
|
||||||
|
MTP_string(token.next),
|
||||||
|
token.next.isEmpty() ? kTlFirstSlice : kTlLimit
|
||||||
|
)).done([=](const MTPstories_BoostersList &result) {
|
||||||
|
_requestId = 0;
|
||||||
|
|
||||||
|
const auto &data = result.data();
|
||||||
|
_peer->owner().processUsers(data.vusers());
|
||||||
|
|
||||||
|
auto list = std::vector<Data::Boost>();
|
||||||
|
list.reserve(data.vboosters().v.size());
|
||||||
|
for (const auto &boost : data.vboosters().v) {
|
||||||
|
list.push_back({
|
||||||
|
boost.data().vuser_id().v,
|
||||||
|
QDateTime::fromSecsSinceEpoch(boost.data().vexpires().v),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
done(Data::BoostsListSlice{
|
||||||
|
.list = std::move(list),
|
||||||
|
.total = data.vcount().v,
|
||||||
|
.allLoaded = (data.vcount().v == data.vboosters().v.size()),
|
||||||
|
.token = Data::BoostsListSlice::OffsetToken{
|
||||||
|
data.vnext_offset()
|
||||||
|
? qs(*data.vnext_offset())
|
||||||
|
: QString()
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}).fail([=] {
|
||||||
|
_requestId = 0;
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
Data::BoostStatus Boosts::boostStatus() const {
|
||||||
|
return _boostStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Api
|
110
Telegram/SourceFiles/api/api_statistics.h
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "data/data_boosts.h"
|
||||||
|
#include "data/data_statistics.h"
|
||||||
|
#include "mtproto/sender.h"
|
||||||
|
|
||||||
|
class ApiWrap;
|
||||||
|
class ChannelData;
|
||||||
|
class PeerData;
|
||||||
|
|
||||||
|
namespace Api {
|
||||||
|
|
||||||
|
class Statistics final {
|
||||||
|
public:
|
||||||
|
explicit Statistics(not_null<ApiWrap*> api);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<rpl::no_value, QString> request(
|
||||||
|
not_null<PeerData*> peer);
|
||||||
|
using GraphResult = rpl::producer<Data::StatisticalGraph, QString>;
|
||||||
|
[[nodiscard]] GraphResult requestZoom(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
const QString &token,
|
||||||
|
float64 x);
|
||||||
|
[[nodiscard]] GraphResult requestMessage(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
MsgId msgId);
|
||||||
|
|
||||||
|
[[nodiscard]] Data::ChannelStatistics channelStats() const;
|
||||||
|
[[nodiscard]] Data::SupergroupStatistics supergroupStats() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Data::ChannelStatistics _channelStats;
|
||||||
|
Data::SupergroupStatistics _supergroupStats;
|
||||||
|
MTP::Sender _api;
|
||||||
|
|
||||||
|
std::deque<Fn<void()>> _zoomDeque;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class PublicForwards final {
|
||||||
|
public:
|
||||||
|
explicit PublicForwards(not_null<ChannelData*> channel, FullMsgId fullId);
|
||||||
|
|
||||||
|
void request(
|
||||||
|
const Data::PublicForwardsSlice::OffsetToken &token,
|
||||||
|
Fn<void(Data::PublicForwardsSlice)> done);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const not_null<ChannelData*> _channel;
|
||||||
|
const FullMsgId _fullId;
|
||||||
|
mtpRequestId _requestId = 0;
|
||||||
|
int _lastTotal = 0;
|
||||||
|
|
||||||
|
MTP::Sender _api;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class MessageStatistics final {
|
||||||
|
public:
|
||||||
|
explicit MessageStatistics(
|
||||||
|
not_null<ChannelData*> channel,
|
||||||
|
FullMsgId fullId);
|
||||||
|
|
||||||
|
void request(Fn<void(Data::MessageStatistics)> done);
|
||||||
|
|
||||||
|
[[nodiscard]] Data::PublicForwardsSlice firstSlice() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
PublicForwards _publicForwards;
|
||||||
|
const not_null<ChannelData*> _channel;
|
||||||
|
const FullMsgId _fullId;
|
||||||
|
|
||||||
|
Data::PublicForwardsSlice _firstSlice;
|
||||||
|
|
||||||
|
mtpRequestId _requestId = 0;
|
||||||
|
MTP::Sender _api;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class Boosts final {
|
||||||
|
public:
|
||||||
|
explicit Boosts(not_null<PeerData*> peer);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||||
|
void requestBoosts(
|
||||||
|
const Data::BoostsListSlice::OffsetToken &token,
|
||||||
|
Fn<void(Data::BoostsListSlice)> done);
|
||||||
|
|
||||||
|
[[nodiscard]] Data::BoostStatus boostStatus() const;
|
||||||
|
|
||||||
|
static constexpr auto kFirstSlice = int(10);
|
||||||
|
static constexpr auto kLimit = int(40);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const not_null<PeerData*> _peer;
|
||||||
|
Data::BoostStatus _boostStatus;
|
||||||
|
|
||||||
|
MTP::Sender _api;
|
||||||
|
mtpRequestId _requestId = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Api
|
|
@ -85,8 +85,6 @@ confirmInviteTitle: FlatLabel(defaultFlatLabel) {
|
||||||
textFg: windowBoldFg;
|
textFg: windowBoldFg;
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(18px semibold);
|
font: font(18px semibold);
|
||||||
linkFont: font(18px semibold);
|
|
||||||
linkFontOver: font(18px semibold underline);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
confirmInviteAbout: FlatLabel(boxLabel) {
|
confirmInviteAbout: FlatLabel(boxLabel) {
|
||||||
|
@ -143,8 +141,6 @@ contactsPadding: margins(16px, 7px, 16px, 7px);
|
||||||
contactsNameTop: 2px;
|
contactsNameTop: 2px;
|
||||||
contactsNameStyle: TextStyle(defaultTextStyle) {
|
contactsNameStyle: TextStyle(defaultTextStyle) {
|
||||||
font: semiboldFont;
|
font: semiboldFont;
|
||||||
linkFont: semiboldFont;
|
|
||||||
linkFontOver: semiboldFont;
|
|
||||||
}
|
}
|
||||||
contactsStatusTop: 23px;
|
contactsStatusTop: 23px;
|
||||||
contactsStatusFont: font(fsize);
|
contactsStatusFont: font(fsize);
|
||||||
|
@ -199,8 +195,6 @@ localStorageRowTitle: FlatLabel(defaultFlatLabel) {
|
||||||
maxHeight: 20px;
|
maxHeight: 20px;
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(14px semibold);
|
font: font(14px semibold);
|
||||||
linkFont: font(14px semibold);
|
|
||||||
linkFontOver: font(14px semibold);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
localStorageRowSize: FlatLabel(defaultFlatLabel) {
|
localStorageRowSize: FlatLabel(defaultFlatLabel) {
|
||||||
|
@ -208,8 +202,6 @@ localStorageRowSize: FlatLabel(defaultFlatLabel) {
|
||||||
maxHeight: 20px;
|
maxHeight: 20px;
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(14px);
|
font: font(14px);
|
||||||
linkFont: font(14px);
|
|
||||||
linkFontOver: font(14px);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
localStorageClear: defaultBoxButton;
|
localStorageClear: defaultBoxButton;
|
||||||
|
@ -228,8 +220,6 @@ sharePhotoTop: 6px;
|
||||||
shareBoxListItem: PeerListItem(defaultPeerListItem) {
|
shareBoxListItem: PeerListItem(defaultPeerListItem) {
|
||||||
nameStyle: TextStyle(defaultTextStyle) {
|
nameStyle: TextStyle(defaultTextStyle) {
|
||||||
font: font(11px);
|
font: font(11px);
|
||||||
linkFont: font(11px);
|
|
||||||
linkFontOver: font(11px);
|
|
||||||
}
|
}
|
||||||
nameFg: windowFg;
|
nameFg: windowFg;
|
||||||
nameFgChecked: windowActiveTextFg;
|
nameFgChecked: windowActiveTextFg;
|
||||||
|
@ -537,8 +527,6 @@ adminLogFilterLittleSkip: 16px;
|
||||||
adminLogFilterCheckbox: Checkbox(defaultBoxCheckbox) {
|
adminLogFilterCheckbox: Checkbox(defaultBoxCheckbox) {
|
||||||
style: TextStyle(boxTextStyle) {
|
style: TextStyle(boxTextStyle) {
|
||||||
font: font(boxFontSize semibold);
|
font: font(boxFontSize semibold);
|
||||||
linkFont: font(boxFontSize semibold);
|
|
||||||
linkFontOver: font(boxFontSize semibold underline);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adminLogFilterSkip: 32px;
|
adminLogFilterSkip: 32px;
|
||||||
|
@ -580,16 +568,12 @@ rightsPhotoButton: UserpicButton(defaultUserpicButton) {
|
||||||
rightsPhotoMargin: margins(20px, 0px, 15px, 18px);
|
rightsPhotoMargin: margins(20px, 0px, 15px, 18px);
|
||||||
rightsNameStyle: TextStyle(semiboldTextStyle) {
|
rightsNameStyle: TextStyle(semiboldTextStyle) {
|
||||||
font: font(15px semibold);
|
font: font(15px semibold);
|
||||||
linkFont: font(15px semibold);
|
|
||||||
linkFontOver: font(15px semibold underline);
|
|
||||||
}
|
}
|
||||||
rightsNameTop: 8px;
|
rightsNameTop: 8px;
|
||||||
rightsStatusTop: 32px;
|
rightsStatusTop: 32px;
|
||||||
rightsHeaderLabel: FlatLabel(boxLabel) {
|
rightsHeaderLabel: FlatLabel(boxLabel) {
|
||||||
style: TextStyle(semiboldTextStyle) {
|
style: TextStyle(semiboldTextStyle) {
|
||||||
font: font(boxFontSize semibold);
|
font: font(boxFontSize semibold);
|
||||||
linkFont: font(boxFontSize semibold);
|
|
||||||
linkFontOver: font(boxFontSize semibold underline);
|
|
||||||
}
|
}
|
||||||
textFg: windowActiveTextFg;
|
textFg: windowActiveTextFg;
|
||||||
}
|
}
|
||||||
|
@ -623,8 +607,6 @@ proxyRowTitlePalette: TextPalette(defaultTextPalette) {
|
||||||
}
|
}
|
||||||
proxyRowTitleStyle: TextStyle(defaultTextStyle) {
|
proxyRowTitleStyle: TextStyle(defaultTextStyle) {
|
||||||
font: semiboldFont;
|
font: semiboldFont;
|
||||||
linkFont: normalFont;
|
|
||||||
linkFontOver: normalFont;
|
|
||||||
}
|
}
|
||||||
proxyRowStatusFg: windowSubTextFg;
|
proxyRowStatusFg: windowSubTextFg;
|
||||||
proxyRowStatusFgOnline: windowActiveTextFg;
|
proxyRowStatusFgOnline: windowActiveTextFg;
|
||||||
|
@ -807,8 +789,6 @@ pollResultsQuestion: FlatLabel(defaultFlatLabel) {
|
||||||
textFg: windowBoldFg;
|
textFg: windowBoldFg;
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(16px semibold);
|
font: font(16px semibold);
|
||||||
linkFont: font(16px semibold);
|
|
||||||
linkFontOver: font(16px semibold underline);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pollResultsVotesCount: FlatLabel(defaultFlatLabel) {
|
pollResultsVotesCount: FlatLabel(defaultFlatLabel) {
|
||||||
|
@ -837,8 +817,6 @@ inviteViaLinkButton: SettingsButton(defaultSettingsButton) {
|
||||||
|
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(14px semibold);
|
font: font(14px semibold);
|
||||||
linkFont: font(14px semibold);
|
|
||||||
linkFontOver: font(14px semibold underline);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
|
|
@ -194,7 +194,7 @@ not_null<Ui::FlatLabel*> CreateWarningLabel(
|
||||||
if (value >= 0) {
|
if (value >= 0) {
|
||||||
result->setText(QString::number(value));
|
result->setText(QString::number(value));
|
||||||
} else {
|
} else {
|
||||||
result->setMarkedText(Ui::Text::PlainLink(
|
result->setMarkedText(Ui::Text::Colorized(
|
||||||
QString::number(value)));
|
QString::number(value)));
|
||||||
}
|
}
|
||||||
result->setVisible(shown);
|
result->setVisible(shown);
|
||||||
|
|
|
@ -170,7 +170,7 @@ void PeerListRowWithLink::rightActionPaint(
|
||||||
int outerWidth,
|
int outerWidth,
|
||||||
bool selected,
|
bool selected,
|
||||||
bool actionSelected) {
|
bool actionSelected) {
|
||||||
p.setFont(actionSelected ? st::linkOverFont : st::linkFont);
|
p.setFont(actionSelected ? st::linkFontOver : st::linkFont);
|
||||||
p.setPen(actionSelected ? st::defaultLinkButton.overColor : st::defaultLinkButton.color);
|
p.setPen(actionSelected ? st::defaultLinkButton.overColor : st::defaultLinkButton.color);
|
||||||
p.drawTextLeft(x, y, outerWidth, _action, _actionWidth);
|
p.drawTextLeft(x, y, outerWidth, _action, _actionWidth);
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,8 +97,6 @@ callButtonLabel: FlatLabel(defaultFlatLabel) {
|
||||||
textFg: callNameFg;
|
textFg: callNameFg;
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(11px);
|
font: font(11px);
|
||||||
linkFont: font(11px);
|
|
||||||
linkFontOver: font(11px underline);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,8 +216,6 @@ callMuteButtonLabel: FlatLabel(defaultFlatLabel) {
|
||||||
textFg: groupCallMembersFg;
|
textFg: groupCallMembersFg;
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(14px);
|
font: font(14px);
|
||||||
linkFont: font(14px);
|
|
||||||
linkFontOver: font(14px underline);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callMuteButtonActiveInner: IconButton {
|
callMuteButtonActiveInner: IconButton {
|
||||||
|
@ -294,8 +290,6 @@ callName: FlatLabel(defaultFlatLabel) {
|
||||||
align: align(top);
|
align: align(top);
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(21px semibold);
|
font: font(21px semibold);
|
||||||
linkFont: font(21px semibold);
|
|
||||||
linkFontOver: font(21px semibold underline);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callStatus: FlatLabel(defaultFlatLabel) {
|
callStatus: FlatLabel(defaultFlatLabel) {
|
||||||
|
@ -305,8 +299,6 @@ callStatus: FlatLabel(defaultFlatLabel) {
|
||||||
align: align(top);
|
align: align(top);
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(14px);
|
font: font(14px);
|
||||||
linkFont: font(14px);
|
|
||||||
linkFontOver: font(14px underline);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callRemoteAudioMute: FlatLabel(callStatus) {
|
callRemoteAudioMute: FlatLabel(callStatus) {
|
||||||
|
@ -314,8 +306,6 @@ callRemoteAudioMute: FlatLabel(callStatus) {
|
||||||
textFg: videoPlayIconFg;
|
textFg: videoPlayIconFg;
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(12px);
|
font: font(12px);
|
||||||
linkFont: font(12px);
|
|
||||||
linkFontOver: font(12px underline);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callRemoteAudioMuteSkip: 12px;
|
callRemoteAudioMuteSkip: 12px;
|
||||||
|
@ -746,8 +736,6 @@ groupCallShareBoxList: PeerList(groupCallMembersList) {
|
||||||
item: PeerListItem(groupCallMembersListItem) {
|
item: PeerListItem(groupCallMembersListItem) {
|
||||||
nameStyle: TextStyle(defaultTextStyle) {
|
nameStyle: TextStyle(defaultTextStyle) {
|
||||||
font: font(11px);
|
font: font(11px);
|
||||||
linkFont: font(11px);
|
|
||||||
linkFontOver: font(11px);
|
|
||||||
}
|
}
|
||||||
checkbox: RoundImageCheckbox(groupCallMembersListCheckbox) {
|
checkbox: RoundImageCheckbox(groupCallMembersListCheckbox) {
|
||||||
imageRadius: 28px;
|
imageRadius: 28px;
|
||||||
|
@ -784,8 +772,6 @@ groupCallTitleLabel: FlatLabel(groupCallSubtitleLabel) {
|
||||||
textFg: groupCallMembersFg;
|
textFg: groupCallMembersFg;
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(semibold 14px);
|
font: font(semibold 14px);
|
||||||
linkFont: font(semibold 14px);
|
|
||||||
linkFontOver: font(semibold 14px);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
groupCallTitleSeparator: 4px;
|
groupCallTitleSeparator: 4px;
|
||||||
|
@ -1203,8 +1189,6 @@ callTopBarMuteCrossLine: CrossLineAnimation {
|
||||||
groupCallStartsIn: FlatLabel(defaultFlatLabel) {
|
groupCallStartsIn: FlatLabel(defaultFlatLabel) {
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(20px semibold);
|
font: font(20px semibold);
|
||||||
linkFont: font(20px semibold);
|
|
||||||
linkFontOver: font(20px semibold underline);
|
|
||||||
}
|
}
|
||||||
textFg: groupCallMembersFg;
|
textFg: groupCallMembersFg;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "styles/style_calls.h"
|
#include "styles/style_calls.h"
|
||||||
|
|
||||||
#include <QtCore/QDateTime>
|
#include <QtCore/QDateTime>
|
||||||
|
#include <QtCore/QLocale>
|
||||||
|
|
||||||
namespace Calls::Group::Ui {
|
namespace Calls::Group::Ui {
|
||||||
|
|
||||||
|
|
|
@ -509,8 +509,6 @@ emojiPanColorAllLabel: FlatLabel(defaultFlatLabel) {
|
||||||
minWidth: 40px;
|
minWidth: 40px;
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(12px);
|
font: font(12px);
|
||||||
linkFont: font(12px);
|
|
||||||
linkFontOver: font(12px);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emojiPanColorAllPadding: margins(10px, 6px, 10px, -1px);
|
emojiPanColorAllPadding: margins(10px, 6px, 10px, -1px);
|
||||||
|
|
|
@ -787,7 +787,7 @@ void MessageLinksParser::parse() {
|
||||||
}
|
}
|
||||||
offset = matchOffset = p - start;
|
offset = matchOffset = p - start;
|
||||||
}
|
}
|
||||||
processTagsBefore(QFIXED_MAX);
|
processTagsBefore(Ui::kQFixedMax);
|
||||||
|
|
||||||
apply(text, ranges);
|
apply(text, ranges);
|
||||||
}
|
}
|
||||||
|
|
|
@ -512,14 +512,16 @@ void Application::startMediaView() {
|
||||||
InvokeQueued(this, [=] {
|
InvokeQueued(this, [=] {
|
||||||
_mediaView = std::make_unique<Media::View::OverlayWidget>();
|
_mediaView = std::make_unique<Media::View::OverlayWidget>();
|
||||||
});
|
});
|
||||||
#else // Q_OS_MAC
|
#elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_WIN
|
||||||
// On Windows we needed such hack for the main window, otherwise
|
// On Windows we needed such hack for the main window, otherwise
|
||||||
// somewhere inside the media viewer creating code its geometry
|
// somewhere inside the media viewer creating code its geometry
|
||||||
// was broken / lost to some invalid values.
|
// was broken / lost to some invalid values.
|
||||||
const auto current = _lastActivePrimaryWindow->widget()->geometry();
|
const auto current = _lastActivePrimaryWindow->widget()->geometry();
|
||||||
_mediaView = std::make_unique<Media::View::OverlayWidget>();
|
_mediaView = std::make_unique<Media::View::OverlayWidget>();
|
||||||
_lastActivePrimaryWindow->widget()->Ui::RpWidget::setGeometry(current);
|
_lastActivePrimaryWindow->widget()->Ui::RpWidget::setGeometry(current);
|
||||||
#endif // Q_OS_MAC
|
#else
|
||||||
|
_mediaView = std::make_unique<Media::View::OverlayWidget>();
|
||||||
|
#endif // Q_OS_MAC || Q_OS_WIN
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::startTray() {
|
void Application::startTray() {
|
||||||
|
|
|
@ -30,6 +30,16 @@ std::map<int, const char*> BetaLogs() {
|
||||||
|
|
||||||
"- Fix memory leak in Direct3D 11 media viewer on Windows.\n"
|
"- Fix memory leak in Direct3D 11 media viewer on Windows.\n"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
4010004,
|
||||||
|
"- Statistics in channels and group chats.\n"
|
||||||
|
|
||||||
|
"- Nice looking code blocks with syntax highlight.\n"
|
||||||
|
|
||||||
|
"- Copy full code block by click on its header.\n"
|
||||||
|
|
||||||
|
"- Send a highlighted code block using ```language syntax.\n"
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -94,9 +94,10 @@ const auto CommandByName = base::flat_map<QString, Command>{
|
||||||
{ u"read_chat"_q , Command::ReadChat },
|
{ u"read_chat"_q , Command::ReadChat },
|
||||||
|
|
||||||
// Shortcuts that have no default values.
|
// Shortcuts that have no default values.
|
||||||
{ u"message"_q , Command::JustSendMessage },
|
{ u"message"_q , Command::JustSendMessage },
|
||||||
{ u"message_silently"_q , Command::SendSilentMessage },
|
{ u"message_silently"_q , Command::SendSilentMessage },
|
||||||
{ u"message_scheduled"_q , Command::ScheduleMessage },
|
{ u"message_scheduled"_q , Command::ScheduleMessage },
|
||||||
|
{ u"mevia_viewer_video_fullscreen"_q , Command::MediaViewerFullscreen },
|
||||||
//
|
//
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,8 @@ enum class Command {
|
||||||
|
|
||||||
ReadChat,
|
ReadChat,
|
||||||
|
|
||||||
|
MediaViewerFullscreen,
|
||||||
|
|
||||||
SupportReloadTemplates,
|
SupportReloadTemplates,
|
||||||
SupportToggleMuted,
|
SupportToggleMuted,
|
||||||
SupportScrollToCurrent,
|
SupportScrollToCurrent,
|
||||||
|
|
|
@ -243,6 +243,16 @@ bool UiIntegration::handleUrlClick(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UiIntegration::copyPreOnClick(const QVariant &context) {
|
||||||
|
const auto my = context.value<ClickHandlerContext>();
|
||||||
|
if (const auto window = my.sessionWindow.get()) {
|
||||||
|
window->showToast(tr::lng_code_copied(tr::now));
|
||||||
|
} else if (my.show) {
|
||||||
|
my.show->showToast(tr::lng_code_copied(tr::now));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<Ui::Text::CustomEmoji> UiIntegration::createCustomEmoji(
|
std::unique_ptr<Ui::Text::CustomEmoji> UiIntegration::createCustomEmoji(
|
||||||
const QString &data,
|
const QString &data,
|
||||||
const std::any &context) {
|
const std::any &context) {
|
||||||
|
|
|
@ -53,6 +53,7 @@ public:
|
||||||
bool handleUrlClick(
|
bool handleUrlClick(
|
||||||
const QString &url,
|
const QString &url,
|
||||||
const QVariant &context) override;
|
const QVariant &context) override;
|
||||||
|
bool copyPreOnClick(const QVariant &context) override;
|
||||||
rpl::producer<> forcePopupMenuHideRequests() override;
|
rpl::producer<> forcePopupMenuHideRequests() override;
|
||||||
const Ui::Emoji::One *defaultEmojiVariant(
|
const Ui::Emoji::One *defaultEmojiVariant(
|
||||||
const Ui::Emoji::One *emoji) override;
|
const Ui::Emoji::One *emoji) override;
|
||||||
|
|
|
@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
|
||||||
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
||||||
constexpr auto AppName = "AyuGram Desktop"_cs;
|
constexpr auto AppName = "AyuGram Desktop"_cs;
|
||||||
constexpr auto AppFile = "AyuGram"_cs;
|
constexpr auto AppFile = "AyuGram"_cs;
|
||||||
constexpr auto AppVersion = 4010002;
|
constexpr auto AppVersion = 4010004;
|
||||||
constexpr auto AppVersionStr = "4.10.2";
|
constexpr auto AppVersionStr = "4.10.4";
|
||||||
constexpr auto AppBetaVersion = false;
|
constexpr auto AppBetaVersion = true;
|
||||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "countries/countries_instance.h"
|
#include "countries/countries_instance.h"
|
||||||
|
|
||||||
#include "base/qt/qt_common_adapters.h"
|
#include "base/qt/qt_common_adapters.h"
|
||||||
|
#include "base/qt/qt_string_view.h"
|
||||||
|
|
||||||
namespace Countries {
|
namespace Countries {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
43
Telegram/SourceFiles/data/data_boosts.h
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
|
||||||
|
struct BoostsOverview final {
|
||||||
|
bool isBoosted = false;
|
||||||
|
int level = 0;
|
||||||
|
int boostCount = 0;
|
||||||
|
int currentLevelBoostCount = 0;
|
||||||
|
int nextLevelBoostCount = 0;
|
||||||
|
int premiumMemberCount = 0;
|
||||||
|
float64 premiumMemberPercentage = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Boost final {
|
||||||
|
UserId userId = UserId(0);
|
||||||
|
QDateTime expirationDate;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BoostsListSlice final {
|
||||||
|
struct OffsetToken final {
|
||||||
|
QString next;
|
||||||
|
};
|
||||||
|
std::vector<Boost> list;
|
||||||
|
int total = 0;
|
||||||
|
bool allLoaded = false;
|
||||||
|
OffsetToken token;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BoostStatus final {
|
||||||
|
BoostsOverview overview;
|
||||||
|
BoostsListSlice firstSlice;
|
||||||
|
QString link;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Data
|
|
@ -997,7 +997,8 @@ void ApplyChannelUpdate(
|
||||||
| Flag::PreHistoryHidden
|
| Flag::PreHistoryHidden
|
||||||
| Flag::AntiSpam
|
| Flag::AntiSpam
|
||||||
| Flag::Location
|
| Flag::Location
|
||||||
| Flag::ParticipantsHidden;
|
| Flag::ParticipantsHidden
|
||||||
|
| Flag::CanGetStatistics;
|
||||||
channel->setFlags((channel->flags() & ~mask)
|
channel->setFlags((channel->flags() & ~mask)
|
||||||
| (update.is_can_set_username() ? Flag::CanSetUsername : Flag())
|
| (update.is_can_set_username() ? Flag::CanSetUsername : Flag())
|
||||||
| (update.is_can_view_participants()
|
| (update.is_can_view_participants()
|
||||||
|
@ -1009,7 +1010,8 @@ void ApplyChannelUpdate(
|
||||||
| (update.vlocation() ? Flag::Location : Flag())
|
| (update.vlocation() ? Flag::Location : Flag())
|
||||||
| (update.is_participants_hidden()
|
| (update.is_participants_hidden()
|
||||||
? Flag::ParticipantsHidden
|
? Flag::ParticipantsHidden
|
||||||
: Flag()));
|
: Flag())
|
||||||
|
| (update.is_can_view_stats() ? Flag::CanGetStatistics : Flag()));
|
||||||
channel->setUserpicPhoto(update.vchat_photo());
|
channel->setUserpicPhoto(update.vchat_photo());
|
||||||
if (const auto migratedFrom = update.vmigrated_from_chat_id()) {
|
if (const auto migratedFrom = update.vmigrated_from_chat_id()) {
|
||||||
channel->addFlags(Flag::Megagroup);
|
channel->addFlags(Flag::Megagroup);
|
||||||
|
|
|
@ -62,6 +62,7 @@ enum class ChannelDataFlag {
|
||||||
StoriesHidden = (1 << 26),
|
StoriesHidden = (1 << 26),
|
||||||
HasActiveStories = (1 << 27),
|
HasActiveStories = (1 << 27),
|
||||||
HasUnreadStories = (1 << 28),
|
HasUnreadStories = (1 << 28),
|
||||||
|
CanGetStatistics = (1 << 29),
|
||||||
};
|
};
|
||||||
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
|
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
|
||||||
using ChannelDataFlags = base::flags<ChannelDataFlag>;
|
using ChannelDataFlags = base::flags<ChannelDataFlag>;
|
||||||
|
|
|
@ -69,7 +69,7 @@ void ApplyPeerCloudDraft(
|
||||||
textWithTags,
|
textWithTags,
|
||||||
replyTo,
|
replyTo,
|
||||||
topicRootId,
|
topicRootId,
|
||||||
MessageCursor(QFIXED_MAX, QFIXED_MAX, QFIXED_MAX),
|
MessageCursor(Ui::kQFixedMax, Ui::kQFixedMax, Ui::kQFixedMax),
|
||||||
(draft.is_no_webpage()
|
(draft.is_no_webpage()
|
||||||
? Data::PreviewState::Cancelled
|
? Data::PreviewState::Cancelled
|
||||||
: Data::PreviewState::Allowed));
|
: Data::PreviewState::Allowed));
|
||||||
|
|
|
@ -75,7 +75,7 @@ constexpr auto kShowChatNamesCount = 8;
|
||||||
.entities = (history->chatListBadgesState().unread
|
.entities = (history->chatListBadgesState().unread
|
||||||
? EntitiesInText{
|
? EntitiesInText{
|
||||||
{ EntityType::Semibold, 0, int(name.size()), QString() },
|
{ EntityType::Semibold, 0, int(name.size()), QString() },
|
||||||
{ EntityType::PlainLink, 0, int(name.size()), QString() },
|
{ EntityType::Colorized, 0, int(name.size()), QString() },
|
||||||
}
|
}
|
||||||
: EntitiesInText{}),
|
: EntitiesInText{}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -85,7 +85,7 @@ using ItemPreviewImage = HistoryView::ItemPreviewImage;
|
||||||
const TextWithEntities &caption,
|
const TextWithEntities &caption,
|
||||||
bool hasMiniImages = false) {
|
bool hasMiniImages = false) {
|
||||||
if (caption.text.isEmpty()) {
|
if (caption.text.isEmpty()) {
|
||||||
return Ui::Text::PlainLink(attachType);
|
return Ui::Text::Colorized(attachType);
|
||||||
}
|
}
|
||||||
|
|
||||||
return hasMiniImages
|
return hasMiniImages
|
||||||
|
@ -96,7 +96,7 @@ using ItemPreviewImage = HistoryView::ItemPreviewImage;
|
||||||
tr::lng_dialogs_text_media_wrapped(
|
tr::lng_dialogs_text_media_wrapped(
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_media,
|
lt_media,
|
||||||
Ui::Text::PlainLink(attachType),
|
Ui::Text::Colorized(attachType),
|
||||||
Ui::Text::WithEntities),
|
Ui::Text::WithEntities),
|
||||||
lt_caption,
|
lt_caption,
|
||||||
caption,
|
caption,
|
||||||
|
@ -558,7 +558,7 @@ ItemPreview Media::toGroupPreview(
|
||||||
: fileCount
|
: fileCount
|
||||||
? tr::lng_in_dlg_file_count(tr::now, lt_count, fileCount)
|
? tr::lng_in_dlg_file_count(tr::now, lt_count, fileCount)
|
||||||
: tr::lng_in_dlg_album(tr::now);
|
: tr::lng_in_dlg_album(tr::now);
|
||||||
result.text = Ui::Text::PlainLink(genericText);
|
result.text = Ui::Text::Colorized(genericText);
|
||||||
}
|
}
|
||||||
if (!loadingContext.empty()) {
|
if (!loadingContext.empty()) {
|
||||||
result.loadingContext = std::move(loadingContext);
|
result.loadingContext = std::move(loadingContext);
|
||||||
|
@ -937,7 +937,7 @@ TextWithEntities MediaFile::notificationText() const {
|
||||||
const auto text = _emoji.isEmpty()
|
const auto text = _emoji.isEmpty()
|
||||||
? tr::lng_in_dlg_sticker(tr::now)
|
? tr::lng_in_dlg_sticker(tr::now)
|
||||||
: tr::lng_in_dlg_sticker_emoji(tr::now, lt_emoji, _emoji);
|
: tr::lng_in_dlg_sticker_emoji(tr::now, lt_emoji, _emoji);
|
||||||
return Ui::Text::PlainLink(text);
|
return Ui::Text::Colorized(text);
|
||||||
}
|
}
|
||||||
const auto type = [&] {
|
const auto type = [&] {
|
||||||
if (_document->isVideoMessage()) {
|
if (_document->isVideoMessage()) {
|
||||||
|
@ -1719,7 +1719,11 @@ PollData *MediaPoll::poll() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities MediaPoll::notificationText() const {
|
TextWithEntities MediaPoll::notificationText() const {
|
||||||
return Ui::Text::PlainLink(_poll->question);
|
return TextWithEntities()
|
||||||
|
.append(QChar(0xD83D))
|
||||||
|
.append(QChar(0xDCCA))
|
||||||
|
.append(QChar(' '))
|
||||||
|
.append(Ui::Text::Colorized(_poll->question));
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MediaPoll::pinnedTextSubstring() const {
|
QString MediaPoll::pinnedTextSubstring() const {
|
||||||
|
|
|
@ -80,6 +80,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "base/call_delayed.h"
|
#include "base/call_delayed.h"
|
||||||
#include "base/random.h"
|
#include "base/random.h"
|
||||||
|
#include "spellcheck/spellcheck_highlight_syntax.h"
|
||||||
#include "styles/style_boxes.h" // st::backgroundSize
|
#include "styles/style_boxes.h" // st::backgroundSize
|
||||||
|
|
||||||
// AyuGram includes
|
// AyuGram includes
|
||||||
|
@ -306,6 +307,11 @@ Session::Session(not_null<Main::Session*> session)
|
||||||
}
|
}
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
|
|
||||||
|
Spellchecker::HighlightReady(
|
||||||
|
) | rpl::start_with_next([=](uint64 processId) {
|
||||||
|
highlightProcessDone(processId);
|
||||||
|
}, _lifetime);
|
||||||
|
|
||||||
subscribeForTopicRepliesLists();
|
subscribeForTopicRepliesLists();
|
||||||
|
|
||||||
crl::on_main(_session, [=] {
|
crl::on_main(_session, [=] {
|
||||||
|
@ -1771,6 +1777,27 @@ void Session::requestItemTextRefresh(not_null<HistoryItem*> item) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Session::registerHighlightProcess(
|
||||||
|
uint64 processId,
|
||||||
|
not_null<HistoryItem*> item) {
|
||||||
|
Expects(item->inHighlightProcess());
|
||||||
|
|
||||||
|
const auto [i, ok] = _highlightings.emplace(processId, item);
|
||||||
|
|
||||||
|
Ensures(ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Session::highlightProcessDone(uint64 processId) {
|
||||||
|
if (const auto done = _highlightings.take(processId)) {
|
||||||
|
for (const auto &[id, item] : _highlightings) {
|
||||||
|
if (item == *done) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(*done)->highlightProcessDone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Session::requestUnreadReactionsAnimation(not_null<HistoryItem*> item) {
|
void Session::requestUnreadReactionsAnimation(not_null<HistoryItem*> item) {
|
||||||
enumerateItemViews(item, [&](not_null<ViewElement*> view) {
|
enumerateItemViews(item, [&](not_null<ViewElement*> view) {
|
||||||
view->animateUnreadReactions();
|
view->animateUnreadReactions();
|
||||||
|
@ -2496,6 +2523,13 @@ void Session::unregisterMessage(not_null<HistoryItem*> item) {
|
||||||
Data::MessageUpdate::Flag::Destroyed);
|
Data::MessageUpdate::Flag::Destroyed);
|
||||||
groups().unregisterMessage(item);
|
groups().unregisterMessage(item);
|
||||||
removeDependencyMessage(item);
|
removeDependencyMessage(item);
|
||||||
|
for (auto i = begin(_highlightings); i != end(_highlightings);) {
|
||||||
|
if (i->second == item) {
|
||||||
|
i = _highlightings.erase(i);
|
||||||
|
} else {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
messagesListForInsert(peerId)->erase(itemId);
|
messagesListForInsert(peerId)->erase(itemId);
|
||||||
|
|
||||||
if (!peerIsChannel(peerId) && IsServerMsgId(itemId)) {
|
if (!peerIsChannel(peerId) && IsServerMsgId(itemId)) {
|
||||||
|
|
|
@ -299,6 +299,10 @@ public:
|
||||||
void notifyPinnedDialogsOrderUpdated();
|
void notifyPinnedDialogsOrderUpdated();
|
||||||
[[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const;
|
[[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const;
|
||||||
|
|
||||||
|
void registerHighlightProcess(
|
||||||
|
uint64 processId,
|
||||||
|
not_null<HistoryItem*> item);
|
||||||
|
|
||||||
void registerHeavyViewPart(not_null<ViewElement*> view);
|
void registerHeavyViewPart(not_null<ViewElement*> view);
|
||||||
void unregisterHeavyViewPart(not_null<ViewElement*> view);
|
void unregisterHeavyViewPart(not_null<ViewElement*> view);
|
||||||
void unloadHeavyViewParts(
|
void unloadHeavyViewParts(
|
||||||
|
@ -845,6 +849,7 @@ private:
|
||||||
TimeId date);
|
TimeId date);
|
||||||
|
|
||||||
void setWallpapers(const QVector<MTPWallPaper> &data, uint64 hash);
|
void setWallpapers(const QVector<MTPWallPaper> &data, uint64 hash);
|
||||||
|
void highlightProcessDone(uint64 processId);
|
||||||
|
|
||||||
void checkPollsClosings();
|
void checkPollsClosings();
|
||||||
|
|
||||||
|
@ -955,6 +960,7 @@ private:
|
||||||
std::unordered_map<
|
std::unordered_map<
|
||||||
FullStoryId,
|
FullStoryId,
|
||||||
base::flat_set<not_null<HistoryItem*>>> _storyItems;
|
base::flat_set<not_null<HistoryItem*>>> _storyItems;
|
||||||
|
base::flat_map<uint64, not_null<HistoryItem*>> _highlightings;
|
||||||
|
|
||||||
base::flat_set<not_null<WebPageData*>> _webpagesUpdated;
|
base::flat_set<not_null<WebPageData*>> _webpagesUpdated;
|
||||||
base::flat_set<not_null<GameData*>> _gamesUpdated;
|
base::flat_set<not_null<GameData*>> _gamesUpdated;
|
||||||
|
|
133
Telegram/SourceFiles/data/data_statistics.h
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "data/data_statistics_chart.h"
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
|
||||||
|
struct StatisticsMessageInteractionInfo final {
|
||||||
|
MsgId messageId;
|
||||||
|
int viewsCount = 0;
|
||||||
|
int forwardsCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StatisticsMessageSenderInfo final {
|
||||||
|
UserId userId = UserId(0);
|
||||||
|
int sentMessageCount = 0;
|
||||||
|
int averageCharacterCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StatisticsAdministratorActionsInfo final {
|
||||||
|
UserId userId = UserId(0);
|
||||||
|
int deletedMessageCount = 0;
|
||||||
|
int bannedUserCount = 0;
|
||||||
|
int restrictedUserCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StatisticsInviterInfo final {
|
||||||
|
UserId userId = UserId(0);
|
||||||
|
int addedMemberCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StatisticalValue final {
|
||||||
|
float64 value = 0.;
|
||||||
|
float64 previousValue = 0.;
|
||||||
|
float64 growthRatePercentage = 0.;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ChannelStatistics final {
|
||||||
|
[[nodiscard]] bool empty() const {
|
||||||
|
return !startDate || !endDate;
|
||||||
|
}
|
||||||
|
[[nodiscard]] explicit operator bool() const {
|
||||||
|
return !empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
int startDate = 0;
|
||||||
|
int endDate = 0;
|
||||||
|
|
||||||
|
StatisticalValue memberCount;
|
||||||
|
StatisticalValue meanViewCount;
|
||||||
|
StatisticalValue meanShareCount;
|
||||||
|
|
||||||
|
float64 enabledNotificationsPercentage = 0.;
|
||||||
|
|
||||||
|
StatisticalGraph memberCountGraph;
|
||||||
|
StatisticalGraph joinGraph;
|
||||||
|
StatisticalGraph muteGraph;
|
||||||
|
StatisticalGraph viewCountByHourGraph;
|
||||||
|
StatisticalGraph viewCountBySourceGraph;
|
||||||
|
StatisticalGraph joinBySourceGraph;
|
||||||
|
StatisticalGraph languageGraph;
|
||||||
|
StatisticalGraph messageInteractionGraph;
|
||||||
|
StatisticalGraph instantViewInteractionGraph;
|
||||||
|
|
||||||
|
std::vector<StatisticsMessageInteractionInfo> recentMessageInteractions;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SupergroupStatistics final {
|
||||||
|
[[nodiscard]] bool empty() const {
|
||||||
|
return !startDate || !endDate;
|
||||||
|
}
|
||||||
|
[[nodiscard]] explicit operator bool() const {
|
||||||
|
return !empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
int startDate = 0;
|
||||||
|
int endDate = 0;
|
||||||
|
|
||||||
|
StatisticalValue memberCount;
|
||||||
|
StatisticalValue messageCount;
|
||||||
|
StatisticalValue viewerCount;
|
||||||
|
StatisticalValue senderCount;
|
||||||
|
|
||||||
|
StatisticalGraph memberCountGraph;
|
||||||
|
StatisticalGraph joinGraph;
|
||||||
|
StatisticalGraph joinBySourceGraph;
|
||||||
|
StatisticalGraph languageGraph;
|
||||||
|
StatisticalGraph messageContentGraph;
|
||||||
|
StatisticalGraph actionGraph;
|
||||||
|
StatisticalGraph dayGraph;
|
||||||
|
StatisticalGraph weekGraph;
|
||||||
|
|
||||||
|
std::vector<StatisticsMessageSenderInfo> topSenders;
|
||||||
|
std::vector<StatisticsAdministratorActionsInfo> topAdministrators;
|
||||||
|
std::vector<StatisticsInviterInfo> topInviters;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MessageStatistics final {
|
||||||
|
explicit operator bool() const {
|
||||||
|
return !messageInteractionGraph.chart.empty() || views;
|
||||||
|
}
|
||||||
|
Data::StatisticalGraph messageInteractionGraph;
|
||||||
|
int publicForwards = 0;
|
||||||
|
int privateForwards = 0;
|
||||||
|
int views = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AnyStatistics final {
|
||||||
|
Data::ChannelStatistics channel;
|
||||||
|
Data::SupergroupStatistics supergroup;
|
||||||
|
Data::MessageStatistics message;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PublicForwardsSlice final {
|
||||||
|
struct OffsetToken final {
|
||||||
|
int rate = 0;
|
||||||
|
FullMsgId fullId;
|
||||||
|
};
|
||||||
|
QVector<FullMsgId> list;
|
||||||
|
int total = 0;
|
||||||
|
bool allLoaded = false;
|
||||||
|
OffsetToken token;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Data
|
156
Telegram/SourceFiles/data/data_statistics_chart.cpp
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "data/data_statistics_chart.h"
|
||||||
|
|
||||||
|
#include <QtCore/QDateTime>
|
||||||
|
#include <QtCore/QLocale>
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
|
||||||
|
void StatisticalChart::measure() {
|
||||||
|
if (x.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto n = x.size();
|
||||||
|
const auto start = x.front();
|
||||||
|
const auto end = x.back();
|
||||||
|
|
||||||
|
xPercentage.clear();
|
||||||
|
xPercentage.resize(n);
|
||||||
|
if (n == 1) {
|
||||||
|
xPercentage[0] = 1;
|
||||||
|
} else {
|
||||||
|
for (auto i = 0; i < n; i++) {
|
||||||
|
xPercentage[i] = (x[i] - start) / float64(end - start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &line : lines) {
|
||||||
|
if (line.maxValue > maxValue) {
|
||||||
|
maxValue = line.maxValue;
|
||||||
|
}
|
||||||
|
if (line.minValue < minValue) {
|
||||||
|
minValue = line.minValue;
|
||||||
|
}
|
||||||
|
line.segmentTree = Statistic::SegmentTree(line.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
daysLookup.clear();
|
||||||
|
const auto dateCount = int((end - start) / timeStep) + 10;
|
||||||
|
daysLookup.reserve(dateCount);
|
||||||
|
constexpr auto kOneDay = 3600 * 24 * 1000;
|
||||||
|
const auto formatter = u"d MMM"_q;
|
||||||
|
for (auto i = 0; i < dateCount; i++) {
|
||||||
|
const auto r = (start + (i * timeStep)) / 1000;
|
||||||
|
const auto dateTime = QDateTime::fromSecsSinceEpoch(r);
|
||||||
|
if (timeStep == 1) {
|
||||||
|
daysLookup.push_back(
|
||||||
|
QString(((i < 10) ? u"0%1:00"_q : u"%1:00"_q).arg(i)));
|
||||||
|
} else if (timeStep < kOneDay) {
|
||||||
|
daysLookup.push_back(u"%1:%2"_q
|
||||||
|
.arg(dateTime.time().hour(), 2, 10, QChar('0'))
|
||||||
|
.arg(dateTime.time().minute(), 2, 10, QChar('0')));
|
||||||
|
} else {
|
||||||
|
const auto date = dateTime.date();
|
||||||
|
daysLookup.push_back(QLocale().toString(date, formatter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oneDayPercentage = timeStep / float64(end - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString StatisticalChart::getDayString(int i) const {
|
||||||
|
return daysLookup[int((x[i] - x[0]) / timeStep)];
|
||||||
|
}
|
||||||
|
|
||||||
|
int StatisticalChart::findStartIndex(float64 v) const {
|
||||||
|
if (!v) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const auto n = int(xPercentage.size());
|
||||||
|
|
||||||
|
if (n < 2) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
auto left = 0;
|
||||||
|
auto right = n - 1;
|
||||||
|
|
||||||
|
while (left <= right) {
|
||||||
|
const auto middle = (right + left) >> 1;
|
||||||
|
if (v < xPercentage[middle]
|
||||||
|
&& (!middle || (v > xPercentage[middle - 1]))) {
|
||||||
|
return middle;
|
||||||
|
}
|
||||||
|
if (v == xPercentage[middle]) {
|
||||||
|
return middle;
|
||||||
|
}
|
||||||
|
if (v < xPercentage[middle]) {
|
||||||
|
right = middle - 1;
|
||||||
|
} else if (v > xPercentage[middle]) {
|
||||||
|
left = middle + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StatisticalChart::findEndIndex(int left, float64 v) const {
|
||||||
|
const auto n = int(xPercentage.size());
|
||||||
|
if (v == 1.) {
|
||||||
|
return n - 1;
|
||||||
|
}
|
||||||
|
auto right = n - 1;
|
||||||
|
|
||||||
|
while (left <= right) {
|
||||||
|
const auto middle = (right + left) >> 1;
|
||||||
|
if (v > xPercentage[middle]
|
||||||
|
&& ((middle == n - 1) || (v < xPercentage[middle + 1]))) {
|
||||||
|
return middle;
|
||||||
|
}
|
||||||
|
if (v == xPercentage[middle]) {
|
||||||
|
return middle;
|
||||||
|
}
|
||||||
|
if (v < xPercentage[middle]) {
|
||||||
|
right = middle - 1;
|
||||||
|
} else if (v > xPercentage[middle]) {
|
||||||
|
left = middle + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return right;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int StatisticalChart::findIndex(int left, int right, float64 v) const {
|
||||||
|
const auto n = int(xPercentage.size());
|
||||||
|
|
||||||
|
if (v <= xPercentage[left]) {
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
if (v >= xPercentage[right]) {
|
||||||
|
return right;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (left <= right) {
|
||||||
|
const auto middle = (right + left) >> 1;
|
||||||
|
if (v > xPercentage[middle]
|
||||||
|
&& ((middle == n - 1) || (v < xPercentage[middle + 1]))) {
|
||||||
|
return middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v == xPercentage[middle]) {
|
||||||
|
return middle;
|
||||||
|
}
|
||||||
|
if (v < xPercentage[middle]) {
|
||||||
|
right = middle - 1;
|
||||||
|
} else if (v > xPercentage[middle]) {
|
||||||
|
left = middle + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return right;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Data
|
77
Telegram/SourceFiles/data/data_statistics_chart.h
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "statistics/segment_tree.h"
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
|
||||||
|
struct StatisticalChart {
|
||||||
|
StatisticalChart() = default;
|
||||||
|
|
||||||
|
[[nodiscard]] bool empty() const {
|
||||||
|
return lines.empty();
|
||||||
|
}
|
||||||
|
[[nodiscard]] explicit operator bool() const {
|
||||||
|
return !empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void measure();
|
||||||
|
|
||||||
|
[[nodiscard]] QString getDayString(int i) const;
|
||||||
|
|
||||||
|
[[nodiscard]] int findStartIndex(float64 v) const;
|
||||||
|
[[nodiscard]] int findEndIndex(int left, float64 v) const;
|
||||||
|
[[nodiscard]] int findIndex(int left, int right, float64 v) const;
|
||||||
|
|
||||||
|
struct Line final {
|
||||||
|
std::vector<int> y;
|
||||||
|
|
||||||
|
Statistic::SegmentTree segmentTree;
|
||||||
|
int id = 0;
|
||||||
|
QString idString;
|
||||||
|
QString name;
|
||||||
|
int maxValue = 0;
|
||||||
|
int minValue = std::numeric_limits<int>::max();
|
||||||
|
QString colorKey;
|
||||||
|
QColor color;
|
||||||
|
QColor colorDark;
|
||||||
|
bool isHiddenOnStart = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<float64> x;
|
||||||
|
std::vector<float64> xPercentage;
|
||||||
|
std::vector<QString> daysLookup;
|
||||||
|
|
||||||
|
std::vector<Line> lines;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
float64 min = 0.;
|
||||||
|
float64 max = 0.;
|
||||||
|
} defaultZoomXIndex;
|
||||||
|
|
||||||
|
int maxValue = 0;
|
||||||
|
int minValue = std::numeric_limits<int>::max();
|
||||||
|
|
||||||
|
float64 oneDayPercentage = 0.;
|
||||||
|
|
||||||
|
float64 timeStep = 0.;
|
||||||
|
|
||||||
|
bool isFooterHidden = false;
|
||||||
|
bool hasPercentages = false;
|
||||||
|
bool weekFormat = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StatisticalGraph final {
|
||||||
|
StatisticalChart chart;
|
||||||
|
QString zoomToken;
|
||||||
|
QString error;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Data
|
|
@ -308,14 +308,14 @@ Image *Story::replyPreview() const {
|
||||||
TextWithEntities Story::inReplyText() const {
|
TextWithEntities Story::inReplyText() const {
|
||||||
const auto type = tr::lng_in_dlg_story(tr::now);
|
const auto type = tr::lng_in_dlg_story(tr::now);
|
||||||
return _caption.text.isEmpty()
|
return _caption.text.isEmpty()
|
||||||
? Ui::Text::PlainLink(type)
|
? Ui::Text::Colorized(type)
|
||||||
: tr::lng_dialogs_text_media(
|
: tr::lng_dialogs_text_media(
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_media_part,
|
lt_media_part,
|
||||||
tr::lng_dialogs_text_media_wrapped(
|
tr::lng_dialogs_text_media_wrapped(
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_media,
|
lt_media,
|
||||||
Ui::Text::PlainLink(type),
|
Ui::Text::Colorized(type),
|
||||||
Ui::Text::WithEntities),
|
Ui::Text::WithEntities),
|
||||||
lt_caption,
|
lt_caption,
|
||||||
_caption,
|
_caption,
|
||||||
|
|
|
@ -102,7 +102,7 @@ void MessageCursor::fillFrom(not_null<const Ui::InputField*> field) {
|
||||||
position = cursor.position();
|
position = cursor.position();
|
||||||
anchor = cursor.anchor();
|
anchor = cursor.anchor();
|
||||||
const auto top = field->scrollTop().current();
|
const auto top = field->scrollTop().current();
|
||||||
scroll = (top != field->scrollTopMax()) ? top : QFIXED_MAX;
|
scroll = (top != field->scrollTopMax()) ? top : Ui::kQFixedMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageCursor::applyTo(not_null<Ui::InputField*> field) {
|
void MessageCursor::applyTo(not_null<Ui::InputField*> field) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/text/text.h" // For QFIXED_MAX
|
#include "ui/text/text.h" // Ui::kQFixedMax.
|
||||||
#include "data/data_peer_id.h"
|
#include "data/data_peer_id.h"
|
||||||
#include "data/data_msg_id.h"
|
#include "data/data_msg_id.h"
|
||||||
#include "base/qt/qt_compare.h"
|
#include "base/qt/qt_compare.h"
|
||||||
|
@ -196,7 +196,7 @@ struct MessageCursor {
|
||||||
|
|
||||||
int position = 0;
|
int position = 0;
|
||||||
int anchor = 0;
|
int anchor = 0;
|
||||||
int scroll = QFIXED_MAX;
|
int scroll = Ui::kQFixedMax;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -303,6 +303,8 @@ enum class MessageFlag : uint64 {
|
||||||
FakeBotAbout = (1ULL << 36),
|
FakeBotAbout = (1ULL << 36),
|
||||||
|
|
||||||
StoryItem = (1ULL << 37),
|
StoryItem = (1ULL << 37),
|
||||||
|
|
||||||
|
InHighlightProcess = (1ULL << 38),
|
||||||
};
|
};
|
||||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||||
using MessageFlags = base::flags<MessageFlag>;
|
using MessageFlags = base::flags<MessageFlag>;
|
||||||
|
|
|
@ -65,12 +65,8 @@ dialogsRipple: RippleAnimation(defaultRippleAnimation) {
|
||||||
color: dialogsRippleBg;
|
color: dialogsRippleBg;
|
||||||
}
|
}
|
||||||
|
|
||||||
dialogsTextFont: font(fsize);
|
dialogsTextFont: normalFont;
|
||||||
dialogsTextStyle: TextStyle(defaultTextStyle) {
|
dialogsTextStyle: defaultTextStyle;
|
||||||
font: dialogsTextFont;
|
|
||||||
linkFont: dialogsTextFont;
|
|
||||||
linkFontOver: dialogsTextFont;
|
|
||||||
}
|
|
||||||
dialogsDateFont: font(13px);
|
dialogsDateFont: font(13px);
|
||||||
dialogsDateSkip: 5px;
|
dialogsDateSkip: 5px;
|
||||||
|
|
||||||
|
@ -447,11 +443,7 @@ dialogsSearchInHeight: 52px;
|
||||||
dialogsSearchInPhotoSize: 36px;
|
dialogsSearchInPhotoSize: 36px;
|
||||||
dialogsSearchInPhotoPadding: 10px;
|
dialogsSearchInPhotoPadding: 10px;
|
||||||
dialogsSearchInSkip: 7px;
|
dialogsSearchInSkip: 7px;
|
||||||
dialogsSearchFromStyle: TextStyle(defaultTextStyle) {
|
dialogsSearchFromStyle: defaultTextStyle;
|
||||||
font: normalFont;
|
|
||||||
linkFont: semiboldFont;
|
|
||||||
linkFontOver: semiboldFont;
|
|
||||||
}
|
|
||||||
dialogsSearchFromPalette: TextPalette(defaultTextPalette) {
|
dialogsSearchFromPalette: TextPalette(defaultTextPalette) {
|
||||||
linkFg: dialogsNameFg;
|
linkFg: dialogsNameFg;
|
||||||
}
|
}
|
||||||
|
@ -507,8 +499,6 @@ downloadTitleLeft: 57px;
|
||||||
downloadTitleTop: 4px;
|
downloadTitleTop: 4px;
|
||||||
downloadInfoStyle: TextStyle(defaultTextStyle) {
|
downloadInfoStyle: TextStyle(defaultTextStyle) {
|
||||||
font: font(12px);
|
font: font(12px);
|
||||||
linkFont: font(12px);
|
|
||||||
linkFontOver: font(12px underline);
|
|
||||||
}
|
}
|
||||||
downloadInfoLeft: 57px;
|
downloadInfoLeft: 57px;
|
||||||
downloadInfoTop: 23px;
|
downloadInfoTop: 23px;
|
||||||
|
@ -541,8 +531,6 @@ chooseTopicListItem: PeerListItem(defaultPeerListItem) {
|
||||||
namePosition: point(55px, 11px);
|
namePosition: point(55px, 11px);
|
||||||
nameStyle: TextStyle(defaultTextStyle) {
|
nameStyle: TextStyle(defaultTextStyle) {
|
||||||
font: font(14px semibold);
|
font: font(14px semibold);
|
||||||
linkFont: font(14px semibold);
|
|
||||||
linkFontOver: font(14px semibold);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chooseTopicList: PeerList(defaultPeerList) {
|
chooseTopicList: PeerList(defaultPeerList) {
|
||||||
|
@ -599,8 +587,6 @@ dialogsStoriesFull: DialogsStories {
|
||||||
nameTop: 56px;
|
nameTop: 56px;
|
||||||
nameStyle: TextStyle(defaultTextStyle) {
|
nameStyle: TextStyle(defaultTextStyle) {
|
||||||
font: font(11px);
|
font: font(11px);
|
||||||
linkFont: font(11px);
|
|
||||||
linkFontOver: font(11px);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2913,7 +2913,7 @@ void InnerWidget::refreshSearchInChatLabel() {
|
||||||
const auto fromUserText = tr::lng_dlg_search_from(
|
const auto fromUserText = tr::lng_dlg_search_from(
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_user,
|
lt_user,
|
||||||
Ui::Text::Link(from),
|
Ui::Text::Semibold(from),
|
||||||
Ui::Text::WithEntities);
|
Ui::Text::WithEntities);
|
||||||
_searchFromUserText.setMarkedText(
|
_searchFromUserText.setMarkedText(
|
||||||
st::dialogsSearchFromStyle,
|
st::dialogsSearchFromStyle,
|
||||||
|
|
|
@ -540,14 +540,12 @@ void Widget::chosenRow(const ChosenRow &row) {
|
||||||
return;
|
return;
|
||||||
} else if (history) {
|
} else if (history) {
|
||||||
const auto peer = history->peer;
|
const auto peer = history->peer;
|
||||||
if (const auto user = peer->asUser()) {
|
if (row.message.fullId.msg == ShowAtUnreadMsgId) {
|
||||||
if (row.message.fullId.msg == ShowAtUnreadMsgId) {
|
if (row.userpicClick
|
||||||
if (row.userpicClick
|
&& peer->hasActiveStories()
|
||||||
&& user->hasActiveStories()
|
&& !peer->isSelf()) {
|
||||||
&& !user->isSelf()) {
|
controller()->openPeerStories(peer->id);
|
||||||
controller()->openPeerStories(user->id);
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto showAtMsgId = controller()->uniqueChatsInSearchResults()
|
const auto showAtMsgId = controller()->uniqueChatsInSearchResults()
|
||||||
|
|
|
@ -260,7 +260,7 @@ void PaintFolderEntryText(
|
||||||
.now = context.now,
|
.now = context.now,
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
||||||
.elisionLines = rect.height() / st::dialogsTextFont->height,
|
.elisionHeight = rect.height(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,7 +420,7 @@ void PaintRow(
|
||||||
.now = context.now,
|
.now = context.now,
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
||||||
.elisionLines = 1,
|
.elisionOneLine = true,
|
||||||
});
|
});
|
||||||
} else if (draft
|
} else if (draft
|
||||||
|| (supportMode
|
|| (supportMode
|
||||||
|
@ -462,13 +462,13 @@ void PaintRow(
|
||||||
auto &cache = thread->cloudDraftTextCache();
|
auto &cache = thread->cloudDraftTextCache();
|
||||||
if (cache.isEmpty()) {
|
if (cache.isEmpty()) {
|
||||||
using namespace TextUtilities;
|
using namespace TextUtilities;
|
||||||
auto draftWrapped = Text::PlainLink(
|
auto draftWrapped = Text::Colorized(
|
||||||
tr::lng_dialogs_text_from_wrapped(
|
tr::lng_dialogs_text_from_wrapped(
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_from,
|
lt_from,
|
||||||
tr::lng_from_draft(tr::now)));
|
tr::lng_from_draft(tr::now)));
|
||||||
auto draftText = supportMode
|
auto draftText = supportMode
|
||||||
? Text::PlainLink(
|
? Text::Colorized(
|
||||||
Support::ChatOccupiedString(history))
|
Support::ChatOccupiedString(history))
|
||||||
: tr::lng_dialogs_text_with_from(
|
: tr::lng_dialogs_text_with_from(
|
||||||
tr::now,
|
tr::now,
|
||||||
|
@ -514,7 +514,7 @@ void PaintRow(
|
||||||
.now = context.now,
|
.now = context.now,
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
||||||
.elisionLines = 1,
|
.elisionOneLine = true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (!item) {
|
} else if (!item) {
|
||||||
|
|
|
@ -94,7 +94,7 @@ TextWithEntities DialogsPreviewText(TextWithEntities text) {
|
||||||
EntityType::Underline,
|
EntityType::Underline,
|
||||||
EntityType::Italic,
|
EntityType::Italic,
|
||||||
EntityType::CustomEmoji,
|
EntityType::CustomEmoji,
|
||||||
EntityType::PlainLink,
|
EntityType::Colorized,
|
||||||
});
|
});
|
||||||
for (auto &entity : result.entities) {
|
for (auto &entity : result.entities) {
|
||||||
if (entity.type() == EntityType::Pre) {
|
if (entity.type() == EntityType::Pre) {
|
||||||
|
@ -102,6 +102,13 @@ TextWithEntities DialogsPreviewText(TextWithEntities text) {
|
||||||
EntityType::Code,
|
EntityType::Code,
|
||||||
entity.offset(),
|
entity.offset(),
|
||||||
entity.length());
|
entity.length());
|
||||||
|
} else if (entity.type() == EntityType::Colorized
|
||||||
|
&& !entity.data().isEmpty()) {
|
||||||
|
// Drop 'data' so that only link-color colorization takes place.
|
||||||
|
entity = EntityInText(
|
||||||
|
EntityType::Colorized,
|
||||||
|
entity.offset(),
|
||||||
|
entity.length());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -188,7 +195,7 @@ void MessageView::prepare(
|
||||||
TextUtilities::Trim(preview.text);
|
TextUtilities::Trim(preview.text);
|
||||||
auto textToCache = DialogsPreviewText(std::move(preview.text));
|
auto textToCache = DialogsPreviewText(std::move(preview.text));
|
||||||
_hasPlainLinkAtBegin = !textToCache.entities.empty()
|
_hasPlainLinkAtBegin = !textToCache.entities.empty()
|
||||||
&& (textToCache.entities.front().type() == EntityType::PlainLink);
|
&& (textToCache.entities.front().type() == EntityType::Colorized);
|
||||||
_textCache.setMarkedText(
|
_textCache.setMarkedText(
|
||||||
st::dialogsTextStyle,
|
st::dialogsTextStyle,
|
||||||
std::move(textToCache),
|
std::move(textToCache),
|
||||||
|
@ -305,7 +312,6 @@ void MessageView::paint(
|
||||||
rect.setWidth(rect.width() - st::forumDialogJumpArrowSkip);
|
rect.setWidth(rect.width() - st::forumDialogJumpArrowSkip);
|
||||||
finalRight -= st::forumDialogJumpArrowSkip;
|
finalRight -= st::forumDialogJumpArrowSkip;
|
||||||
}
|
}
|
||||||
const auto lines = rect.height() / st::dialogsTextFont->height;
|
|
||||||
const auto pausedSpoiler = context.paused
|
const auto pausedSpoiler = context.paused
|
||||||
|| On(PowerSaving::kChatSpoiler);
|
|| On(PowerSaving::kChatSpoiler);
|
||||||
if (!_senderCache.isEmpty()) {
|
if (!_senderCache.isEmpty()) {
|
||||||
|
@ -313,7 +319,7 @@ void MessageView::paint(
|
||||||
.position = rect.topLeft(),
|
.position = rect.topLeft(),
|
||||||
.availableWidth = rect.width(),
|
.availableWidth = rect.width(),
|
||||||
.palette = palette,
|
.palette = palette,
|
||||||
.elisionLines = lines,
|
.elisionHeight = rect.height(),
|
||||||
});
|
});
|
||||||
rect.setLeft(rect.x() + _senderCache.maxWidth());
|
rect.setLeft(rect.x() + _senderCache.maxWidth());
|
||||||
if (!_imagesCache.empty() && !_leftIcon) {
|
if (!_imagesCache.empty() && !_leftIcon) {
|
||||||
|
@ -381,7 +387,7 @@ void MessageView::paint(
|
||||||
.now = context.now,
|
.now = context.now,
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
.pausedSpoiler = pausedSpoiler,
|
.pausedSpoiler = pausedSpoiler,
|
||||||
.elisionLines = lines,
|
.elisionHeight = rect.height(),
|
||||||
});
|
});
|
||||||
rect.setLeft(rect.x() + _textCache.maxWidth());
|
rect.setLeft(rect.x() + _textCache.maxWidth());
|
||||||
}
|
}
|
||||||
|
@ -457,7 +463,7 @@ HistoryView::ItemPreview PreviewWithSender(
|
||||||
auto fullWithOffset = tr::lng_dialogs_text_with_from(
|
auto fullWithOffset = tr::lng_dialogs_text_with_from(
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_from_part,
|
lt_from_part,
|
||||||
Ui::Text::PlainLink(std::move(wrappedWithOffset.text)),
|
Ui::Text::Colorized(std::move(wrappedWithOffset.text)),
|
||||||
lt_message,
|
lt_message,
|
||||||
std::move(preview.text),
|
std::move(preview.text),
|
||||||
TextWithTagOffset<lt_from_part>::FromString);
|
TextWithTagOffset<lt_from_part>::FromString);
|
||||||
|
|
|
@ -75,7 +75,7 @@ void TopicsView::prepare(MsgId frontRootId, Fn<void()> customEmojiRepaint) {
|
||||||
title.title.setMarkedText(
|
title.title.setMarkedText(
|
||||||
st::dialogsTextStyle,
|
st::dialogsTextStyle,
|
||||||
(unread
|
(unread
|
||||||
? Ui::Text::PlainLink(
|
? Ui::Text::Colorized(
|
||||||
Ui::Text::Wrapped(
|
Ui::Text::Wrapped(
|
||||||
std::move(topicTitle),
|
std::move(topicTitle),
|
||||||
EntityType::Bold))
|
EntityType::Bold))
|
||||||
|
@ -141,7 +141,7 @@ void TopicsView::paint(
|
||||||
.now = context.now,
|
.now = context.now,
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
||||||
.elisionLines = 1,
|
.elisionOneLine = true,
|
||||||
});
|
});
|
||||||
const auto skip = skipBig
|
const auto skip = skipBig
|
||||||
? context.st->topicsSkipBig
|
? context.st->topicsSkipBig
|
||||||
|
|
|
@ -38,8 +38,6 @@ photoEditorTextButtonPadding: margins(22px, 0px, 22px, 0px);
|
||||||
|
|
||||||
photoEditorButtonStyle: TextStyle(semiboldTextStyle) {
|
photoEditorButtonStyle: TextStyle(semiboldTextStyle) {
|
||||||
font: font(14px semibold);
|
font: font(14px semibold);
|
||||||
linkFont: font(14px semibold);
|
|
||||||
linkFontOver: font(14px semibold underline);
|
|
||||||
}
|
}
|
||||||
photoEditorButtonTextTop: 15px;
|
photoEditorButtonTextTop: 15px;
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,6 @@ exportSubSettingPadding: margins(56px, 4px, 22px, 12px);
|
||||||
exportHeaderLabel: FlatLabel(boxTitle) {
|
exportHeaderLabel: FlatLabel(boxTitle) {
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(15px semibold);
|
font: font(15px semibold);
|
||||||
linkFont: font(15px semibold);
|
|
||||||
linkFontOver: font(15px semibold underline);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exportHeaderPadding: margins(22px, 20px, 22px, 9px);
|
exportHeaderPadding: margins(22px, 20px, 22px, 9px);
|
||||||
|
@ -57,8 +55,6 @@ exportProgressLabel: FlatLabel(boxLabel) {
|
||||||
maxHeight: 20px;
|
maxHeight: 20px;
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(14px semibold);
|
font: font(14px semibold);
|
||||||
linkFont: font(14px semibold);
|
|
||||||
linkFontOver: font(14px semibold);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exportProgressInfoLabel: FlatLabel(boxLabel) {
|
exportProgressInfoLabel: FlatLabel(boxLabel) {
|
||||||
|
|
|
@ -45,7 +45,7 @@ void TopBar::updateData(Content &&content) {
|
||||||
.append(" \xe2\x80\x93 ")
|
.append(" \xe2\x80\x93 ")
|
||||||
.append(row.label)
|
.append(row.label)
|
||||||
.append(' ')
|
.append(' ')
|
||||||
.append(Ui::Text::PlainLink(row.info)));
|
.append(Ui::Text::Colorized(row.info)));
|
||||||
_progress->setValue(row.progress);
|
_progress->setValue(row.progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_web_page.h"
|
#include "data/data_web_page.h"
|
||||||
#include "chat_helpers/stickers_gift_box_pack.h"
|
#include "chat_helpers/stickers_gift_box_pack.h"
|
||||||
#include "payments/payments_checkout_process.h" // CheckoutProcess::Start.
|
#include "payments/payments_checkout_process.h" // CheckoutProcess::Start.
|
||||||
|
#include "spellcheck/spellcheck_highlight_syntax.h"
|
||||||
#include "styles/style_dialogs.h"
|
#include "styles/style_dialogs.h"
|
||||||
|
|
||||||
// AyuGram includes
|
// AyuGram includes
|
||||||
|
@ -2922,15 +2923,32 @@ void HistoryItem::setText(const TextWithEntities &textWithEntities) {
|
||||||
: std::move(textWithEntities));
|
: std::move(textWithEntities));
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryItem::setTextValue(TextWithEntities text) {
|
void HistoryItem::setTextValue(TextWithEntities text, bool force) {
|
||||||
|
if (const auto processId = Spellchecker::TryHighlightSyntax(text)) {
|
||||||
|
_flags |= MessageFlag::InHighlightProcess;
|
||||||
|
history()->owner().registerHighlightProcess(processId, this);
|
||||||
|
}
|
||||||
const auto had = !_text.empty();
|
const auto had = !_text.empty();
|
||||||
_text = std::move(text);
|
_text = std::move(text);
|
||||||
RemoveComponents(HistoryMessageTranslation::Bit());
|
RemoveComponents(HistoryMessageTranslation::Bit());
|
||||||
if (had) {
|
if (had || force) {
|
||||||
history()->owner().requestItemTextRefresh(this);
|
history()->owner().requestItemTextRefresh(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HistoryItem::inHighlightProcess() const {
|
||||||
|
return _flags & MessageFlag::InHighlightProcess;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryItem::highlightProcessDone() {
|
||||||
|
Expects(inHighlightProcess());
|
||||||
|
|
||||||
|
_flags &= ~MessageFlag::InHighlightProcess;
|
||||||
|
if (!_text.empty()) {
|
||||||
|
setTextValue(base::take(_text), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool HistoryItem::showNotification() const {
|
bool HistoryItem::showNotification() const {
|
||||||
const auto channel = _history->peer->asChannel();
|
const auto channel = _history->peer->asChannel();
|
||||||
if (channel && !channel->amIn()) {
|
if (channel && !channel->amIn()) {
|
||||||
|
@ -2983,9 +3001,7 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
|
||||||
// Because larger version is shown exactly to the left of the small.
|
// Because larger version is shown exactly to the left of the small.
|
||||||
//auto media = _media ? _media->toPreview(options) : ItemPreview();
|
//auto media = _media ? _media->toPreview(options) : ItemPreview();
|
||||||
return {
|
return {
|
||||||
.text = Ui::Text::Wrapped(
|
.text = Ui::Text::Colorized(notificationText()),
|
||||||
notificationText(),
|
|
||||||
EntityType::PlainLink),
|
|
||||||
//.images = std::move(media.images),
|
//.images = std::move(media.images),
|
||||||
//.loadingContext = std::move(media.loadingContext),
|
//.loadingContext = std::move(media.loadingContext),
|
||||||
};
|
};
|
||||||
|
@ -3062,7 +3078,7 @@ TextWithEntities HistoryItem::inReplyText() const {
|
||||||
result = Ui::Text::Mid(result, name.size());
|
result = Ui::Text::Mid(result, name.size());
|
||||||
TextUtilities::Trim(result);
|
TextUtilities::Trim(result);
|
||||||
}
|
}
|
||||||
return Ui::Text::Wrapped(result, EntityType::PlainLink);
|
return Ui::Text::Colorized(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<ClickHandlerPtr> &HistoryItem::customTextLinks() const {
|
const std::vector<ClickHandlerPtr> &HistoryItem::customTextLinks() const {
|
||||||
|
|
|
@ -322,6 +322,8 @@ public:
|
||||||
[[nodiscard]] bool repliesAreComments() const;
|
[[nodiscard]] bool repliesAreComments() const;
|
||||||
[[nodiscard]] bool externalReply() const;
|
[[nodiscard]] bool externalReply() const;
|
||||||
[[nodiscard]] bool hasExtendedMediaPreview() const;
|
[[nodiscard]] bool hasExtendedMediaPreview() const;
|
||||||
|
[[nodiscard]] bool inHighlightProcess() const;
|
||||||
|
void highlightProcessDone();
|
||||||
|
|
||||||
void setCommentsInboxReadTill(MsgId readTillId);
|
void setCommentsInboxReadTill(MsgId readTillId);
|
||||||
void setCommentsMaxId(MsgId maxId);
|
void setCommentsMaxId(MsgId maxId);
|
||||||
|
@ -538,7 +540,7 @@ private:
|
||||||
[[nodiscard]] bool generateLocalEntitiesByReply() const;
|
[[nodiscard]] bool generateLocalEntitiesByReply() const;
|
||||||
[[nodiscard]] TextWithEntities withLocalEntities(
|
[[nodiscard]] TextWithEntities withLocalEntities(
|
||||||
const TextWithEntities &textWithEntities) const;
|
const TextWithEntities &textWithEntities) const;
|
||||||
void setTextValue(TextWithEntities text);
|
void setTextValue(TextWithEntities text, bool force = false);
|
||||||
[[nodiscard]] bool isTooOldForEdit(TimeId now) const;
|
[[nodiscard]] bool isTooOldForEdit(TimeId now) const;
|
||||||
[[nodiscard]] bool isLegacyMessage() const {
|
[[nodiscard]] bool isLegacyMessage() const {
|
||||||
return _flags & MessageFlag::Legacy;
|
return _flags & MessageFlag::Legacy;
|
||||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/qt/qt_key_modifiers.h"
|
#include "base/qt/qt_key_modifiers.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
|
#include "ui/effects/spoiler_mess.h"
|
||||||
#include "ui/image/image.h"
|
#include "ui/image/image.h"
|
||||||
#include "ui/toast/toast.h"
|
#include "ui/toast/toast.h"
|
||||||
#include "ui/text/text_options.h"
|
#include "ui/text/text_options.h"
|
||||||
|
@ -260,6 +261,17 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HistoryMessageReply::HistoryMessageReply() = default;
|
||||||
|
|
||||||
|
HistoryMessageReply &HistoryMessageReply::operator=(
|
||||||
|
HistoryMessageReply &&other) = default;
|
||||||
|
|
||||||
|
HistoryMessageReply::~HistoryMessageReply() {
|
||||||
|
// clearData() should be called by holder.
|
||||||
|
Expects(replyToMsg.empty());
|
||||||
|
Expects(replyToVia == nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
bool HistoryMessageReply::updateData(
|
bool HistoryMessageReply::updateData(
|
||||||
not_null<HistoryItem*> holder,
|
not_null<HistoryItem*> holder,
|
||||||
bool force) {
|
bool force) {
|
||||||
|
@ -311,7 +323,7 @@ bool HistoryMessageReply::updateData(
|
||||||
.customEmojiRepaint = repaint,
|
.customEmojiRepaint = repaint,
|
||||||
};
|
};
|
||||||
replyToText.setMarkedText(
|
replyToText.setMarkedText(
|
||||||
st::messageTextStyle,
|
st::defaultTextStyle,
|
||||||
(replyToMsg
|
(replyToMsg
|
||||||
? replyToMsg->inReplyText()
|
? replyToMsg->inReplyText()
|
||||||
: replyToStory->inReplyText()),
|
: replyToStory->inReplyText()),
|
||||||
|
@ -333,7 +345,8 @@ bool HistoryMessageReply::updateData(
|
||||||
if (replyToMsg) {
|
if (replyToMsg) {
|
||||||
const auto peer = replyToMsg->history()->peer;
|
const auto peer = replyToMsg->history()->peer;
|
||||||
replyToColorKey = (!holder->out()
|
replyToColorKey = (!holder->out()
|
||||||
&& (peer->isMegagroup() || peer->isChat()))
|
&& (peer->isMegagroup() || peer->isChat())
|
||||||
|
&& replyToMsg->from()->isUser())
|
||||||
? replyToMsg->from()->id
|
? replyToMsg->from()->id
|
||||||
: PeerId(0);
|
: PeerId(0);
|
||||||
} else {
|
} else {
|
||||||
|
@ -630,7 +643,7 @@ void HistoryMessageReply::paint(
|
||||||
.pausedEmoji = (context.paused
|
.pausedEmoji = (context.paused
|
||||||
|| On(PowerSaving::kEmojiChat)),
|
|| On(PowerSaving::kEmojiChat)),
|
||||||
.pausedSpoiler = pausedSpoiler,
|
.pausedSpoiler = pausedSpoiler,
|
||||||
.elisionLines = 1,
|
.elisionOneLine = true,
|
||||||
});
|
});
|
||||||
p.setTextPalette(stm->textPalette);
|
p.setTextPalette(stm->textPalette);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ namespace Ui {
|
||||||
struct ChatPaintContext;
|
struct ChatPaintContext;
|
||||||
class ChatStyle;
|
class ChatStyle;
|
||||||
struct PeerUserpicView;
|
struct PeerUserpicView;
|
||||||
|
class SpoilerAnimation;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
@ -227,17 +228,13 @@ private:
|
||||||
|
|
||||||
struct HistoryMessageReply
|
struct HistoryMessageReply
|
||||||
: public RuntimeComponent<HistoryMessageReply, HistoryItem> {
|
: public RuntimeComponent<HistoryMessageReply, HistoryItem> {
|
||||||
HistoryMessageReply() = default;
|
HistoryMessageReply();
|
||||||
HistoryMessageReply(const HistoryMessageReply &other) = delete;
|
HistoryMessageReply(const HistoryMessageReply &other) = delete;
|
||||||
HistoryMessageReply(HistoryMessageReply &&other) = delete;
|
HistoryMessageReply(HistoryMessageReply &&other) = delete;
|
||||||
HistoryMessageReply &operator=(
|
HistoryMessageReply &operator=(
|
||||||
const HistoryMessageReply &other) = delete;
|
const HistoryMessageReply &other) = delete;
|
||||||
HistoryMessageReply &operator=(HistoryMessageReply &&other) = default;
|
HistoryMessageReply &operator=(HistoryMessageReply &&other);
|
||||||
~HistoryMessageReply() {
|
~HistoryMessageReply();
|
||||||
// clearData() should be called by holder.
|
|
||||||
Expects(replyToMsg.empty());
|
|
||||||
Expects(replyToVia == nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr auto kBarAlpha = 230. / 255.;
|
static constexpr auto kBarAlpha = 230. / 255.;
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/chat/message_bar.h"
|
#include "ui/chat/message_bar.h"
|
||||||
#include "ui/chat/attach/attach_send_files_way.h"
|
#include "ui/chat/attach/attach_send_files_way.h"
|
||||||
#include "ui/chat/choose_send_as.h"
|
#include "ui/chat/choose_send_as.h"
|
||||||
|
#include "ui/effects/spoiler_mess.h"
|
||||||
#include "ui/image/image.h"
|
#include "ui/image/image.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "ui/power_saving.h"
|
#include "ui/power_saving.h"
|
||||||
|
@ -1779,7 +1780,7 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(
|
||||||
MessageCursor cursor = {
|
MessageCursor cursor = {
|
||||||
int(textWithTags.text.size()),
|
int(textWithTags.text.size()),
|
||||||
int(textWithTags.text.size()),
|
int(textWithTags.text.size()),
|
||||||
QFIXED_MAX,
|
Ui::kQFixedMax,
|
||||||
};
|
};
|
||||||
_history->setLocalDraft(std::make_unique<Data::Draft>(
|
_history->setLocalDraft(std::make_unique<Data::Draft>(
|
||||||
textWithTags,
|
textWithTags,
|
||||||
|
@ -6216,7 +6217,11 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
|
||||||
crl::guard(_list, [=] { cancelEdit(); }));
|
crl::guard(_list, [=] { cancelEdit(); }));
|
||||||
} else if (_inReplyEditForward) {
|
} else if (_inReplyEditForward) {
|
||||||
if (isReadyToForward) {
|
if (isReadyToForward) {
|
||||||
_forwardPanel->editOptions(controller()->uiShow());
|
if (e->button() != Qt::LeftButton) {
|
||||||
|
_forwardPanel->editToNextOption();
|
||||||
|
} else {
|
||||||
|
_forwardPanel->editOptions(controller()->uiShow());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
controller()->showPeerHistory(
|
controller()->showPeerHistory(
|
||||||
_peer,
|
_peer,
|
||||||
|
@ -7201,7 +7206,7 @@ void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
|
||||||
const auto cursor = MessageCursor {
|
const auto cursor = MessageCursor {
|
||||||
int(editData.text.size()),
|
int(editData.text.size()),
|
||||||
int(editData.text.size()),
|
int(editData.text.size()),
|
||||||
QFIXED_MAX
|
Ui::kQFixedMax
|
||||||
};
|
};
|
||||||
const auto previewPage = [&]() -> WebPageData* {
|
const auto previewPage = [&]() -> WebPageData* {
|
||||||
if (const auto media = item->media()) {
|
if (const auto media = item->media()) {
|
||||||
|
@ -7503,7 +7508,7 @@ void HistoryWidget::updatePreview() {
|
||||||
Ui::NameTextOptions());
|
Ui::NameTextOptions());
|
||||||
auto linkText = QStringView(_previewLinks).split(' ').at(0).toString();
|
auto linkText = QStringView(_previewLinks).split(' ').at(0).toString();
|
||||||
_previewDescription.setText(
|
_previewDescription.setText(
|
||||||
st::messageTextStyle,
|
st::defaultTextStyle,
|
||||||
linkText,
|
linkText,
|
||||||
Ui::DialogTextOptions());
|
Ui::DialogTextOptions());
|
||||||
|
|
||||||
|
@ -7524,7 +7529,7 @@ void HistoryWidget::updatePreview() {
|
||||||
preview.title,
|
preview.title,
|
||||||
Ui::NameTextOptions());
|
Ui::NameTextOptions());
|
||||||
_previewDescription.setText(
|
_previewDescription.setText(
|
||||||
st::messageTextStyle,
|
st::defaultTextStyle,
|
||||||
preview.description,
|
preview.description,
|
||||||
Ui::DialogTextOptions());
|
Ui::DialogTextOptions());
|
||||||
}
|
}
|
||||||
|
@ -7779,7 +7784,7 @@ void HistoryWidget::updateReplyEditText(not_null<HistoryItem*> item) {
|
||||||
.customEmojiRepaint = [=] { updateField(); },
|
.customEmojiRepaint = [=] { updateField(); },
|
||||||
};
|
};
|
||||||
_replyEditMsgText.setMarkedText(
|
_replyEditMsgText.setMarkedText(
|
||||||
st::messageTextStyle,
|
st::defaultTextStyle,
|
||||||
item->inReplyText(),
|
item->inReplyText(),
|
||||||
Ui::DialogTextOptions(),
|
Ui::DialogTextOptions(),
|
||||||
context);
|
context);
|
||||||
|
@ -7970,7 +7975,7 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
|
||||||
.now = now,
|
.now = now,
|
||||||
.pausedEmoji = paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = paused || On(PowerSaving::kEmojiChat),
|
||||||
.pausedSpoiler = pausedSpoiler,
|
.pausedSpoiler = pausedSpoiler,
|
||||||
.elisionLines = 1,
|
.elisionOneLine = true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
p.setFont(st::msgDateFont);
|
p.setFont(st::msgDateFont);
|
||||||
|
|
|
@ -68,6 +68,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/controls/send_as_button.h"
|
#include "ui/controls/send_as_button.h"
|
||||||
#include "ui/controls/silent_toggle.h"
|
#include "ui/controls/silent_toggle.h"
|
||||||
#include "ui/chat/choose_send_as.h"
|
#include "ui/chat/choose_send_as.h"
|
||||||
|
#include "ui/effects/spoiler_mess.h"
|
||||||
#include "window/window_adaptive.h"
|
#include "window/window_adaptive.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
@ -902,7 +903,7 @@ void FieldHeader::paintEditOrReplyToMessage(Painter &p) {
|
||||||
.now = crl::now(),
|
.now = crl::now(),
|
||||||
.pausedEmoji = p.inactive() || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = p.inactive() || On(PowerSaving::kEmojiChat),
|
||||||
.pausedSpoiler = p.inactive() || On(PowerSaving::kChatSpoiler),
|
.pausedSpoiler = p.inactive() || On(PowerSaving::kChatSpoiler),
|
||||||
.elisionLines = 1,
|
.elisionOneLine = true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2883,7 +2884,7 @@ void ComposeControls::editMessage(not_null<HistoryItem*> item) {
|
||||||
const auto cursor = MessageCursor{
|
const auto cursor = MessageCursor{
|
||||||
int(editData.text.size()),
|
int(editData.text.size()),
|
||||||
int(editData.text.size()),
|
int(editData.text.size()),
|
||||||
QFIXED_MAX
|
Ui::kQFixedMax
|
||||||
};
|
};
|
||||||
const auto previewPage = [&]() -> WebPageData* {
|
const auto previewPage = [&]() -> WebPageData* {
|
||||||
if (const auto media = item->media()) {
|
if (const auto media = item->media()) {
|
||||||
|
|
|
@ -36,6 +36,31 @@ constexpr auto kUnknownVersion = -1;
|
||||||
constexpr auto kNameWithCaptionsVersion = -2;
|
constexpr auto kNameWithCaptionsVersion = -2;
|
||||||
constexpr auto kNameNoCaptionsVersion = -3;
|
constexpr auto kNameNoCaptionsVersion = -3;
|
||||||
|
|
||||||
|
[[nodiscard]] bool HasCaptions(const HistoryItemsList &list) {
|
||||||
|
for (const auto &item : list) {
|
||||||
|
if (const auto media = item->media()) {
|
||||||
|
if (!item->originalText().text.isEmpty()
|
||||||
|
&& media->allowsEditCaption()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool HasOnlyForcedForwardedInfo(const HistoryItemsList &list) {
|
||||||
|
for (const auto &item : list) {
|
||||||
|
if (const auto media = item->media()) {
|
||||||
|
if (!media->forceForwardedInfo()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ForwardPanel::ForwardPanel(Fn<void()> repaint)
|
ForwardPanel::ForwardPanel(Fn<void()> repaint)
|
||||||
|
@ -181,7 +206,7 @@ void ForwardPanel::updateTexts() {
|
||||||
text = DropCustomEmoji(std::move(text));
|
text = DropCustomEmoji(std::move(text));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
text = Ui::Text::PlainLink(
|
text = Ui::Text::Colorized(
|
||||||
tr::lng_forward_messages(tr::now, lt_count, count));
|
tr::lng_forward_messages(tr::now, lt_count, count));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,7 +216,7 @@ void ForwardPanel::updateTexts() {
|
||||||
.customEmojiRepaint = _repaint,
|
.customEmojiRepaint = _repaint,
|
||||||
};
|
};
|
||||||
_text.setMarkedText(
|
_text.setMarkedText(
|
||||||
st::messageTextStyle,
|
st::defaultTextStyle,
|
||||||
text,
|
text,
|
||||||
Ui::DialogTextOptions(),
|
Ui::DialogTextOptions(),
|
||||||
context);
|
context);
|
||||||
|
@ -224,32 +249,10 @@ void ForwardPanel::editOptions(std::shared_ptr<ChatHelpers::Show> show) {
|
||||||
const auto now = _data.options;
|
const auto now = _data.options;
|
||||||
const auto count = _data.items.size();
|
const auto count = _data.items.size();
|
||||||
const auto dropNames = (now != Options::PreserveInfo);
|
const auto dropNames = (now != Options::PreserveInfo);
|
||||||
const auto hasCaptions = [&] {
|
const auto hasCaptions = HasCaptions(_data.items);
|
||||||
for (const auto item : _data.items) {
|
const auto hasOnlyForcedForwardedInfo = hasCaptions
|
||||||
if (const auto media = item->media()) {
|
? false
|
||||||
if (!item->originalText().text.isEmpty()
|
: HasOnlyForcedForwardedInfo(_data.items);
|
||||||
&& media->allowsEditCaption()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}();
|
|
||||||
const auto hasOnlyForcedForwardedInfo = [&] {
|
|
||||||
if (hasCaptions) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (const auto item : _data.items) {
|
|
||||||
if (const auto media = item->media()) {
|
|
||||||
if (!media->forceForwardedInfo()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}();
|
|
||||||
const auto dropCaptions = (now == Options::NoNamesAndCaptions);
|
const auto dropCaptions = (now == Options::NoNamesAndCaptions);
|
||||||
const auto weak = base::make_weak(this);
|
const auto weak = base::make_weak(this);
|
||||||
const auto changeRecipient = crl::guard(this, [=] {
|
const auto changeRecipient = crl::guard(this, [=] {
|
||||||
|
@ -299,6 +302,30 @@ void ForwardPanel::editOptions(std::shared_ptr<ChatHelpers::Show> show) {
|
||||||
changeRecipient));
|
changeRecipient));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ForwardPanel::editToNextOption() {
|
||||||
|
using Options = Data::ForwardOptions;
|
||||||
|
const auto hasCaptions = HasCaptions(_data.items);
|
||||||
|
const auto hasOnlyForcedForwardedInfo = hasCaptions
|
||||||
|
? false
|
||||||
|
: HasOnlyForcedForwardedInfo(_data.items);
|
||||||
|
if (hasOnlyForcedForwardedInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto now = _data.options;
|
||||||
|
const auto next = (now == Options::PreserveInfo)
|
||||||
|
? Options::NoSenderNames
|
||||||
|
: ((now == Options::NoSenderNames) && hasCaptions)
|
||||||
|
? Options::NoNamesAndCaptions
|
||||||
|
: Options::PreserveInfo;
|
||||||
|
|
||||||
|
_to->owningHistory()->setForwardDraft(_to->topicRootId(), {
|
||||||
|
.ids = _to->owner().itemsToIds(_data.items),
|
||||||
|
.options = next,
|
||||||
|
});
|
||||||
|
_repaint();
|
||||||
|
}
|
||||||
|
|
||||||
void ForwardPanel::paint(
|
void ForwardPanel::paint(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
int x,
|
int x,
|
||||||
|
@ -364,7 +391,7 @@ void ForwardPanel::paint(
|
||||||
.now = now,
|
.now = now,
|
||||||
.pausedEmoji = paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = paused || On(PowerSaving::kEmojiChat),
|
||||||
.pausedSpoiler = pausedSpoiler,
|
.pausedSpoiler = pausedSpoiler,
|
||||||
.elisionLines = 1,
|
.elisionOneLine = true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ public:
|
||||||
[[nodiscard]] rpl::producer<> itemsUpdated() const;
|
[[nodiscard]] rpl::producer<> itemsUpdated() const;
|
||||||
|
|
||||||
void editOptions(std::shared_ptr<ChatHelpers::Show> show);
|
void editOptions(std::shared_ptr<ChatHelpers::Show> show);
|
||||||
|
void editToNextOption();
|
||||||
|
|
||||||
[[nodiscard]] const HistoryItemsList &items() const;
|
[[nodiscard]] const HistoryItemsList &items() const;
|
||||||
[[nodiscard]] bool empty() const;
|
[[nodiscard]] bool empty() const;
|
||||||
|
|
|
@ -1622,6 +1622,9 @@ void Message::paintText(
|
||||||
.position = trect.topLeft(),
|
.position = trect.topLeft(),
|
||||||
.availableWidth = trect.width(),
|
.availableWidth = trect.width(),
|
||||||
.palette = &stm->textPalette,
|
.palette = &stm->textPalette,
|
||||||
|
.pre = stm->preCache.get(),
|
||||||
|
.blockquote = stm->blockquoteCache.get(),
|
||||||
|
.colors = context.st->highlightColors(),
|
||||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||||
.now = context.now,
|
.now = context.now,
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
|
|
|
@ -358,15 +358,15 @@ void ServiceMessagePainter::PaintComplexBubble(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<int> ServiceMessagePainter::CountLineWidths(
|
std::vector<int> ServiceMessagePainter::CountLineWidths(
|
||||||
const Ui::Text::String &text,
|
const Ui::Text::String &text,
|
||||||
const QRect &textRect) {
|
const QRect &textRect) {
|
||||||
const auto linesCount = qMax(
|
const auto linesCount = qMax(
|
||||||
textRect.height() / st::msgServiceFont->height,
|
textRect.height() / st::msgServiceFont->height,
|
||||||
1);
|
1);
|
||||||
auto result = QVector<int>();
|
auto result = text.countLineWidths(textRect.width(), {
|
||||||
result.reserve(linesCount);
|
.reserve = linesCount,
|
||||||
text.countLineWidths(textRect.width(), &result);
|
});
|
||||||
|
|
||||||
const auto minDelta = 2 * (Ui::HistoryServiceMsgRadius()
|
const auto minDelta = 2 * (Ui::HistoryServiceMsgRadius()
|
||||||
+ Ui::HistoryServiceMsgInvertedRadius()
|
+ Ui::HistoryServiceMsgInvertedRadius()
|
||||||
|
|
|
@ -113,7 +113,7 @@ public:
|
||||||
const QRect &textRect);
|
const QRect &textRect);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static QVector<int> CountLineWidths(
|
static std::vector<int> CountLineWidths(
|
||||||
const Ui::Text::String &text,
|
const Ui::Text::String &text,
|
||||||
const QRect &textRect);
|
const QRect &textRect);
|
||||||
|
|
||||||
|
|
|
@ -1113,7 +1113,9 @@ void TopBarWidget::updateControlsVisibility() {
|
||||||
const auto callsEnabled = [&] {
|
const auto callsEnabled = [&] {
|
||||||
if (const auto peer = _activeChat.key.peer()) {
|
if (const auto peer = _activeChat.key.peer()) {
|
||||||
if (const auto user = peer->asUser()) {
|
if (const auto user = peer->asUser()) {
|
||||||
return !user->isSelf() && !user->isBot();
|
return !user->isSelf()
|
||||||
|
&& !user->isBot()
|
||||||
|
&& !peer->isServiceUser();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -748,6 +748,9 @@ void Document::draw(
|
||||||
.position = { st::msgPadding.left(), captiontop },
|
.position = { st::msgPadding.left(), captiontop },
|
||||||
.availableWidth = captionw,
|
.availableWidth = captionw,
|
||||||
.palette = &stm->textPalette,
|
.palette = &stm->textPalette,
|
||||||
|
.pre = stm->preCache.get(),
|
||||||
|
.blockquote = stm->blockquoteCache.get(),
|
||||||
|
.colors = context.st->highlightColors(),
|
||||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||||
.now = context.now,
|
.now = context.now,
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
|
|
|
@ -235,6 +235,9 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const {
|
||||||
painty + painth + st::mediaCaptionSkip),
|
painty + painth + st::mediaCaptionSkip),
|
||||||
.availableWidth = captionw,
|
.availableWidth = captionw,
|
||||||
.palette = &stm->textPalette,
|
.palette = &stm->textPalette,
|
||||||
|
.pre = stm->preCache.get(),
|
||||||
|
.blockquote = stm->blockquoteCache.get(),
|
||||||
|
.colors = context.st->highlightColors(),
|
||||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||||
.now = context.now,
|
.now = context.now,
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
|
|
|
@ -257,7 +257,7 @@ void Game::draw(Painter &p, const PaintContext &context) const {
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
||||||
.selection = toDescriptionSelection(context.selection),
|
.selection = toDescriptionSelection(context.selection),
|
||||||
.elisionLines = _descriptionLines,
|
.elisionHeight = _descriptionLines * lineHeight,
|
||||||
.elisionRemoveFromEnd = endskip,
|
.elisionRemoveFromEnd = endskip,
|
||||||
});
|
});
|
||||||
tshift += _descriptionLines * lineHeight;
|
tshift += _descriptionLines * lineHeight;
|
||||||
|
|
|
@ -709,6 +709,9 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
|
||||||
.position = QPoint(st::msgPadding.left(), top),
|
.position = QPoint(st::msgPadding.left(), top),
|
||||||
.availableWidth = captionw,
|
.availableWidth = captionw,
|
||||||
.palette = &stm->textPalette,
|
.palette = &stm->textPalette,
|
||||||
|
.pre = stm->preCache.get(),
|
||||||
|
.blockquote = stm->blockquoteCache.get(),
|
||||||
|
.colors = context.st->highlightColors(),
|
||||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||||
.now = context.now,
|
.now = context.now,
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
|
|
|
@ -14,6 +14,10 @@ namespace Stickers {
|
||||||
struct LargeEmojiImage;
|
struct LargeEmojiImage;
|
||||||
} // namespace Stickers
|
} // namespace Stickers
|
||||||
|
|
||||||
|
namespace Ui::Text {
|
||||||
|
class CustomEmoji;
|
||||||
|
} // namespace Ui::Text
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
using LargeEmojiMedia = std::variant<
|
using LargeEmojiMedia = std::variant<
|
||||||
|
|
|
@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/item_text_options.h"
|
#include "ui/item_text_options.h"
|
||||||
#include "ui/chat/chat_style.h"
|
#include "ui/chat/chat_style.h"
|
||||||
#include "ui/chat/message_bubble.h"
|
#include "ui/chat/message_bubble.h"
|
||||||
|
#include "ui/effects/spoiler_mess.h"
|
||||||
#include "ui/image/image_prepare.h"
|
#include "ui/image/image_prepare.h"
|
||||||
#include "ui/power_saving.h"
|
#include "ui/power_saving.h"
|
||||||
#include "core/ui_integration.h"
|
#include "core/ui_integration.h"
|
||||||
|
|
|
@ -367,6 +367,9 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
|
||||||
captiony),
|
captiony),
|
||||||
.availableWidth = captionw,
|
.availableWidth = captionw,
|
||||||
.palette = &stm->textPalette,
|
.palette = &stm->textPalette,
|
||||||
|
.pre = stm->preCache.get(),
|
||||||
|
.blockquote = stm->blockquoteCache.get(),
|
||||||
|
.colors = context.st->highlightColors(),
|
||||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||||
.now = context.now,
|
.now = context.now,
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
|
|
|
@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/chat/message_bubble.h"
|
#include "ui/chat/message_bubble.h"
|
||||||
#include "ui/effects/animations.h"
|
#include "ui/effects/animations.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class SpoilerAnimation;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
struct MediaSpoiler {
|
struct MediaSpoiler {
|
||||||
|
|
|
@ -405,6 +405,9 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
|
||||||
.position = QPoint(st::msgPadding.left(), top),
|
.position = QPoint(st::msgPadding.left(), top),
|
||||||
.availableWidth = captionw,
|
.availableWidth = captionw,
|
||||||
.palette = &stm->textPalette,
|
.palette = &stm->textPalette,
|
||||||
|
.pre = stm->preCache.get(),
|
||||||
|
.blockquote = stm->blockquoteCache.get(),
|
||||||
|
.colors = context.st->highlightColors(),
|
||||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||||
.now = context.now,
|
.now = context.now,
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
|
|
|
@ -583,7 +583,9 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
||||||
.selection = toDescriptionSelection(context.selection),
|
.selection = toDescriptionSelection(context.selection),
|
||||||
.elisionLines = std::max(_descriptionLines, 0),
|
.elisionHeight = ((_descriptionLines > 0)
|
||||||
|
? (_descriptionLines * lineHeight)
|
||||||
|
: 0),
|
||||||
.elisionRemoveFromEnd = (_descriptionLines > 0) ? endskip : 0,
|
.elisionRemoveFromEnd = (_descriptionLines > 0) ? endskip : 0,
|
||||||
});
|
});
|
||||||
tshift += (_descriptionLines > 0)
|
tshift += (_descriptionLines > 0)
|
||||||
|
|
|
@ -20,6 +20,10 @@ struct ReactionFlyAnimationArgs;
|
||||||
class ReactionFlyAnimation;
|
class ReactionFlyAnimation;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Ui::Text {
|
||||||
|
class CustomEmoji;
|
||||||
|
} // namespace Ui::Text
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
using PaintContext = Ui::ChatPaintContext;
|
using PaintContext = Ui::ChatPaintContext;
|
||||||
class Message;
|
class Message;
|
||||||
|
|
|
@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/text/text_custom_emoji.h" // Ui::Text::CustomEmojiFactory.
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
enum class WhoReadType;
|
enum class WhoReadType;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
305
Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp
Normal file
|
@ -0,0 +1,305 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "info/boosts/info_boosts_inner_widget.h"
|
||||||
|
|
||||||
|
#include "api/api_statistics.h"
|
||||||
|
#include "boxes/peers/edit_peer_invite_link.h"
|
||||||
|
#include "info/boosts/info_boosts_widget.h"
|
||||||
|
#include "info/info_controller.h"
|
||||||
|
#include "info/statistics/info_statistics_list_controllers.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "settings/settings_common.h"
|
||||||
|
#include "statistics/widgets/chart_header_widget.h"
|
||||||
|
#include "ui/boxes/boost_box.h"
|
||||||
|
#include "ui/controls/invite_link_buttons.h"
|
||||||
|
#include "ui/controls/invite_link_label.h"
|
||||||
|
#include "ui/rect.h"
|
||||||
|
#include "ui/widgets/labels.h"
|
||||||
|
#include "styles/style_info.h"
|
||||||
|
#include "styles/style_statistics.h"
|
||||||
|
|
||||||
|
#include <QtGui/QGuiApplication>
|
||||||
|
|
||||||
|
namespace Info::Boosts {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void AddHeader(
|
||||||
|
not_null<Ui::VerticalLayout*> content,
|
||||||
|
tr::phrase<> text) {
|
||||||
|
const auto header = content->add(
|
||||||
|
object_ptr<Statistic::Header>(content),
|
||||||
|
st::statisticsLayerMargins + st::boostsChartHeaderPadding);
|
||||||
|
header->resizeToWidth(header->width());
|
||||||
|
header->setTitle(text(tr::now));
|
||||||
|
header->setSubTitle({});
|
||||||
|
}
|
||||||
|
|
||||||
|
void FillOverview(
|
||||||
|
not_null<Ui::VerticalLayout*> content,
|
||||||
|
const Data::BoostStatus &status) {
|
||||||
|
const auto &stats = status.overview;
|
||||||
|
|
||||||
|
::Settings::AddSkip(content, st::boostsLayerOverviewMargins.top());
|
||||||
|
AddHeader(content, tr::lng_stats_overview_title);
|
||||||
|
::Settings::AddSkip(content);
|
||||||
|
|
||||||
|
const auto diffBetweenHeaders = 0
|
||||||
|
+ st::statisticsOverviewValue.style.font->height
|
||||||
|
- st::statisticsHeaderTitleTextStyle.font->height;
|
||||||
|
|
||||||
|
const auto container = content->add(
|
||||||
|
object_ptr<Ui::RpWidget>(content),
|
||||||
|
st::statisticsLayerMargins);
|
||||||
|
|
||||||
|
const auto addPrimary = [&](float64 v) {
|
||||||
|
return Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
container,
|
||||||
|
(v >= 0)
|
||||||
|
? Lang::FormatCountToShort(v).string
|
||||||
|
: QString(),
|
||||||
|
st::statisticsOverviewValue);
|
||||||
|
};
|
||||||
|
const auto addSub = [&](
|
||||||
|
not_null<Ui::RpWidget*> primary,
|
||||||
|
float64 percentage,
|
||||||
|
tr::phrase<> text) {
|
||||||
|
const auto second = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
container,
|
||||||
|
percentage
|
||||||
|
? u"%1%"_q.arg(std::abs(std::round(percentage * 10.) / 10.))
|
||||||
|
: QString(),
|
||||||
|
st::statisticsOverviewSecondValue);
|
||||||
|
second->setTextColorOverride(st::windowSubTextFg->c);
|
||||||
|
const auto sub = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
container,
|
||||||
|
text(),
|
||||||
|
st::statisticsOverviewSubtext);
|
||||||
|
sub->setTextColorOverride(st::windowSubTextFg->c);
|
||||||
|
|
||||||
|
primary->geometryValue(
|
||||||
|
) | rpl::start_with_next([=](const QRect &g) {
|
||||||
|
const auto &padding = st::statisticsOverviewSecondValuePadding;
|
||||||
|
second->moveToLeft(
|
||||||
|
rect::right(g) + padding.left(),
|
||||||
|
g.y() + padding.top());
|
||||||
|
sub->moveToLeft(
|
||||||
|
g.x(),
|
||||||
|
st::statisticsChartHeaderHeight
|
||||||
|
- st::statisticsOverviewSubtext.style.font->height
|
||||||
|
+ g.y()
|
||||||
|
+ diffBetweenHeaders);
|
||||||
|
}, primary->lifetime());
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const auto topLeftLabel = addPrimary(stats.level);
|
||||||
|
const auto topRightLabel = addPrimary(stats.premiumMemberCount);
|
||||||
|
const auto bottomLeftLabel = addPrimary(stats.boostCount);
|
||||||
|
const auto bottomRightLabel = addPrimary(
|
||||||
|
stats.nextLevelBoostCount - stats.boostCount);
|
||||||
|
|
||||||
|
addSub(
|
||||||
|
topLeftLabel,
|
||||||
|
0,
|
||||||
|
tr::lng_boosts_level);
|
||||||
|
addSub(
|
||||||
|
topRightLabel,
|
||||||
|
stats.premiumMemberPercentage,
|
||||||
|
tr::lng_boosts_premium_audience);
|
||||||
|
addSub(
|
||||||
|
bottomLeftLabel,
|
||||||
|
0,
|
||||||
|
tr::lng_boosts_existing);
|
||||||
|
addSub(
|
||||||
|
bottomRightLabel,
|
||||||
|
0,
|
||||||
|
tr::lng_boosts_next_level);
|
||||||
|
|
||||||
|
container->showChildren();
|
||||||
|
container->resize(container->width(), topLeftLabel->height() * 5);
|
||||||
|
container->sizeValue(
|
||||||
|
) | rpl::start_with_next([=](const QSize &s) {
|
||||||
|
const auto halfWidth = s.width() / 2;
|
||||||
|
{
|
||||||
|
const auto &p = st::boostsOverviewValuePadding;
|
||||||
|
topLeftLabel->moveToLeft(p.left(), p.top());
|
||||||
|
}
|
||||||
|
topRightLabel->moveToLeft(
|
||||||
|
topLeftLabel->x() + halfWidth + st::statisticsOverviewRightSkip,
|
||||||
|
topLeftLabel->y());
|
||||||
|
bottomLeftLabel->moveToLeft(
|
||||||
|
topLeftLabel->x(),
|
||||||
|
topLeftLabel->y() + st::statisticsOverviewMidSkip);
|
||||||
|
bottomRightLabel->moveToLeft(
|
||||||
|
topRightLabel->x(),
|
||||||
|
bottomLeftLabel->y());
|
||||||
|
}, container->lifetime());
|
||||||
|
::Settings::AddSkip(content, st::boostsLayerOverviewMargins.bottom());
|
||||||
|
}
|
||||||
|
|
||||||
|
void FillShareLink(
|
||||||
|
not_null<Ui::VerticalLayout*> content,
|
||||||
|
std::shared_ptr<Ui::Show> show,
|
||||||
|
const QString &link,
|
||||||
|
not_null<PeerData*> peer) {
|
||||||
|
const auto weak = Ui::MakeWeak(content);
|
||||||
|
const auto copyLink = crl::guard(weak, [=] {
|
||||||
|
QGuiApplication::clipboard()->setText(link);
|
||||||
|
show->showToast(tr::lng_channel_public_link_copied(tr::now));
|
||||||
|
});
|
||||||
|
const auto shareLink = crl::guard(weak, [=] {
|
||||||
|
show->showBox(ShareInviteLinkBox(peer, link));
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto label = content->lifetime().make_state<Ui::InviteLinkLabel>(
|
||||||
|
content,
|
||||||
|
rpl::single(link),
|
||||||
|
nullptr);
|
||||||
|
content->add(
|
||||||
|
label->take(),
|
||||||
|
st::boostsLinkFieldPadding);
|
||||||
|
|
||||||
|
label->clicks(
|
||||||
|
) | rpl::start_with_next(copyLink, label->lifetime());
|
||||||
|
const auto copyShareWrap = content->add(
|
||||||
|
object_ptr<Ui::VerticalLayout>(content));
|
||||||
|
Ui::AddCopyShareLinkButtons(copyShareWrap, copyLink, shareLink);
|
||||||
|
copyShareWrap->widgetAt(0)->showChildren();
|
||||||
|
::Settings::AddSkip(content, st::boostsLinkFieldPadding.bottom());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
InnerWidget::InnerWidget(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Controller*> controller,
|
||||||
|
not_null<PeerData*> peer)
|
||||||
|
: VerticalLayout(parent)
|
||||||
|
, _controller(controller)
|
||||||
|
, _peer(peer)
|
||||||
|
, _show(controller->uiShow()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void InnerWidget::load() {
|
||||||
|
const auto api = lifetime().make_state<Api::Boosts>(_peer);
|
||||||
|
|
||||||
|
_showFinished.events(
|
||||||
|
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||||
|
api->request(
|
||||||
|
) | rpl::start_with_error_done([](const QString &error) {
|
||||||
|
}, [=] {
|
||||||
|
_state = api->boostStatus();
|
||||||
|
fill();
|
||||||
|
}, lifetime());
|
||||||
|
}, lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
void InnerWidget::fill() {
|
||||||
|
const auto fakeShowed = lifetime().make_state<rpl::event_stream<>>();
|
||||||
|
const auto &status = _state;
|
||||||
|
const auto inner = this;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto dividerContent = object_ptr<Ui::VerticalLayout>(inner);
|
||||||
|
Ui::FillBoostLimit(
|
||||||
|
fakeShowed->events(),
|
||||||
|
rpl::single(status.overview.isBoosted),
|
||||||
|
dividerContent.data(),
|
||||||
|
Ui::BoostBoxData{
|
||||||
|
.boost = Ui::BoostCounters{
|
||||||
|
.level = status.overview.level,
|
||||||
|
.boosts = status.overview.boostCount,
|
||||||
|
.thisLevelBoosts
|
||||||
|
= status.overview.currentLevelBoostCount,
|
||||||
|
.nextLevelBoosts
|
||||||
|
= status.overview.nextLevelBoostCount,
|
||||||
|
.mine = status.overview.isBoosted,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
st::statisticsLimitsLinePadding);
|
||||||
|
inner->add(object_ptr<Ui::DividerLabel>(
|
||||||
|
inner,
|
||||||
|
std::move(dividerContent),
|
||||||
|
st::statisticsLimitsDividerPadding));
|
||||||
|
}
|
||||||
|
|
||||||
|
FillOverview(inner, status);
|
||||||
|
|
||||||
|
::Settings::AddSkip(inner);
|
||||||
|
::Settings::AddDivider(inner);
|
||||||
|
::Settings::AddSkip(inner);
|
||||||
|
|
||||||
|
if (status.firstSlice.total > 0) {
|
||||||
|
::Settings::AddSkip(inner);
|
||||||
|
using PeerPtr = not_null<PeerData*>;
|
||||||
|
const auto header = inner->add(
|
||||||
|
object_ptr<Statistic::Header>(inner),
|
||||||
|
st::statisticsLayerMargins
|
||||||
|
+ st::boostsChartHeaderPadding);
|
||||||
|
header->resizeToWidth(header->width());
|
||||||
|
header->setTitle(tr::lng_boosts_list_title(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
status.firstSlice.total));
|
||||||
|
header->setSubTitle({});
|
||||||
|
Statistics::AddBoostsList(
|
||||||
|
status.firstSlice,
|
||||||
|
inner,
|
||||||
|
[=](PeerPtr p) { _controller->showPeerInfo(p); },
|
||||||
|
_peer,
|
||||||
|
tr::lng_boosts_title());
|
||||||
|
::Settings::AddSkip(inner);
|
||||||
|
::Settings::AddDividerText(
|
||||||
|
inner,
|
||||||
|
tr::lng_boosts_list_subtext());
|
||||||
|
::Settings::AddSkip(inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
::Settings::AddSkip(inner);
|
||||||
|
AddHeader(inner, tr::lng_boosts_link_title);
|
||||||
|
::Settings::AddSkip(inner, st::boostsLinkSkip);
|
||||||
|
FillShareLink(inner, _show, status.link, _peer);
|
||||||
|
::Settings::AddSkip(inner);
|
||||||
|
::Settings::AddDividerText(inner, tr::lng_boosts_link_subtext());
|
||||||
|
|
||||||
|
resizeToWidth(width());
|
||||||
|
crl::on_main([=]{ fakeShowed->fire({}); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void InnerWidget::saveState(not_null<Memento*> memento) {
|
||||||
|
memento->setState(base::take(_state));
|
||||||
|
}
|
||||||
|
|
||||||
|
void InnerWidget::restoreState(not_null<Memento*> memento) {
|
||||||
|
_state = memento->state();
|
||||||
|
if (!_state.link.isEmpty()) {
|
||||||
|
fill();
|
||||||
|
} else {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
Ui::RpWidget::resizeToWidth(width());
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<Ui::ScrollToRequest> InnerWidget::scrollToRequests() const {
|
||||||
|
return _scrollToRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto InnerWidget::showRequests() const -> rpl::producer<ShowRequest> {
|
||||||
|
return _showRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InnerWidget::showFinished() {
|
||||||
|
_showFinished.fire({});
|
||||||
|
}
|
||||||
|
|
||||||
|
not_null<PeerData*> InnerWidget::peer() const {
|
||||||
|
return _peer;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Info::Boosts
|
||||||
|
|
63
Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.h
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "data/data_boosts.h"
|
||||||
|
#include "ui/widgets/scroll_area.h"
|
||||||
|
#include "ui/wrap/vertical_layout.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class Show;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Info {
|
||||||
|
class Controller;
|
||||||
|
} // namespace Info
|
||||||
|
|
||||||
|
namespace Info::Boosts {
|
||||||
|
|
||||||
|
class Memento;
|
||||||
|
|
||||||
|
class InnerWidget final : public Ui::VerticalLayout {
|
||||||
|
public:
|
||||||
|
struct ShowRequest final {
|
||||||
|
};
|
||||||
|
|
||||||
|
InnerWidget(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Controller*> controller,
|
||||||
|
not_null<PeerData*> peer);
|
||||||
|
|
||||||
|
[[nodiscard]] not_null<PeerData*> peer() const;
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
|
||||||
|
[[nodiscard]] rpl::producer<ShowRequest> showRequests() const;
|
||||||
|
|
||||||
|
void showFinished();
|
||||||
|
|
||||||
|
void saveState(not_null<Memento*> memento);
|
||||||
|
void restoreState(not_null<Memento*> memento);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void load();
|
||||||
|
void fill();
|
||||||
|
|
||||||
|
not_null<Controller*> _controller;
|
||||||
|
not_null<PeerData*> _peer;
|
||||||
|
std::shared_ptr<Ui::Show> _show;
|
||||||
|
|
||||||
|
Data::BoostStatus _state;
|
||||||
|
|
||||||
|
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
|
||||||
|
rpl::event_stream<ShowRequest> _showRequests;
|
||||||
|
rpl::event_stream<> _showFinished;
|
||||||
|
rpl::event_stream<bool> _loaded;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Info::Boosts
|
121
Telegram/SourceFiles/info/boosts/info_boosts_widget.cpp
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "info/boosts/info_boosts_widget.h"
|
||||||
|
|
||||||
|
#include "info/boosts/info_boosts_inner_widget.h"
|
||||||
|
#include "info/info_controller.h"
|
||||||
|
#include "info/info_memento.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
|
||||||
|
namespace Info::Boosts {
|
||||||
|
|
||||||
|
Memento::Memento(not_null<Controller*> controller)
|
||||||
|
: ContentMemento(Info::Statistics::Tag{
|
||||||
|
controller->statisticsPeer(),
|
||||||
|
{}
|
||||||
|
}) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Memento::Memento(not_null<PeerData*> peer)
|
||||||
|
: ContentMemento(Info::Statistics::Tag{ peer, {} }) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Memento::~Memento() = default;
|
||||||
|
|
||||||
|
Section Memento::section() const {
|
||||||
|
return Section(Section::Type::Boosts);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Memento::setState(SavedState state) {
|
||||||
|
_state = std::move(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
Memento::SavedState Memento::state() {
|
||||||
|
return base::take(_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
object_ptr<ContentWidget> Memento::createWidget(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Controller*> controller,
|
||||||
|
const QRect &geometry) {
|
||||||
|
auto result = object_ptr<Widget>(parent, controller);
|
||||||
|
result->setInternalState(geometry, this);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget::Widget(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Controller*> controller)
|
||||||
|
: ContentWidget(parent, controller)
|
||||||
|
, _inner(setInnerWidget(
|
||||||
|
object_ptr<InnerWidget>(
|
||||||
|
this,
|
||||||
|
controller,
|
||||||
|
controller->statisticsPeer()))) {
|
||||||
|
_inner->showRequests(
|
||||||
|
) | rpl::start_with_next([=](InnerWidget::ShowRequest request) {
|
||||||
|
}, _inner->lifetime());
|
||||||
|
_inner->scrollToRequests(
|
||||||
|
) | rpl::start_with_next([=](const Ui::ScrollToRequest &request) {
|
||||||
|
scrollTo(request);
|
||||||
|
}, _inner->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
not_null<PeerData*> Widget::peer() const {
|
||||||
|
return _inner->peer();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Widget::showInternal(not_null<ContentMemento*> memento) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<QString> Widget::title() {
|
||||||
|
return tr::lng_boosts_title();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::setInternalState(
|
||||||
|
const QRect &geometry,
|
||||||
|
not_null<Memento*> memento) {
|
||||||
|
setGeometry(geometry);
|
||||||
|
Ui::SendPendingMoveResizeEvents(this);
|
||||||
|
restoreState(memento);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<bool> Widget::desiredShadowVisibility() const {
|
||||||
|
return rpl::single<bool>(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::showFinished() {
|
||||||
|
_inner->showFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<ContentMemento> Widget::doCreateMemento() {
|
||||||
|
auto result = std::make_shared<Memento>(controller());
|
||||||
|
saveState(result.get());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::saveState(not_null<Memento*> memento) {
|
||||||
|
memento->setScrollTop(scrollTopSave());
|
||||||
|
_inner->saveState(memento);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::restoreState(not_null<Memento*> memento) {
|
||||||
|
_inner->restoreState(memento);
|
||||||
|
scrollTopRestore(memento->scrollTop());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Info::Memento> Make(not_null<PeerData*> peer) {
|
||||||
|
return std::make_shared<Info::Memento>(
|
||||||
|
std::vector<std::shared_ptr<ContentMemento>>(
|
||||||
|
1,
|
||||||
|
std::make_shared<Memento>(peer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Info::Boosts
|
||||||
|
|
68
Telegram/SourceFiles/info/boosts/info_boosts_widget.h
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "data/data_boosts.h"
|
||||||
|
#include "info/info_content_widget.h"
|
||||||
|
|
||||||
|
namespace Info::Boosts {
|
||||||
|
|
||||||
|
class InnerWidget;
|
||||||
|
|
||||||
|
class Memento final : public ContentMemento {
|
||||||
|
public:
|
||||||
|
Memento(not_null<Controller*> controller);
|
||||||
|
Memento(not_null<PeerData*> peer);
|
||||||
|
~Memento();
|
||||||
|
|
||||||
|
object_ptr<ContentWidget> createWidget(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Controller*> controller,
|
||||||
|
const QRect &geometry) override;
|
||||||
|
|
||||||
|
Section section() const override;
|
||||||
|
|
||||||
|
using SavedState = Data::BoostStatus;
|
||||||
|
|
||||||
|
void setState(SavedState states);
|
||||||
|
[[nodiscard]] SavedState state();
|
||||||
|
|
||||||
|
private:
|
||||||
|
SavedState _state;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class Widget final : public ContentWidget {
|
||||||
|
public:
|
||||||
|
Widget(QWidget *parent, not_null<Controller*> controller);
|
||||||
|
|
||||||
|
bool showInternal(not_null<ContentMemento*> memento) override;
|
||||||
|
rpl::producer<QString> title() override;
|
||||||
|
rpl::producer<bool> desiredShadowVisibility() const override;
|
||||||
|
void showFinished() override;
|
||||||
|
|
||||||
|
[[nodiscard]] not_null<PeerData*> peer() const;
|
||||||
|
[[nodiscard]] FullMsgId contextId() const;
|
||||||
|
|
||||||
|
void setInternalState(
|
||||||
|
const QRect &geometry,
|
||||||
|
not_null<Memento*> memento);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void saveState(not_null<Memento*> memento);
|
||||||
|
void restoreState(not_null<Memento*> memento);
|
||||||
|
|
||||||
|
std::shared_ptr<ContentMemento> doCreateMemento() override;
|
||||||
|
|
||||||
|
const not_null<InnerWidget*> _inner;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] std::shared_ptr<Info::Memento> Make(not_null<PeerData*> peer);
|
||||||
|
|
||||||
|
} // namespace Info::Boosts
|
|
@ -131,8 +131,6 @@ infoTopBarTitle: FlatLabel(defaultFlatLabel) {
|
||||||
maxHeight: 20px;
|
maxHeight: 20px;
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(14px semibold);
|
font: font(14px semibold);
|
||||||
linkFont: font(14px semibold);
|
|
||||||
linkFontOver: font(14px semibold);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
infoTopBarMediaCancel: IconButton(infoTopBarBack) {
|
infoTopBarMediaCancel: IconButton(infoTopBarBack) {
|
||||||
|
@ -301,11 +299,6 @@ infoProfilePhotoSize: size(
|
||||||
infoProfileStatus: FlatLabel(defaultFlatLabel) {
|
infoProfileStatus: FlatLabel(defaultFlatLabel) {
|
||||||
maxHeight: 18px;
|
maxHeight: 18px;
|
||||||
textFg: windowSubTextFg;
|
textFg: windowSubTextFg;
|
||||||
style: TextStyle(defaultTextStyle) {
|
|
||||||
font: normalFont;
|
|
||||||
linkFont: normalFont;
|
|
||||||
linkFontOver: normalFont;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
infoProfileCover: InfoProfileCover {
|
infoProfileCover: InfoProfileCover {
|
||||||
height: 108px;
|
height: 108px;
|
||||||
|
@ -320,8 +313,6 @@ infoProfileCover: InfoProfileCover {
|
||||||
textFg: windowBoldFg;
|
textFg: windowBoldFg;
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(16px semibold);
|
font: font(16px semibold);
|
||||||
linkFont: font(16px semibold);
|
|
||||||
linkFontOver: font(16px semibold underline);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nameLeft: 109px;
|
nameLeft: 109px;
|
||||||
|
@ -333,7 +324,6 @@ infoProfileCover: InfoProfileCover {
|
||||||
}
|
}
|
||||||
infoProfileMegagroupCover: InfoProfileCover(infoProfileCover) {
|
infoProfileMegagroupCover: InfoProfileCover(infoProfileCover) {
|
||||||
status: FlatLabel(infoProfileStatus) {
|
status: FlatLabel(infoProfileStatus) {
|
||||||
style: defaultTextStyle;
|
|
||||||
palette: TextPalette(defaultTextPalette) {
|
palette: TextPalette(defaultTextPalette) {
|
||||||
linkFg: windowSubTextFg;
|
linkFg: windowSubTextFg;
|
||||||
}
|
}
|
||||||
|
@ -427,8 +417,6 @@ infoBlockHeaderLabel: FlatLabel(infoProfileStatus) {
|
||||||
textFg: windowBoldFg;
|
textFg: windowBoldFg;
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: semiboldFont;
|
font: semiboldFont;
|
||||||
linkFont: semiboldFont;
|
|
||||||
linkFontOver: semiboldFont;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
infoBlockHeaderPosition: point(79px, 17px);
|
infoBlockHeaderPosition: point(79px, 17px);
|
||||||
|
@ -550,8 +538,6 @@ infoCommonGroupsListItem: PeerListItem(defaultPeerListItem) {
|
||||||
namePosition: point(71px, 15px);
|
namePosition: point(71px, 15px);
|
||||||
nameStyle: TextStyle(defaultTextStyle) {
|
nameStyle: TextStyle(defaultTextStyle) {
|
||||||
font: font(14px semibold);
|
font: font(14px semibold);
|
||||||
linkFont: font(14px semibold);
|
|
||||||
linkFontOver: font(14px semibold);
|
|
||||||
}
|
}
|
||||||
statusPosition: point(79px, 31px);
|
statusPosition: point(79px, 31px);
|
||||||
}
|
}
|
||||||
|
@ -582,12 +568,11 @@ manageGroupButtonInner: SettingsButton(infoProfileButton) {
|
||||||
}
|
}
|
||||||
manageGroupButton: SettingsCountButton(managePeerButton) {
|
manageGroupButton: SettingsCountButton(managePeerButton) {
|
||||||
button: manageGroupButtonInner;
|
button: manageGroupButtonInner;
|
||||||
labelPosition: point(22px, 12px);
|
labelPosition: point(22px, 10px);
|
||||||
iconPosition: point(20px, 4px);
|
iconPosition: point(20px, 4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
manageGroupTopButtonWithText: SettingsCountButton(manageGroupButton) {
|
manageGroupTopButtonWithText: SettingsCountButton(manageGroupButton) {
|
||||||
labelPosition: point(22px, 10px);
|
|
||||||
iconPosition: point(0px, 0px);
|
iconPosition: point(0px, 0px);
|
||||||
}
|
}
|
||||||
manageGroupTopicsButton: SettingsCountButton(manageGroupTopButtonWithText) {
|
manageGroupTopicsButton: SettingsCountButton(manageGroupTopButtonWithText) {
|
||||||
|
@ -773,7 +758,8 @@ topBarConnectingAnimation: InfiniteRadialAnimation(defaultInfiniteRadialAnimatio
|
||||||
size: size(8px, 8px);
|
size: size(8px, 8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
inviteLinkFieldHeight: 44px;
|
inviteLinkFieldRadius: 5px;
|
||||||
|
inviteLinkFieldHeight: 42px;
|
||||||
inviteLinkFieldMargin: margins(14px, 12px, 36px, 9px);
|
inviteLinkFieldMargin: margins(14px, 12px, 36px, 9px);
|
||||||
inviteLinkThreeDotsIcon: icon {{ "info/edit/dotsmini", dialogsMenuIconFg }};
|
inviteLinkThreeDotsIcon: icon {{ "info/edit/dotsmini", dialogsMenuIconFg }};
|
||||||
inviteLinkThreeDotsIconOver: icon {{ "info/edit/dotsmini", dialogsMenuIconFgOver }};
|
inviteLinkThreeDotsIconOver: icon {{ "info/edit/dotsmini", dialogsMenuIconFgOver }};
|
||||||
|
@ -788,10 +774,14 @@ inviteLinkThreeDots: IconButton(defaultIconButton) {
|
||||||
rippleAreaSize: 0px;
|
rippleAreaSize: 0px;
|
||||||
}
|
}
|
||||||
inviteLinkFieldPadding: margins(22px, 7px, 22px, 14px);
|
inviteLinkFieldPadding: margins(22px, 7px, 22px, 14px);
|
||||||
|
inviteLinkFieldLabel: FlatLabel(defaultFlatLabel) {
|
||||||
|
align: align(center);
|
||||||
|
}
|
||||||
|
|
||||||
inviteLinkButton: RoundButton(defaultActiveButton) {
|
inviteLinkButton: RoundButton(defaultActiveButton) {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
textTop: 9px;
|
textTop: 9px;
|
||||||
|
radius: 6px;
|
||||||
}
|
}
|
||||||
inviteLinkButtonsPadding: margins(22px, 0px, 22px, 0px);
|
inviteLinkButtonsPadding: margins(22px, 0px, 22px, 0px);
|
||||||
inviteLinkButtonsSkip: 10px;
|
inviteLinkButtonsSkip: 10px;
|
||||||
|
@ -925,8 +915,6 @@ shortInfoCover: ShortInfoCover {
|
||||||
maxHeight: 19px;
|
maxHeight: 19px;
|
||||||
style: TextStyle(defaultTextStyle) {
|
style: TextStyle(defaultTextStyle) {
|
||||||
font: font(15px semibold);
|
font: font(15px semibold);
|
||||||
linkFont: font(15px semibold);
|
|
||||||
linkFontOver: font(15px semibold underline);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
namePosition: point(25px, 37px);
|
namePosition: point(25px, 37px);
|
||||||
|
|
|
@ -339,6 +339,8 @@ Key ContentMemento::key() const {
|
||||||
return Settings::Tag{ self };
|
return Settings::Tag{ self };
|
||||||
} else if (const auto peer = storiesPeer()) {
|
} else if (const auto peer = storiesPeer()) {
|
||||||
return Stories::Tag{ peer, storiesTab() };
|
return Stories::Tag{ peer, storiesTab() };
|
||||||
|
} else if (const auto peer = statisticsPeer()) {
|
||||||
|
return Statistics::Tag{ peer, statisticsContextId() };
|
||||||
} else {
|
} else {
|
||||||
return Downloads::Tag();
|
return Downloads::Tag();
|
||||||
}
|
}
|
||||||
|
@ -375,4 +377,9 @@ ContentMemento::ContentMemento(Stories::Tag stories)
|
||||||
, _storiesTab(stories.tab) {
|
, _storiesTab(stories.tab) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContentMemento::ContentMemento(Statistics::Tag statistics)
|
||||||
|
: _statisticsPeer(statistics.peer)
|
||||||
|
, _statisticsContextId(statistics.contextId) {
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Info
|
} // namespace Info
|
||||||
|
|
|
@ -41,6 +41,10 @@ struct Tag;
|
||||||
enum class Tab;
|
enum class Tab;
|
||||||
} // namespace Info::Stories
|
} // namespace Info::Stories
|
||||||
|
|
||||||
|
namespace Info::Statistics {
|
||||||
|
struct Tag;
|
||||||
|
} // namespace Info::Statistics
|
||||||
|
|
||||||
namespace Info {
|
namespace Info {
|
||||||
|
|
||||||
class ContentMemento;
|
class ContentMemento;
|
||||||
|
@ -163,6 +167,7 @@ public:
|
||||||
explicit ContentMemento(Settings::Tag settings);
|
explicit ContentMemento(Settings::Tag settings);
|
||||||
explicit ContentMemento(Downloads::Tag downloads);
|
explicit ContentMemento(Downloads::Tag downloads);
|
||||||
explicit ContentMemento(Stories::Tag stories);
|
explicit ContentMemento(Stories::Tag stories);
|
||||||
|
explicit ContentMemento(Statistics::Tag statistics);
|
||||||
ContentMemento(not_null<PollData*> poll, FullMsgId contextId)
|
ContentMemento(not_null<PollData*> poll, FullMsgId contextId)
|
||||||
: _poll(poll)
|
: _poll(poll)
|
||||||
, _pollContextId(contextId) {
|
, _pollContextId(contextId) {
|
||||||
|
@ -191,6 +196,12 @@ public:
|
||||||
Stories::Tab storiesTab() const {
|
Stories::Tab storiesTab() const {
|
||||||
return _storiesTab;
|
return _storiesTab;
|
||||||
}
|
}
|
||||||
|
PeerData *statisticsPeer() const {
|
||||||
|
return _statisticsPeer;
|
||||||
|
}
|
||||||
|
FullMsgId statisticsContextId() const {
|
||||||
|
return _statisticsContextId;
|
||||||
|
}
|
||||||
PollData *poll() const {
|
PollData *poll() const {
|
||||||
return _poll;
|
return _poll;
|
||||||
}
|
}
|
||||||
|
@ -235,6 +246,8 @@ private:
|
||||||
UserData * const _settingsSelf = nullptr;
|
UserData * const _settingsSelf = nullptr;
|
||||||
PeerData * const _storiesPeer = nullptr;
|
PeerData * const _storiesPeer = nullptr;
|
||||||
Stories::Tab _storiesTab = {};
|
Stories::Tab _storiesTab = {};
|
||||||
|
PeerData * const _statisticsPeer = nullptr;
|
||||||
|
const FullMsgId _statisticsContextId;
|
||||||
PollData * const _poll = nullptr;
|
PollData * const _poll = nullptr;
|
||||||
const FullMsgId _pollContextId;
|
const FullMsgId _pollContextId;
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,9 @@ Key::Key(Downloads::Tag downloads) : _value(downloads) {
|
||||||
Key::Key(Stories::Tag stories) : _value(stories) {
|
Key::Key(Stories::Tag stories) : _value(stories) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Key::Key(Statistics::Tag statistics) : _value(statistics) {
|
||||||
|
}
|
||||||
|
|
||||||
Key::Key(not_null<PollData*> poll, FullMsgId contextId)
|
Key::Key(not_null<PollData*> poll, FullMsgId contextId)
|
||||||
: _value(PollKey{ poll, contextId }) {
|
: _value(PollKey{ poll, contextId }) {
|
||||||
}
|
}
|
||||||
|
@ -89,6 +92,20 @@ Stories::Tab Key::storiesTab() const {
|
||||||
return Stories::Tab();
|
return Stories::Tab();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PeerData *Key::statisticsPeer() const {
|
||||||
|
if (const auto tag = std::get_if<Statistics::Tag>(&_value)) {
|
||||||
|
return tag->peer;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FullMsgId Key::statisticsContextId() const {
|
||||||
|
if (const auto tag = std::get_if<Statistics::Tag>(&_value)) {
|
||||||
|
return tag->contextId;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
PollData *Key::poll() const {
|
PollData *Key::poll() const {
|
||||||
if (const auto data = std::get_if<PollKey>(&_value)) {
|
if (const auto data = std::get_if<PollKey>(&_value)) {
|
||||||
return data->poll;
|
return data->poll;
|
||||||
|
|
|
@ -55,6 +55,20 @@ struct Tag {
|
||||||
|
|
||||||
} // namespace Info::Stories
|
} // namespace Info::Stories
|
||||||
|
|
||||||
|
namespace Info::Statistics {
|
||||||
|
|
||||||
|
struct Tag {
|
||||||
|
explicit Tag(not_null<PeerData*> peer, FullMsgId contextId)
|
||||||
|
: peer(peer)
|
||||||
|
, contextId(contextId) {
|
||||||
|
}
|
||||||
|
|
||||||
|
not_null<PeerData*> peer;
|
||||||
|
FullMsgId contextId;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Info::Statistics
|
||||||
|
|
||||||
namespace Info {
|
namespace Info {
|
||||||
|
|
||||||
class Key {
|
class Key {
|
||||||
|
@ -64,6 +78,7 @@ public:
|
||||||
Key(Settings::Tag settings);
|
Key(Settings::Tag settings);
|
||||||
Key(Downloads::Tag downloads);
|
Key(Downloads::Tag downloads);
|
||||||
Key(Stories::Tag stories);
|
Key(Stories::Tag stories);
|
||||||
|
Key(Statistics::Tag statistics);
|
||||||
Key(not_null<PollData*> poll, FullMsgId contextId);
|
Key(not_null<PollData*> poll, FullMsgId contextId);
|
||||||
|
|
||||||
PeerData *peer() const;
|
PeerData *peer() const;
|
||||||
|
@ -72,6 +87,8 @@ public:
|
||||||
bool isDownloads() const;
|
bool isDownloads() const;
|
||||||
PeerData *storiesPeer() const;
|
PeerData *storiesPeer() const;
|
||||||
Stories::Tab storiesTab() const;
|
Stories::Tab storiesTab() const;
|
||||||
|
PeerData *statisticsPeer() const;
|
||||||
|
FullMsgId statisticsContextId() const;
|
||||||
PollData *poll() const;
|
PollData *poll() const;
|
||||||
FullMsgId pollContextId() const;
|
FullMsgId pollContextId() const;
|
||||||
|
|
||||||
|
@ -86,6 +103,7 @@ private:
|
||||||
Settings::Tag,
|
Settings::Tag,
|
||||||
Downloads::Tag,
|
Downloads::Tag,
|
||||||
Stories::Tag,
|
Stories::Tag,
|
||||||
|
Statistics::Tag,
|
||||||
PollKey> _value;
|
PollKey> _value;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -106,6 +124,8 @@ public:
|
||||||
Downloads,
|
Downloads,
|
||||||
Stories,
|
Stories,
|
||||||
PollResults,
|
PollResults,
|
||||||
|
Statistics,
|
||||||
|
Boosts,
|
||||||
};
|
};
|
||||||
using SettingsType = ::Settings::Type;
|
using SettingsType = ::Settings::Type;
|
||||||
using MediaType = Storage::SharedMediaType;
|
using MediaType = Storage::SharedMediaType;
|
||||||
|
@ -168,6 +188,12 @@ public:
|
||||||
[[nodiscard]] Stories::Tab storiesTab() const {
|
[[nodiscard]] Stories::Tab storiesTab() const {
|
||||||
return key().storiesTab();
|
return key().storiesTab();
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] PeerData *statisticsPeer() const {
|
||||||
|
return key().statisticsPeer();
|
||||||
|
}
|
||||||
|
[[nodiscard]] FullMsgId statisticsContextId() const {
|
||||||
|
return key().statisticsContextId();
|
||||||
|
}
|
||||||
[[nodiscard]] PollData *poll() const;
|
[[nodiscard]] PollData *poll() const;
|
||||||
[[nodiscard]] FullMsgId pollContextId() const {
|
[[nodiscard]] FullMsgId pollContextId() const {
|
||||||
return key().pollContextId();
|
return key().pollContextId();
|
||||||
|
|
|
@ -152,11 +152,16 @@ std::shared_ptr<ContentMemento> Memento::DefaultContent(
|
||||||
std::shared_ptr<ContentMemento> Memento::DefaultContent(
|
std::shared_ptr<ContentMemento> Memento::DefaultContent(
|
||||||
not_null<Data::ForumTopic*> topic,
|
not_null<Data::ForumTopic*> topic,
|
||||||
Section section) {
|
Section section) {
|
||||||
|
const auto peer = topic->peer();
|
||||||
|
const auto migrated = peer->migrateFrom();
|
||||||
|
const auto migratedPeerId = migrated ? migrated->id : PeerId(0);
|
||||||
switch (section.type()) {
|
switch (section.type()) {
|
||||||
case Section::Type::Profile:
|
case Section::Type::Profile:
|
||||||
return std::make_shared<Profile::Memento>(topic);
|
return std::make_shared<Profile::Memento>(topic);
|
||||||
case Section::Type::Media:
|
case Section::Type::Media:
|
||||||
return std::make_shared<Media::Memento>(topic, section.mediaType());
|
return std::make_shared<Media::Memento>(topic, section.mediaType());
|
||||||
|
case Section::Type::Members:
|
||||||
|
return std::make_shared<Members::Memento>(peer, migratedPeerId);
|
||||||
}
|
}
|
||||||
Unexpected("Wrong section type in Info::Memento::DefaultContent()");
|
Unexpected("Wrong section type in Info::Memento::DefaultContent()");
|
||||||
}
|
}
|
||||||
|
|
|
@ -250,7 +250,10 @@ Dialogs::RowDescriptor WrapWidget::activeChat() const {
|
||||||
storiesPeer->owner().history(storiesPeer),
|
storiesPeer->owner().history(storiesPeer),
|
||||||
FullMsgId())
|
FullMsgId())
|
||||||
: Dialogs::RowDescriptor();
|
: Dialogs::RowDescriptor();
|
||||||
} else if (key().settingsSelf() || key().isDownloads() || key().poll()) {
|
} else if (key().settingsSelf()
|
||||||
|
|| key().isDownloads()
|
||||||
|
|| key().poll()
|
||||||
|
|| key().statisticsPeer()) {
|
||||||
return Dialogs::RowDescriptor();
|
return Dialogs::RowDescriptor();
|
||||||
}
|
}
|
||||||
Unexpected("Owner in WrapWidget::activeChat().");
|
Unexpected("Owner in WrapWidget::activeChat().");
|
||||||
|
|
|
@ -76,6 +76,8 @@ Badge::Badge(
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Badge::~Badge() = default;
|
||||||
|
|
||||||
Ui::RpWidget *Badge::widget() const {
|
Ui::RpWidget *Badge::widget() const {
|
||||||
return _view.data();
|
return _view.data();
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,10 @@ class RpWidget;
|
||||||
class AbstractButton;
|
class AbstractButton;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Ui::Text {
|
||||||
|
class CustomEmoji;
|
||||||
|
} // namespace Ui::Text
|
||||||
|
|
||||||
namespace Info::Profile {
|
namespace Info::Profile {
|
||||||
|
|
||||||
class EmojiStatusPanel;
|
class EmojiStatusPanel;
|
||||||
|
@ -69,6 +73,8 @@ public:
|
||||||
base::flags<BadgeType> allowed
|
base::flags<BadgeType> allowed
|
||||||
= base::flags<BadgeType>::from_raw(-1));
|
= base::flags<BadgeType>::from_raw(-1));
|
||||||
|
|
||||||
|
~Badge();
|
||||||
|
|
||||||
[[nodiscard]] Ui::RpWidget *widget() const;
|
[[nodiscard]] Ui::RpWidget *widget() const;
|
||||||
|
|
||||||
void setPremiumClickCallback(Fn<void()> callback);
|
void setPremiumClickCallback(Fn<void()> callback);
|
||||||
|
|
|
@ -524,7 +524,7 @@ void Cover::refreshStatusText() {
|
||||||
_refreshStatusTimer.callOnce(updateIn);
|
_refreshStatusTimer.callOnce(updateIn);
|
||||||
}
|
}
|
||||||
return showOnline
|
return showOnline
|
||||||
? PlainLink(result)
|
? Ui::Text::Colorized(result)
|
||||||
: TextWithEntities{ .text = result };
|
: TextWithEntities{ .text = result };
|
||||||
} else if (auto chat = _peer->asChat()) {
|
} else if (auto chat = _peer->asChat()) {
|
||||||
if (!chat->amIn()) {
|
if (!chat->amIn()) {
|
||||||
|
@ -543,7 +543,7 @@ void Cover::refreshStatusText() {
|
||||||
onlineCount,
|
onlineCount,
|
||||||
channel->isMegagroup());
|
channel->isMegagroup());
|
||||||
return hasMembersLink
|
return hasMembersLink
|
||||||
? PlainLink(result)
|
? Ui::Text::Link(result)
|
||||||
: TextWithEntities{ .text = result };
|
: TextWithEntities{ .text = result };
|
||||||
}
|
}
|
||||||
return tr::lng_chat_status_unaccessible(tr::now, WithEntities);
|
return tr::lng_chat_status_unaccessible(tr::now, WithEntities);
|
||||||
|
|