diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index 0bcb434fdd..389bdb6fcd 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -257,6 +257,8 @@ PRIVATE
boxes/peers/prepare_short_info_box.h
boxes/peers/replace_boost_box.cpp
boxes/peers/replace_boost_box.h
+ boxes/peers/tag_info_box.cpp
+ boxes/peers/tag_info_box.h
boxes/peers/verify_peers_box.cpp
boxes/peers/verify_peers_box.h
boxes/about_box.cpp
diff --git a/Telegram/Resources/icons/chat/large_user_tag.svg b/Telegram/Resources/icons/chat/large_user_tag.svg
new file mode 100644
index 0000000000..ed97783eba
--- /dev/null
+++ b/Telegram/Resources/icons/chat/large_user_tag.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index a6d0a69823..72296389fd 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -6021,6 +6021,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_rights_edit_tag_title" = "Edit tag";
"lng_rights_tag_about" = "Add short tag next to {name}'s name.";
"lng_rights_tag_about_self" = "Share your role, title, or how you're known in this group. Your tag is visible to all members.";
+
+"lng_tag_info_title_user" = "Member Tags";
+"lng_tag_info_title_admin" = "Admin Tags";
+"lng_tag_info_title_owner" = "Owner Tags";
+"lng_tag_info_text_user" = "This grey tag {emoji} is {author}'s member tag in {group}.";
+"lng_tag_info_text_admin" = "This green tag {emoji} is {author}'s admin tag in {group}.";
+"lng_tag_info_text_owner" = "This purple tag {emoji} is {author}'s owner tag in {group}.";
+"lng_tag_info_preview_member" = "Member Tag";
+"lng_tag_info_preview_admin" = "Admin Tag";
+"lng_tag_info_preview_owner" = "Owner Tag";
+"lng_tag_info_add_my_tag" = "Add My Tag";
+"lng_tag_info_edit_my_tag" = "Edit My Tag";
+"lng_tag_info_admins_only" = "Only admins can change tags in this group.";
+
"lng_rights_promote_member" = "Promote to Admin";
"lng_rights_remove_member" = "Remove from Group";
"lng_rights_dismiss_admin" = "Dismiss Admin";
diff --git a/Telegram/SourceFiles/api/api_chat_participants.cpp b/Telegram/SourceFiles/api/api_chat_participants.cpp
index 904306929b..8900ca8dba 100644
--- a/Telegram/SourceFiles/api/api_chat_participants.cpp
+++ b/Telegram/SourceFiles/api/api_chat_participants.cpp
@@ -51,12 +51,9 @@ std::vector ParseList(
void ApplyMegagroupAdmins(not_null channel, Members list) {
Expects(channel->isMegagroup());
- const auto i = ranges::find_if(list, &Api::ChatParticipant::isCreator);
- if (i != list.end()) {
- i->tryApplyCreatorTo(channel);
- } else {
- channel->mgInfo->creator = nullptr;
- }
+ const auto creatorIt = ranges::find_if(
+ list,
+ &Api::ChatParticipant::isCreator);
auto adding = base::flat_set();
auto addingRanks = base::flat_map();
@@ -68,13 +65,10 @@ void ApplyMegagroupAdmins(not_null channel, Members list) {
}
}
}
- if (channel->mgInfo->creator) {
- const auto creatorId = peerToUser(channel->mgInfo->creator->id);
- adding.emplace(creatorId);
- const auto r = channel->mgInfo->memberRanks.find(creatorId);
- if (r != channel->mgInfo->memberRanks.end()
- && !r->second.isEmpty()) {
- addingRanks.emplace(creatorId, r->second);
+ if (creatorIt != list.end() && creatorIt->isUser()) {
+ adding.emplace(creatorIt->userId());
+ if (!creatorIt->rank().isEmpty()) {
+ addingRanks.emplace(creatorIt->userId(), creatorIt->rank());
}
}
auto removing = channel->mgInfo->admins;
@@ -84,6 +78,11 @@ void ApplyMegagroupAdmins(not_null channel, Members list) {
}
Data::ChannelAdminChanges changes(channel);
+ if (creatorIt != list.end()) {
+ creatorIt->tryApplyCreatorTo(channel);
+ } else {
+ channel->mgInfo->creator = nullptr;
+ }
for (const auto &addingId : adding) {
const auto r = addingRanks.find(addingId);
const auto rank = (r != end(addingRanks))
diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp
index 3a5a276690..f88326378f 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp
@@ -2080,8 +2080,7 @@ void ParticipantsBoxController::editRestrictedDone(
if (_role == Role::Restricted) {
prependRow(participant);
} else if (_role == Role::Kicked
- || _role == Role::Admins
- || _role == Role::Members) {
+ || _role == Role::Admins) {
removeRow(participant);
}
}
diff --git a/Telegram/SourceFiles/boxes/peers/tag_info_box.cpp b/Telegram/SourceFiles/boxes/peers/tag_info_box.cpp
new file mode 100644
index 0000000000..53b699a963
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/peers/tag_info_box.cpp
@@ -0,0 +1,531 @@
+/*
+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 "boxes/peers/tag_info_box.h"
+
+#include "boxes/peers/edit_participants_box.h"
+#include "boxes/peers/edit_tag_control.h"
+#include "data/data_channel.h"
+#include "data/data_chat.h"
+#include "data/data_chat_participant_status.h"
+#include "data/data_peer.h"
+#include "data/data_user.h"
+#include "history/view/history_view_message.h"
+#include "lang/lang_keys.h"
+#include "main/main_session.h"
+#include "ui/chat/chat_style.h"
+#include "ui/chat/chat_theme.h"
+#include "ui/layers/generic_box.h"
+#include "ui/painter.h"
+#include "ui/text/custom_emoji_helper.h"
+#include "ui/text/text_utilities.h"
+#include "ui/widgets/buttons.h"
+#include "ui/widgets/labels.h"
+#include "window/section_widget.h"
+#include "window/themes/window_theme.h"
+#include "styles/style_boxes.h"
+#include "styles/style_calls.h"
+#include "styles/style_chat.h"
+#include "styles/style_info.h"
+#include "styles/style_layers.h"
+#include "styles/style_widgets.h"
+
+namespace {
+
+using HistoryView::BadgeRole;
+
+constexpr auto kTextLinesAlpha = 0.1;
+
+[[nodiscard]] QColor RoleColor(BadgeRole role) {
+ return (role == BadgeRole::Creator)
+ ? st::rankOwnerFg->c
+ : (role == BadgeRole::Admin)
+ ? st::rankAdminFg->c
+ : st::rankUserFg->c;
+}
+
+[[nodiscard]] object_ptr MakeRoundColoredLogo(
+ not_null parent,
+ BadgeRole role) {
+ const auto &icon = st::tagInfoIcon;
+ const auto &padding = st::tagInfoIconPadding;
+ const auto logoSize = icon.size();
+ const auto logoOuter = logoSize.grownBy(padding);
+ auto result = object_ptr(parent);
+ const auto logo = result.data();
+ logo->resize(logo->width(), logoOuter.height());
+ logo->paintRequest() | rpl::on_next([=, &icon] {
+ if (logo->width() < logoOuter.width()) {
+ return;
+ }
+ auto p = QPainter(logo);
+ auto hq = PainterHighQualityEnabler(p);
+ const auto x = (logo->width() - logoOuter.width()) / 2;
+ const auto outer = QRect(QPoint(x, 0), logoOuter);
+ p.setBrush(RoleColor(role));
+ p.setPen(Qt::NoPen);
+ p.drawEllipse(outer);
+ icon.paintInCenter(p, outer);
+ }, logo->lifetime());
+ return result;
+}
+
+[[nodiscard]] Ui::Text::PaletteDependentEmoji MakeTagPillEmoji(
+ const QString &text,
+ BadgeRole role) {
+ return { .factory = [=] {
+ const auto color = RoleColor(role);
+ const auto &padding = st::msgTagBadgePadding;
+ auto string = Ui::Text::String(st::defaultTextStyle, text);
+ const auto textWidth = string.maxWidth();
+ const auto isUser = (role == BadgeRole::User);
+ const auto contentWidth = padding.left()
+ + textWidth
+ + padding.right();
+ const auto pillHeight = padding.top()
+ + st::msgFont->height
+ + padding.bottom();
+ const auto imgWidth = isUser
+ ? textWidth
+ : std::max(contentWidth, pillHeight);
+ const auto imgHeight = isUser
+ ? st::msgFont->height
+ : pillHeight;
+ const auto ratio = style::DevicePixelRatio();
+
+ auto result = QImage(
+ QSize(imgWidth, imgHeight) * ratio,
+ QImage::Format_ARGB32_Premultiplied);
+ result.setDevicePixelRatio(ratio);
+ result.fill(Qt::transparent);
+
+ auto p = QPainter(&result);
+ auto hq = PainterHighQualityEnabler(p);
+ if (!isUser) {
+ auto bgColor = color;
+ bgColor.setAlphaF(0.15);
+ p.setPen(Qt::NoPen);
+ p.setBrush(bgColor);
+ p.drawRoundedRect(
+ 0,
+ 0,
+ imgWidth,
+ imgHeight,
+ imgHeight / 2.,
+ imgHeight / 2.);
+ }
+ p.setPen(color);
+ string.draw(p, {
+ .position = QPoint(
+ isUser ? 0 : ((imgWidth - textWidth) / 2),
+ isUser ? 0 : padding.top()),
+ .availableWidth = textWidth,
+ });
+ p.end();
+ return result;
+ }, .margin = st::customEmojiTextBadgeMargin };
+}
+
+class TagPreviewsWidget final : public Ui::RpWidget {
+public:
+ TagPreviewsWidget(
+ QWidget *parent,
+ not_null session,
+ BadgeRole role);
+
+protected:
+ void paintEvent(QPaintEvent *e) override;
+
+private:
+ void paintPreview(
+ QPainter &p,
+ QRect rect,
+ BadgeRole previewRole) const;
+ void paintBubbleToImage(
+ QRect rect,
+ BadgeRole previewRole) const;
+ void invalidateCache();
+
+ const std::unique_ptr _theme;
+ std::unique_ptr _style;
+ BadgeRole _role = BadgeRole::User;
+ mutable QImage _leftCache;
+ mutable QImage _rightCache;
+
+};
+
+TagPreviewsWidget::TagPreviewsWidget(
+ QWidget *parent,
+ not_null session,
+ BadgeRole role)
+: RpWidget(parent)
+, _theme(Window::Theme::DefaultChatThemeOn(lifetime()))
+, _style(std::make_unique(
+ session->colorIndicesValue()))
+, _role(role) {
+ _style->apply(_theme.get());
+ resize(width(), st::tagInfoPreviewHeight);
+
+ _theme->repaintBackgroundRequests(
+ ) | rpl::on_next([=] {
+ invalidateCache();
+ update();
+ }, lifetime());
+
+ style::PaletteChanged() | rpl::on_next([=] {
+ invalidateCache();
+ update();
+ }, lifetime());
+
+ sizeValue() | rpl::skip(1) | rpl::on_next([=] {
+ invalidateCache();
+ }, lifetime());
+}
+
+void TagPreviewsWidget::invalidateCache() {
+ _leftCache = QImage();
+ _rightCache = QImage();
+}
+
+void TagPreviewsWidget::paintEvent(QPaintEvent *e) {
+ auto p = QPainter(this);
+
+ const auto gap = st::tagInfoPreviewGap;
+ const auto previewWidth = (width() - gap) / 2;
+ if (previewWidth <= 0) {
+ return;
+ }
+
+ const auto leftRect = QRect(0, 0, previewWidth, height());
+ const auto rightRect = QRect(
+ previewWidth + gap,
+ 0,
+ width() - previewWidth - gap,
+ height());
+
+ paintPreview(p, leftRect, BadgeRole::User);
+
+ const auto rightRole = (_role == BadgeRole::User)
+ ? BadgeRole::Admin
+ : _role;
+ paintPreview(p, rightRect, rightRole);
+}
+
+void TagPreviewsWidget::paintPreview(
+ QPainter &p,
+ QRect rect,
+ BadgeRole previewRole) const {
+ const auto previewRadius = st::tagInfoPreviewRadius;
+
+ p.save();
+ p.translate(rect.topLeft());
+
+ const auto local = QRect(0, 0, rect.width(), rect.height());
+ auto clipPath = QPainterPath();
+ clipPath.addRoundedRect(local, previewRadius, previewRadius);
+ p.setClipPath(clipPath);
+
+ Window::SectionWidget::PaintBackground(
+ p,
+ _theme.get(),
+ QSize(rect.width(), window()->height()),
+ local);
+
+ auto &cache = (previewRole == BadgeRole::User)
+ ? _leftCache
+ : _rightCache;
+ if (cache.isNull()) {
+ paintBubbleToImage(rect, previewRole);
+ }
+ p.drawImage(0, 0, cache);
+
+ p.restore();
+}
+
+void TagPreviewsWidget::paintBubbleToImage(
+ QRect rect,
+ BadgeRole previewRole) const {
+ const auto ratio = style::DevicePixelRatio();
+ auto &cache = (previewRole == BadgeRole::User)
+ ? _leftCache
+ : _rightCache;
+ cache = QImage(
+ rect.size() * ratio,
+ QImage::Format_ARGB32_Premultiplied);
+ cache.setDevicePixelRatio(ratio);
+ cache.fill(Qt::transparent);
+
+ const auto &stm = _style->messageStyle(false, false);
+ const auto &padding = st::tagInfoPreviewBubblePadding;
+ const auto radius = st::tagInfoPreviewBubbleRadius;
+ const auto rightMargin = st::tagInfoPreviewBubbleRightMargin;
+ const auto bubbleTop = padding.top();
+ const auto bubbleHeight = rect.height()
+ - padding.top()
+ - padding.bottom();
+ const auto bubbleRight = rect.width() - rightMargin;
+ const auto bubbleRect = QRect(
+ -radius,
+ bubbleTop,
+ bubbleRight + radius,
+ bubbleHeight);
+
+ auto p = QPainter(&cache);
+ {
+ auto hq = PainterHighQualityEnabler(p);
+ p.setPen(Qt::NoPen);
+ p.setBrush(stm.msgBg);
+ p.drawRoundedRect(bubbleRect, radius, radius);
+ }
+
+ const auto innerLeft = padding.left();
+ const auto innerRight = bubbleRight - padding.right();
+ const auto available = innerRight - innerLeft;
+
+ const auto badgeColor = RoleColor(previewRole);
+ const auto badgeText = (previewRole == BadgeRole::Creator)
+ ? tr::lng_tag_info_preview_owner(tr::now)
+ : (previewRole == BadgeRole::Admin)
+ ? tr::lng_tag_info_preview_admin(tr::now)
+ : tr::lng_tag_info_preview_member(tr::now);
+ auto badgeString = Ui::Text::String(st::defaultTextStyle, badgeText);
+ const auto badgeTextWidth = badgeString.maxWidth();
+ const auto &badgePadding = st::msgTagBadgePadding;
+ const auto badgeContentWidth = badgePadding.left()
+ + badgeTextWidth
+ + badgePadding.right();
+ const auto pillHeight = badgePadding.top()
+ + st::msgFont->height
+ + badgePadding.bottom();
+ const auto pillWidth = std::max(badgeContentWidth, pillHeight);
+
+ const auto badgeRight = innerRight;
+ const auto badgeLeft = badgeRight - pillWidth;
+ const auto badgeTop = bubbleTop + st::tagInfoPreviewBadgeTop;
+
+ if (previewRole != BadgeRole::User) {
+ auto bgColor = badgeColor;
+ bgColor.setAlphaF(0.15);
+ const auto pillRect = QRect(
+ badgeLeft,
+ badgeTop,
+ pillWidth,
+ pillHeight);
+ auto hq = PainterHighQualityEnabler(p);
+ p.setPen(Qt::NoPen);
+ p.setBrush(bgColor);
+ p.drawRoundedRect(pillRect, pillHeight / 2., pillHeight / 2.);
+ p.setPen(badgeColor);
+ badgeString.draw(p, {
+ .position = QPoint(
+ badgeLeft + (pillWidth - badgeTextWidth) / 2,
+ badgeTop + badgePadding.top()),
+ .availableWidth = badgeTextWidth,
+ });
+ } else if (badgeTextWidth > 0) {
+ p.setPen(st::rankUserFg);
+ badgeString.draw(p, {
+ .position = QPoint(innerRight - badgeTextWidth, badgeTop),
+ .availableWidth = badgeTextWidth,
+ });
+ }
+
+ p.setFont(st::msgDateFont);
+ const auto timeText = u"12:00"_q;
+ const auto timeWidth = st::msgDateFont->width(timeText);
+ const auto timeX = innerRight - timeWidth;
+ const auto timeY = bubbleTop
+ + bubbleHeight
+ - padding.bottom()
+ + st::msgDateFont->ascent
+ - st::msgDateFont->height;
+ p.setPen(stm.msgDateFg);
+ p.drawText(timeX, timeY, timeText);
+
+ {
+ auto color = stm.historyTextFg->c;
+ color.setAlphaF(color.alphaF() * kTextLinesAlpha);
+ p.setPen(Qt::NoPen);
+ p.setBrush(color);
+ auto hq = PainterHighQualityEnabler(p);
+
+ const auto lineHeight = st::tagInfoPreviewLineHeight;
+ const auto lineSpacing = st::tagInfoPreviewLineSpacing;
+ const auto linesTop = badgeTop
+ + pillHeight
+ + lineSpacing;
+ const auto lineRadius = lineHeight / 2.0;
+ const auto timeAreaLeft = timeX - padding.right();
+ const auto fractions = { 1.0, 0.65, 0.65 };
+ auto y = double(linesTop);
+ auto lineIndex = 0;
+ for (const auto fraction : fractions) {
+ auto w = available * fraction;
+ const auto lineBottom = y + lineHeight;
+ if (lineIndex >= 1 && lineBottom > (timeY - lineSpacing)) {
+ w = std::min(w, double(timeAreaLeft - innerLeft));
+ }
+ p.drawRoundedRect(
+ QRectF(innerLeft, y, w, lineHeight),
+ lineRadius,
+ lineRadius);
+ y += lineHeight + lineSpacing;
+ ++lineIndex;
+ }
+ }
+
+ const auto fadeWidth = st::tagInfoPreviewFadeWidth;
+ p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
+ auto gradient = QLinearGradient(0, 0, fadeWidth, 0);
+ gradient.setStops({
+ { 0., QColor(255, 255, 255, 0) },
+ { 1., QColor(255, 255, 255, 255) },
+ });
+ p.fillRect(0, 0, fadeWidth, rect.height(), gradient);
+ p.end();
+}
+
+[[nodiscard]] QString LookupCurrentRank(not_null peer) {
+ const auto selfId = peerToUser(peer->session().user()->id);
+ if (const auto channel = peer->asMegagroup()) {
+ if (const auto info = channel->mgInfo.get()) {
+ const auto it = info->memberRanks.find(selfId);
+ if (it != info->memberRanks.end()) {
+ return it->second;
+ }
+ }
+ } else if (const auto chat = peer->asChat()) {
+ const auto it = chat->memberRanks.find(selfId);
+ if (it != chat->memberRanks.end()) {
+ return it->second;
+ }
+ }
+ return QString();
+}
+
+} // namespace
+
+void TagInfoBox(
+ not_null box,
+ std::shared_ptr show,
+ not_null peer,
+ not_null author,
+ const QString &tagText,
+ HistoryView::BadgeRole role) {
+ box->setStyle(st::confcallJoinBox);
+ box->setWidth(st::boxWideWidth);
+ box->setNoContentMargin(true);
+ box->addTopButton(st::boxTitleClose, [=] {
+ box->closeBox();
+ });
+
+ box->addRow(
+ MakeRoundColoredLogo(box, role),
+ st::boxRowPadding + st::confcallLinkHeaderIconPadding);
+
+ auto title = (role == BadgeRole::Creator)
+ ? tr::lng_tag_info_title_owner()
+ : (role == BadgeRole::Admin)
+ ? tr::lng_tag_info_title_admin()
+ : tr::lng_tag_info_title_user();
+ box->addRow(
+ object_ptr(box, std::move(title), st::boxTitle),
+ st::boxRowPadding + st::confcallLinkTitlePadding,
+ style::al_top);
+
+ auto helper = Ui::Text::CustomEmojiHelper();
+ const auto tagPill = helper.paletteDependent(
+ MakeTagPillEmoji(tagText, role));
+ const auto authorName = author->shortName();
+ const auto groupName = peer->name();
+ const auto descText = (role == BadgeRole::Creator)
+ ? tr::lng_tag_info_text_owner(
+ tr::now,
+ lt_emoji,
+ tagPill,
+ lt_author,
+ tr::bold(authorName),
+ lt_group,
+ tr::bold(groupName),
+ tr::rich)
+ : (role == BadgeRole::Admin)
+ ? tr::lng_tag_info_text_admin(
+ tr::now,
+ lt_emoji,
+ tagPill,
+ lt_author,
+ tr::bold(authorName),
+ lt_group,
+ tr::bold(groupName),
+ tr::rich)
+ : tr::lng_tag_info_text_user(
+ tr::now,
+ lt_emoji,
+ tagPill,
+ lt_author,
+ tr::bold(authorName),
+ lt_group,
+ tr::bold(groupName),
+ tr::rich);
+ const auto context = helper.context();
+ const auto desc = box->addRow(
+ object_ptr(
+ box,
+ rpl::single(descText),
+ st::confcallLinkCenteredText,
+ st::defaultPopupMenu,
+ context),
+ st::boxRowPadding,
+ style::al_top);
+ desc->setTryMakeSimilarLines(true);
+
+ box->addRow(
+ object_ptr(
+ box,
+ &peer->session(),
+ role),
+ st::boxRowPadding + st::tagInfoPreviewPadding);
+
+ const auto selfUser = peer->session().user();
+ const auto selfRole = LookupBadgeRole(peer, selfUser);
+ const auto isAdmin = (selfRole != BadgeRole::User);
+ const auto canEditSelf = isAdmin
+ || !peer->amRestricted(ChatRestriction::EditRank);
+
+ if (canEditSelf) {
+ const auto currentRank = LookupCurrentRank(peer);
+ auto buttonText = currentRank.isEmpty()
+ ? tr::lng_tag_info_add_my_tag()
+ : tr::lng_tag_info_edit_my_tag();
+ box->addButton(std::move(buttonText), [=] {
+ box->closeBox();
+ show->show(Box(
+ EditCustomRankBox,
+ show,
+ peer,
+ selfUser,
+ currentRank,
+ true,
+ Fn(nullptr)));
+ });
+ } else {
+ box->addRow(
+ object_ptr(
+ box,
+ tr::lng_tag_info_admins_only(),
+ st::tagInfoAdminsOnlyLabel),
+ st::boxRowPadding + st::tagInfoAdminsOnlyPadding,
+ style::al_top);
+ box->addButton(
+ rpl::single(QString()),
+ [=] { box->closeBox(); }
+ )->setText(rpl::single(Ui::Text::IconEmoji(
+ &st::infoStarsUnderstood
+ ).append(' ').append(
+ tr::lng_stars_rating_understood(tr::now))));
+ }
+}
diff --git a/Telegram/SourceFiles/boxes/peers/tag_info_box.h b/Telegram/SourceFiles/boxes/peers/tag_info_box.h
new file mode 100644
index 0000000000..f54813eef0
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/peers/tag_info_box.h
@@ -0,0 +1,27 @@
+/*
+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 Ui {
+class GenericBox;
+class Show;
+} // namespace Ui
+
+namespace HistoryView {
+enum class BadgeRole : uchar;
+} // namespace HistoryView
+
+class PeerData;
+
+void TagInfoBox(
+ not_null box,
+ std::shared_ptr show,
+ not_null peer,
+ not_null author,
+ const QString &tagText,
+ HistoryView::BadgeRole role);
diff --git a/Telegram/SourceFiles/data/data_channel_admins.cpp b/Telegram/SourceFiles/data/data_channel_admins.cpp
index 157c532be1..49764162cc 100644
--- a/Telegram/SourceFiles/data/data_channel_admins.cpp
+++ b/Telegram/SourceFiles/data/data_channel_admins.cpp
@@ -17,7 +17,8 @@ namespace Data {
ChannelAdminChanges::ChannelAdminChanges(not_null channel)
: _channel(channel)
-, _admins(_channel->mgInfo->admins) {
+, _admins(_channel->mgInfo->admins)
+, _oldCreator(_channel->mgInfo->creator) {
}
void ChannelAdminChanges::add(UserId userId, const QString &rank) {
@@ -45,6 +46,15 @@ void ChannelAdminChanges::remove(UserId userId) {
}
ChannelAdminChanges::~ChannelAdminChanges() {
+ const auto creator = _channel->mgInfo->creator;
+ if (creator != _oldCreator) {
+ if (creator) {
+ _changes.emplace(peerToUser(creator->id));
+ }
+ if (_oldCreator) {
+ _changes.emplace(peerToUser(_oldCreator->id));
+ }
+ }
if (_changes.size() > 1
|| (!_changes.empty()
&& _changes.front() != _channel->session().userId())) {
diff --git a/Telegram/SourceFiles/data/data_channel_admins.h b/Telegram/SourceFiles/data/data_channel_admins.h
index b37f67d6ff..b97d3259f3 100644
--- a/Telegram/SourceFiles/data/data_channel_admins.h
+++ b/Telegram/SourceFiles/data/data_channel_admins.h
@@ -22,6 +22,7 @@ private:
not_null _channel;
base::flat_set &_admins;
base::flat_set _changes;
+ UserData *_oldCreator = nullptr;
};
diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp
index 10d809ff1c..8a8f8ac224 100644
--- a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp
@@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/ui_utility.h"
#include "base/timer_rpl.h"
#include "api/api_bot.h"
+#include "api/api_chat_participants.h"
#include "api/api_editing.h"
#include "api/api_sending.h"
#include "apiwrap.h"
@@ -299,6 +300,11 @@ ChatWidget::ChatWidget(
setupTranslateBar();
_peer->updateFull();
+ if (const auto channel = _peer->asMegagroup()) {
+ if (!channel->mgInfo->adminsLoaded) {
+ session().api().chatParticipants().requestAdmins(channel);
+ }
+ }
refreshTopBarActiveChat();
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index 6ffb0ec71b..61f5bc2f03 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "boxes/premium_preview_box.h"
#include "boxes/share_box.h"
+#include "boxes/peers/tag_info_box.h"
#include "ui/effects/reaction_fly_animation.h"
#include "ui/text/text_utilities.h"
#include "ui/text/text_extended_data.h"
@@ -248,20 +249,27 @@ void Message::refreshRightBadge() {
if (const auto badge = Get(); badge && badge->overridden) {
return;
}
+ if (hasOutLayout()) {
+ if (Has()) {
+ RemoveComponents(RightBadge::Bit());
+ }
+ return;
+ }
const auto item = data();
- const auto [text, role] = [&]() -> std::pair {
+ const auto [text, role, special] = [&]() -> std::tuple {
if (item->isDiscussionPost()) {
return {
(delegate()->elementContext() == Context::Replies)
? QString()
: tr::lng_channel_badge(tr::now),
BadgeRole::User,
+ true,
};
} else if (item->author()->isMegagroup()) {
if (const auto msgsigned = item->Get()) {
if (!msgsigned->viaBusinessBot) {
Assert(msgsigned->isAnonymousRank);
- return { msgsigned->author, BadgeRole::User };
+ return { msgsigned->author, BadgeRole::User, false };
}
}
}
@@ -279,14 +287,14 @@ void Message::refreshRightBadge() {
: chat->admins.contains(user)
? BadgeRole::Admin
: BadgeRole::User;
- return { j->second, basicRole };
+ return { j->second, basicRole, false };
}
}
}
- return { QString(), BadgeRole::User };
+ return { QString(), BadgeRole::User, false };
}
if (!user) {
- return { QString(), BadgeRole::User };
+ return { QString(), BadgeRole::User, false };
}
const auto info = channel->mgInfo.get();
const auto userId = peerToUser(user->id);
@@ -298,18 +306,19 @@ void Message::refreshRightBadge() {
return {
r->second,
isCreator ? BadgeRole::Creator : BadgeRole::Admin,
+ false,
};
}
if (isCreator) {
- return { tr::lng_owner_badge(tr::now), BadgeRole::Creator };
+ return { tr::lng_owner_badge(tr::now), BadgeRole::Creator, false };
}
- return { tr::lng_admin_badge(tr::now), BadgeRole::Admin };
+ return { tr::lng_admin_badge(tr::now), BadgeRole::Admin, false };
}
const auto fromRank = item->fromRank();
if (!fromRank.isEmpty()) {
- return { fromRank, BadgeRole::User };
+ return { fromRank, BadgeRole::User, false };
}
- return { QString(), BadgeRole::User };
+ return { QString(), BadgeRole::User, false };
}();
auto tagText = TextWithEntities{
(text.isEmpty()
@@ -329,6 +338,8 @@ void Message::refreshRightBadge() {
}
const auto badge = Get();
badge->role = role;
+ badge->special = special || (text.isEmpty() && !tagText.empty());
+ badge->tagLink = nullptr;
if (tagText.empty()) {
badge->tag.clear();
} else {
@@ -2811,10 +2822,16 @@ bool Message::getStateFromName(
&& point.x() >= boostLeft
&& point.x() < badgeRight) {
if (!badge->boostsLink) {
- badge->boostsLink = std::make_shared([=](
- ClickContext context) {
+ const auto fullId = item->fullId();
+ badge->boostsLink = std::make_shared([
+ fullId
+ ](ClickContext context) {
if (const auto controller = ExtractController(context)) {
- controller->showToast(u"Boosts clicked"_q);
+ if (const auto item = controller->session().data().message(fullId)) {
+ if (const auto channel = item->history()->peer->asChannel()) {
+ controller->resolveBoostState(channel);
+ }
+ }
}
});
}
@@ -2825,11 +2842,31 @@ bool Message::getStateFromName(
? (boostLeft - st::msgTagBadgeBoostSkip)
: badgeRight;
if (point.x() >= badgeLeft && point.x() < tagRight) {
+ if (badge->special) {
+ return false;
+ }
if (!badge->tagLink) {
- badge->tagLink = std::make_shared([=](
- ClickContext context) {
+ const auto weak = base::make_weak(this);
+ badge->tagLink = std::make_shared([
+ weak
+ ](ClickContext context) {
if (const auto controller = ExtractController(context)) {
- controller->showToast(u"Tag clicked"_q);
+ if (const auto view = weak.get()) {
+ const auto badge = view->Get();
+ if (!badge) {
+ return;
+ }
+ const auto item = view->data();
+ const auto peer = item->history()->peer;
+ const auto author = item->author();
+ controller->uiShow()->show(Box(
+ TagInfoBox,
+ controller->uiShow(),
+ peer,
+ author,
+ badge->tag.toString(),
+ badge->role));
+ }
}
});
}
diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h
index d7768b2086..8f69238af7 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.h
+++ b/Telegram/SourceFiles/history/view/history_view_message.h
@@ -77,6 +77,7 @@ struct RightBadge : RuntimeComponent {
int width = 0;
BadgeRole role = BadgeRole::User;
bool overridden = false;
+ bool special = false;
};
struct BottomRippleMask {
diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style
index c983f8cf90..6a42ed7b89 100644
--- a/Telegram/SourceFiles/ui/chat/chat.style
+++ b/Telegram/SourceFiles/ui/chat/chat.style
@@ -25,6 +25,30 @@ msgPhotoSkip: 40px;
msgPadding: margins(11px, 8px, 11px, 8px);
msgTagBadgePadding: margins(5px, -1px, 5px, 0px);
msgTagBadgeBoostSkip: 2px;
+
+tagInfoIcon: icon {{ "chat/large_user_tag-48x48", windowFgActive }};
+tagInfoIconPadding: margins(8px, 8px, 8px, 8px);
+tagInfoPreviewHeight: 80px;
+tagInfoPreviewGap: 8px;
+tagInfoPreviewBubbleRadius: 8px;
+tagInfoPreviewLineHeight: 6px;
+tagInfoPreviewLineSpacing: 4px;
+tagInfoPreviewBubblePadding: margins(10px, 8px, 10px, 8px);
+tagInfoPreviewRadius: 8px;
+tagInfoPreviewBubbleRightMargin: 10px;
+tagInfoPreviewFadeWidth: 24px;
+tagInfoPreviewBadgeTop: 6px;
+tagInfoPreviewPadding: margins(0px, 16px, 0px, 0px);
+tagInfoAdminsOnlyPadding: margins(0px, 8px, 0px, 0px);
+tagInfoAdminsOnlyLabel: FlatLabel(defaultFlatLabel) {
+ align: align(top);
+ minWidth: 40px;
+ textFg: windowSubTextFg;
+ style: TextStyle(defaultTextStyle) {
+ font: font(12px);
+ }
+}
+
msgMargin: margins(16px, 6px, 56px, 2px);
msgMarginTopAttached: 0px;
msgShadow: 2px;