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
This commit is contained in:
ZavaruKitsu 2023-10-21 22:04:45 +03:00
commit eff273dd7d
212 changed files with 10767 additions and 690 deletions

View file

@ -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: |

View file

@ -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
View file

@ -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

View file

@ -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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -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...";

View file

@ -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>

View file

@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop" <Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE" ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="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>

View file

@ -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"

View file

@ -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"

View 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

View 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

View file

@ -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;

View file

@ -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);

View file

@ -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);
} }

View file

@ -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;
} }

View file

@ -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 {

View file

@ -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);

View file

@ -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);
} }

View file

@ -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() {

View file

@ -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"
}
}; };
}; };

View file

@ -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 },
// //
}; };

View file

@ -59,6 +59,8 @@ enum class Command {
ReadChat, ReadChat,
MediaViewerFullscreen,
SupportReloadTemplates, SupportReloadTemplates,
SupportToggleMuted, SupportToggleMuted,
SupportScrollToCurrent, SupportScrollToCurrent,

View file

@ -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) {

View file

@ -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;

View file

@ -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;

View file

@ -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 {

View 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

View file

@ -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);

View file

@ -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>;

View file

@ -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));

View file

@ -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{}),
}; };

View file

@ -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 {

View file

@ -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)) {

View file

@ -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;

View 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

View 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

View 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

View file

@ -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,

View file

@ -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) {

View file

@ -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>;

View file

@ -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);
} }
} }

View file

@ -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,

View file

@ -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()

View file

@ -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) {

View file

@ -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);

View file

@ -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

View file

@ -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;

View file

@ -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) {

View file

@ -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);
} }

View file

@ -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 {

View file

@ -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;

View file

@ -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);
} }

View file

@ -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.;

View file

@ -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);

View file

@ -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()) {

View file

@ -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,
}); });
} }

View file

@ -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;

View file

@ -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),

View file

@ -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()

View file

@ -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);

View file

@ -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;

View file

@ -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),

View file

@ -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),

View file

@ -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;

View file

@ -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),

View file

@ -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<

View file

@ -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"

View file

@ -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),

View file

@ -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 {

View file

@ -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),

View file

@ -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)

View file

@ -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;

View file

@ -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

View 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

View 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

View 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

View 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

View file

@ -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);

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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();

View file

@ -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()");
} }

View file

@ -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().");

View file

@ -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();
} }

View file

@ -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);

View file

@ -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);

Some files were not shown because too many files have changed in this diff Show more