Nice tag badge click handler in groups.

This commit is contained in:
John Preston
2026-02-23 22:15:26 +04:00
parent 6d0ff399fb
commit c39c0b597c
13 changed files with 689 additions and 31 deletions
+2
View File
@@ -257,6 +257,8 @@ PRIVATE
boxes/peers/prepare_short_info_box.h boxes/peers/prepare_short_info_box.h
boxes/peers/replace_boost_box.cpp boxes/peers/replace_boost_box.cpp
boxes/peers/replace_boost_box.h 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.cpp
boxes/peers/verify_peers_box.h boxes/peers/verify_peers_box.h
boxes/about_box.cpp boxes/about_box.cpp
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="192px" height="192px" viewBox="0 0 192 192" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Other / Large / large_user_tag</title>
<g id="Other-/-Large-/-large_user_tag" stroke="none" fill="none" fill-rule="evenodd">
<path d="M134.779316,99.2776105 L155.861136,126.586165 C159.213567,130.92877 158.410871,137.166828 154.068266,140.519258 L137.349024,153.118955 C133.003707,156.393386 126.832804,155.562756 123.507963,151.255889 L102.341831,123.838121 C101.020315,122.126282 100.709294,119.840656 101.525344,117.837943 L107.904357,102.18287 C108.669944,100.304003 110.404748,98.9967346 112.421855,98.7786913 L129.321766,96.9518613 C131.421678,96.724867 133.488626,97.6057022 134.779316,99.2776105 Z M93.0045522,97.5 C96.0738449,97.5 99.0280025,97.6802939 101.867025,98.0408816 C101.658034,98.4287114 101.468568,98.8297452 101.299796,99.2430624 L94.927069,114.849633 C93.2728501,118.900753 93.7623745,123.498344 96.1864336,127.10949 L96.6969772,127.818112 L110.089,145.166 L55.2019123,145.166667 C50.0608762,145.166667 46.4337638,144.380785 44.3205752,142.809021 L44.1207992,142.654461 C42.0402664,140.979657 41,138.612081 41,135.551733 C41,131.543526 42.2098787,127.327177 44.6296361,122.902685 C47.0493935,118.478193 50.5360562,114.345377 55.0896242,110.504236 C59.6431923,106.663095 65.1152969,103.535437 71.5059383,101.121262 C77.8965797,98.7070875 85.0627843,97.5 93.0045522,97.5 Z M115.981148,108.01419 C113.77151,109.670379 113.331818,112.797477 114.999066,114.998781 C116.666338,117.200067 119.809193,117.641973 122.018852,115.98581 C124.22849,114.329621 124.668182,111.202523 123.000934,109.001219 C121.333662,106.799933 118.190807,106.358027 115.981148,108.01419 Z M93,86.6666667 C97.7764274,86.6666667 102.130651,85.4958452 106.062672,83.1542021 C109.994693,80.8125591 113.133919,77.6591033 115.480352,73.6938349 C117.826784,69.7285665 119,65.2736229 119,60.3290043 C119,55.562273 117.815549,51.2391277 115.446646,47.3595686 C113.077743,43.4800095 109.922365,40.394636 105.980514,38.1034483 C102.038662,35.8122605 97.7118241,34.6666667 93,34.6666667 C88.2881759,34.6666667 83.9615135,35.8242275 80.020013,38.1393492 C76.0785124,40.4544708 72.9231351,43.5619993 70.5538811,47.4619346 C68.184627,51.3618699 67,55.6835597 67,60.427004 C67.0196619,65.3062895 68.2027089,69.7285665 70.5491411,73.6938349 C72.8955734,77.6591033 76.0412953,80.8125591 79.9863069,83.1542021 C83.9313185,85.4958452 88.2692162,86.6666667 93,86.6666667 Z" id="Shape" fill="#FFFFFF"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

+14
View File
@@ -6021,6 +6021,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_rights_edit_tag_title" = "Edit tag"; "lng_rights_edit_tag_title" = "Edit tag";
"lng_rights_tag_about" = "Add short tag next to {name}'s name."; "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_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_promote_member" = "Promote to Admin";
"lng_rights_remove_member" = "Remove from Group"; "lng_rights_remove_member" = "Remove from Group";
"lng_rights_dismiss_admin" = "Dismiss Admin"; "lng_rights_dismiss_admin" = "Dismiss Admin";
@@ -51,12 +51,9 @@ std::vector<ChatParticipant> ParseList(
void ApplyMegagroupAdmins(not_null<ChannelData*> channel, Members list) { void ApplyMegagroupAdmins(not_null<ChannelData*> channel, Members list) {
Expects(channel->isMegagroup()); Expects(channel->isMegagroup());
const auto i = ranges::find_if(list, &Api::ChatParticipant::isCreator); const auto creatorIt = ranges::find_if(
if (i != list.end()) { list,
i->tryApplyCreatorTo(channel); &Api::ChatParticipant::isCreator);
} else {
channel->mgInfo->creator = nullptr;
}
auto adding = base::flat_set<UserId>(); auto adding = base::flat_set<UserId>();
auto addingRanks = base::flat_map<UserId, QString>(); auto addingRanks = base::flat_map<UserId, QString>();
@@ -68,13 +65,10 @@ void ApplyMegagroupAdmins(not_null<ChannelData*> channel, Members list) {
} }
} }
} }
if (channel->mgInfo->creator) { if (creatorIt != list.end() && creatorIt->isUser()) {
const auto creatorId = peerToUser(channel->mgInfo->creator->id); adding.emplace(creatorIt->userId());
adding.emplace(creatorId); if (!creatorIt->rank().isEmpty()) {
const auto r = channel->mgInfo->memberRanks.find(creatorId); addingRanks.emplace(creatorIt->userId(), creatorIt->rank());
if (r != channel->mgInfo->memberRanks.end()
&& !r->second.isEmpty()) {
addingRanks.emplace(creatorId, r->second);
} }
} }
auto removing = channel->mgInfo->admins; auto removing = channel->mgInfo->admins;
@@ -84,6 +78,11 @@ void ApplyMegagroupAdmins(not_null<ChannelData*> channel, Members list) {
} }
Data::ChannelAdminChanges changes(channel); Data::ChannelAdminChanges changes(channel);
if (creatorIt != list.end()) {
creatorIt->tryApplyCreatorTo(channel);
} else {
channel->mgInfo->creator = nullptr;
}
for (const auto &addingId : adding) { for (const auto &addingId : adding) {
const auto r = addingRanks.find(addingId); const auto r = addingRanks.find(addingId);
const auto rank = (r != end(addingRanks)) const auto rank = (r != end(addingRanks))
@@ -2080,8 +2080,7 @@ void ParticipantsBoxController::editRestrictedDone(
if (_role == Role::Restricted) { if (_role == Role::Restricted) {
prependRow(participant); prependRow(participant);
} else if (_role == Role::Kicked } else if (_role == Role::Kicked
|| _role == Role::Admins || _role == Role::Admins) {
|| _role == Role::Members) {
removeRow(participant); removeRow(participant);
} }
} }
@@ -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<Ui::RpWidget> MakeRoundColoredLogo(
not_null<QWidget*> 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<Ui::RpWidget>(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<Main::Session*> 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<Ui::ChatTheme> _theme;
std::unique_ptr<Ui::ChatStyle> _style;
BadgeRole _role = BadgeRole::User;
mutable QImage _leftCache;
mutable QImage _rightCache;
};
TagPreviewsWidget::TagPreviewsWidget(
QWidget *parent,
not_null<Main::Session*> session,
BadgeRole role)
: RpWidget(parent)
, _theme(Window::Theme::DefaultChatThemeOn(lifetime()))
, _style(std::make_unique<Ui::ChatStyle>(
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<PeerData*> 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<Ui::GenericBox*> box,
std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer,
not_null<PeerData*> 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<Ui::FlatLabel>(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<Ui::FlatLabel>(
box,
rpl::single(descText),
st::confcallLinkCenteredText,
st::defaultPopupMenu,
context),
st::boxRowPadding,
style::al_top);
desc->setTryMakeSimilarLines(true);
box->addRow(
object_ptr<TagPreviewsWidget>(
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<void(QString)>(nullptr)));
});
} else {
box->addRow(
object_ptr<Ui::FlatLabel>(
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))));
}
}
@@ -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<Ui::GenericBox*> box,
std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer,
not_null<PeerData*> author,
const QString &tagText,
HistoryView::BadgeRole role);
@@ -17,7 +17,8 @@ namespace Data {
ChannelAdminChanges::ChannelAdminChanges(not_null<ChannelData*> channel) ChannelAdminChanges::ChannelAdminChanges(not_null<ChannelData*> channel)
: _channel(channel) : _channel(channel)
, _admins(_channel->mgInfo->admins) { , _admins(_channel->mgInfo->admins)
, _oldCreator(_channel->mgInfo->creator) {
} }
void ChannelAdminChanges::add(UserId userId, const QString &rank) { void ChannelAdminChanges::add(UserId userId, const QString &rank) {
@@ -45,6 +46,15 @@ void ChannelAdminChanges::remove(UserId userId) {
} }
ChannelAdminChanges::~ChannelAdminChanges() { 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 if (_changes.size() > 1
|| (!_changes.empty() || (!_changes.empty()
&& _changes.front() != _channel->session().userId())) { && _changes.front() != _channel->session().userId())) {
@@ -22,6 +22,7 @@ private:
not_null<ChannelData*> _channel; not_null<ChannelData*> _channel;
base::flat_set<UserId> &_admins; base::flat_set<UserId> &_admins;
base::flat_set<UserId> _changes; base::flat_set<UserId> _changes;
UserData *_oldCreator = nullptr;
}; };
@@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "base/timer_rpl.h" #include "base/timer_rpl.h"
#include "api/api_bot.h" #include "api/api_bot.h"
#include "api/api_chat_participants.h"
#include "api/api_editing.h" #include "api/api_editing.h"
#include "api/api_sending.h" #include "api/api_sending.h"
#include "apiwrap.h" #include "apiwrap.h"
@@ -299,6 +300,11 @@ ChatWidget::ChatWidget(
setupTranslateBar(); setupTranslateBar();
_peer->updateFull(); _peer->updateFull();
if (const auto channel = _peer->asMegagroup()) {
if (!channel->mgInfo->adminsLoaded) {
session().api().chatParticipants().requestAdmins(channel);
}
}
refreshTopBarActiveChat(); refreshTopBarActiveChat();
@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h" #include "history/history.h"
#include "boxes/premium_preview_box.h" #include "boxes/premium_preview_box.h"
#include "boxes/share_box.h" #include "boxes/share_box.h"
#include "boxes/peers/tag_info_box.h"
#include "ui/effects/reaction_fly_animation.h" #include "ui/effects/reaction_fly_animation.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/text/text_extended_data.h" #include "ui/text/text_extended_data.h"
@@ -248,20 +249,27 @@ void Message::refreshRightBadge() {
if (const auto badge = Get<RightBadge>(); badge && badge->overridden) { if (const auto badge = Get<RightBadge>(); badge && badge->overridden) {
return; return;
} }
if (hasOutLayout()) {
if (Has<RightBadge>()) {
RemoveComponents(RightBadge::Bit());
}
return;
}
const auto item = data(); const auto item = data();
const auto [text, role] = [&]() -> std::pair<QString, BadgeRole> { const auto [text, role, special] = [&]() -> std::tuple<QString, BadgeRole, bool> {
if (item->isDiscussionPost()) { if (item->isDiscussionPost()) {
return { return {
(delegate()->elementContext() == Context::Replies) (delegate()->elementContext() == Context::Replies)
? QString() ? QString()
: tr::lng_channel_badge(tr::now), : tr::lng_channel_badge(tr::now),
BadgeRole::User, BadgeRole::User,
true,
}; };
} else if (item->author()->isMegagroup()) { } else if (item->author()->isMegagroup()) {
if (const auto msgsigned = item->Get<HistoryMessageSigned>()) { if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
if (!msgsigned->viaBusinessBot) { if (!msgsigned->viaBusinessBot) {
Assert(msgsigned->isAnonymousRank); 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) : chat->admins.contains(user)
? BadgeRole::Admin ? BadgeRole::Admin
: BadgeRole::User; : BadgeRole::User;
return { j->second, basicRole }; return { j->second, basicRole, false };
} }
} }
} }
return { QString(), BadgeRole::User }; return { QString(), BadgeRole::User, false };
} }
if (!user) { if (!user) {
return { QString(), BadgeRole::User }; return { QString(), BadgeRole::User, false };
} }
const auto info = channel->mgInfo.get(); const auto info = channel->mgInfo.get();
const auto userId = peerToUser(user->id); const auto userId = peerToUser(user->id);
@@ -298,18 +306,19 @@ void Message::refreshRightBadge() {
return { return {
r->second, r->second,
isCreator ? BadgeRole::Creator : BadgeRole::Admin, isCreator ? BadgeRole::Creator : BadgeRole::Admin,
false,
}; };
} }
if (isCreator) { 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(); const auto fromRank = item->fromRank();
if (!fromRank.isEmpty()) { if (!fromRank.isEmpty()) {
return { fromRank, BadgeRole::User }; return { fromRank, BadgeRole::User, false };
} }
return { QString(), BadgeRole::User }; return { QString(), BadgeRole::User, false };
}(); }();
auto tagText = TextWithEntities{ auto tagText = TextWithEntities{
(text.isEmpty() (text.isEmpty()
@@ -329,6 +338,8 @@ void Message::refreshRightBadge() {
} }
const auto badge = Get<RightBadge>(); const auto badge = Get<RightBadge>();
badge->role = role; badge->role = role;
badge->special = special || (text.isEmpty() && !tagText.empty());
badge->tagLink = nullptr;
if (tagText.empty()) { if (tagText.empty()) {
badge->tag.clear(); badge->tag.clear();
} else { } else {
@@ -2811,10 +2822,16 @@ bool Message::getStateFromName(
&& point.x() >= boostLeft && point.x() >= boostLeft
&& point.x() < badgeRight) { && point.x() < badgeRight) {
if (!badge->boostsLink) { if (!badge->boostsLink) {
badge->boostsLink = std::make_shared<LambdaClickHandler>([=]( const auto fullId = item->fullId();
ClickContext context) { badge->boostsLink = std::make_shared<LambdaClickHandler>([
fullId
](ClickContext context) {
if (const auto controller = ExtractController(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) ? (boostLeft - st::msgTagBadgeBoostSkip)
: badgeRight; : badgeRight;
if (point.x() >= badgeLeft && point.x() < tagRight) { if (point.x() >= badgeLeft && point.x() < tagRight) {
if (badge->special) {
return false;
}
if (!badge->tagLink) { if (!badge->tagLink) {
badge->tagLink = std::make_shared<LambdaClickHandler>([=]( const auto weak = base::make_weak(this);
ClickContext context) { badge->tagLink = std::make_shared<LambdaClickHandler>([
weak
](ClickContext context) {
if (const auto controller = ExtractController(context)) { if (const auto controller = ExtractController(context)) {
controller->showToast(u"Tag clicked"_q); if (const auto view = weak.get()) {
const auto badge = view->Get<RightBadge>();
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));
}
} }
}); });
} }
@@ -77,6 +77,7 @@ struct RightBadge : RuntimeComponent<RightBadge, Element> {
int width = 0; int width = 0;
BadgeRole role = BadgeRole::User; BadgeRole role = BadgeRole::User;
bool overridden = false; bool overridden = false;
bool special = false;
}; };
struct BottomRippleMask { struct BottomRippleMask {
+24
View File
@@ -25,6 +25,30 @@ msgPhotoSkip: 40px;
msgPadding: margins(11px, 8px, 11px, 8px); msgPadding: margins(11px, 8px, 11px, 8px);
msgTagBadgePadding: margins(5px, -1px, 5px, 0px); msgTagBadgePadding: margins(5px, -1px, 5px, 0px);
msgTagBadgeBoostSkip: 2px; 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); msgMargin: margins(16px, 6px, 56px, 2px);
msgMarginTopAttached: 0px; msgMarginTopAttached: 0px;
msgShadow: 2px; msgShadow: 2px;