diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 513390ae49..06c7f6f8b3 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -228,6 +228,8 @@ PRIVATE boxes/peers/edit_members_visible.h boxes/peers/edit_participant_box.cpp boxes/peers/edit_participant_box.h + boxes/peers/edit_tag_control.cpp + boxes/peers/edit_tag_control.h boxes/peers/edit_participants_box.cpp boxes/peers/edit_participants_box.h boxes/peers/edit_peer_color_box.cpp diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index bcac6f7eaf..215d198105 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -852,6 +852,10 @@ customBadgeField: InputField(defaultInputField) { heightMin: 32px; } +tagPreviewLineHeight: 8px; +tagPreviewLineSpacing: 4px; +tagPreviewInputSkip: 16px; + pollResultsQuestion: FlatLabel(defaultFlatLabel) { minWidth: 320px; textFg: windowBoldFg; diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp index 018ea98b65..c0d721f1ee 100644 --- a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp @@ -1582,6 +1582,7 @@ void AddSpecialBoxController::showRestricted( user, _additional.adminRights(user).has_value(), currentRights, + _additional.memberRank(user), _additional.restrictedBy(user), _additional.restrictedSince(user)); if (_additional.canRestrictParticipant(user)) { diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp index 1840710f69..37b749a1aa 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp @@ -7,6 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/peers/edit_participant_box.h" +#include "boxes/peers/edit_tag_control.h" +#include "boxes/peers/edit_participants_box.h" +#include "history/view/history_view_message.h" #include "lang/lang_keys.h" #include "ui/controls/userpic_button.h" #include "ui/vertical_list.h" @@ -47,7 +50,64 @@ namespace { constexpr auto kMaxRestrictDelayDays = 366; constexpr auto kSecondsInDay = 24 * 60 * 60; constexpr auto kSecondsInWeek = 7 * kSecondsInDay; -constexpr auto kAdminRoleLimit = 16; + +class Cover final : public Ui::FixedHeightWidget { +public: + Cover(QWidget *parent, not_null user, bool hasAdminRights); + +private: + void setupChildGeometry(); + + const style::InfoProfileCover &_st; + const not_null _user; + object_ptr _userpic; + object_ptr _name = { nullptr }; + object_ptr _status = { nullptr }; + +}; + +Cover::Cover( + QWidget *parent, + not_null user, + bool hasAdminRights) +: FixedHeightWidget(parent, st::infoEditContactCover.height) +, _st(st::infoEditContactCover) +, _user(user) +, _userpic(this, _user, _st.photo) { + _userpic->setAttribute(Qt::WA_TransparentForMouseEvents); + + _name = object_ptr(this, _st.name); + _name->setText(_user->name()); + + const auto statusText = [&] { + if (_user->isBot()) { + const auto seesAllMessages = _user->botInfo->readsAllHistory + || hasAdminRights; + return (seesAllMessages + ? tr::lng_status_bot_reads_all + : tr::lng_status_bot_not_reads_all)(tr::now); + } + return Data::OnlineText(_user->lastseen(), base::unixtime::now()); + }(); + _status = object_ptr(this, _st.status); + _status->setText(statusText); + _status->setAttribute(Qt::WA_TransparentForMouseEvents); + + setupChildGeometry(); +} + +void Cover::setupChildGeometry() { + widthValue( + ) | rpl::on_next([this](int newWidth) { + _userpic->moveToLeft(_st.photoLeft, _st.photoTop, newWidth); + auto nameWidth = newWidth - _st.nameLeft - _st.rightSkip; + _name->resizeToNaturalWidth(nameWidth); + _name->moveToLeft(_st.nameLeft, _st.nameTop, newWidth); + auto statusWidth = newWidth - _st.statusLeft - _st.rightSkip; + _status->resizeToNaturalWidth(statusWidth); + _status->moveToLeft(_st.statusLeft, _st.statusTop, newWidth); + }, lifetime()); +} } // namespace @@ -66,6 +126,8 @@ public: return _rows; } + void setCoverMode(bool enabled); + protected: int resizeGetHeight(int newWidth) override; void paintEvent(QPaintEvent *e) override; @@ -76,6 +138,7 @@ private: object_ptr _userPhoto; Ui::Text::String _userName; bool _hasAdminRights = false; + bool _coverMode = false; object_ptr _rows; }; @@ -110,7 +173,20 @@ Widget *EditParticipantBox::Inner::addControl( return _rows->add(std::move(widget), margin); } +void EditParticipantBox::Inner::setCoverMode(bool enabled) { + _coverMode = enabled; + if (enabled) { + _userPhoto->hide(); + } + resizeToWidth(width()); +} + int EditParticipantBox::Inner::resizeGetHeight(int newWidth) { + if (_coverMode) { + _rows->resizeToWidth(newWidth); + _rows->moveToLeft(0, 0, newWidth); + return _rows->heightNoMargins(); + } _userPhoto->moveToLeft( st::rightsPhotoMargin.left(), st::rightsPhotoMargin.top()); @@ -127,6 +203,10 @@ void EditParticipantBox::Inner::paintEvent(QPaintEvent *e) { p.fillRect(e->rect(), st::boxBg); + if (_coverMode) { + return; + } + p.setPen(st::contactsNameFg); auto namex = st::rightsPhotoMargin.left() + st::rightsPhotoButton.size .width() @@ -198,6 +278,12 @@ bool EditParticipantBox::amCreator() const { Unexpected("Peer type in EditParticipantBox::Inner::amCreator."); } +void EditParticipantBox::setCoverMode(bool enabled) { + Expects(_inner != nullptr); + + _inner->setCoverMode(enabled); +} + EditAdminBox::EditAdminBox( QWidget*, not_null peer, @@ -255,6 +341,11 @@ void EditAdminBox::prepare() { EditParticipantBox::prepare(); + setCoverMode(true); + addControl( + object_ptr(this, user(), hasAdminRights()), + style::margins()); + setTitle(_addingBot ? (_addingBot->existing ? tr::lng_rights_edit_admin() @@ -433,7 +524,17 @@ void EditAdminBox::prepare() { } if (canSave()) { - _rank = hasRank ? addRankInput(inner).get() : nullptr; + if (hasRank) { + const auto role = LookupBadgeRole(peer(), user()); + _tagControl = inner->add( + object_ptr( + inner, + &peer()->session(), + user(), + _oldRank, + role), + style::margins()); + } _finishSave = [=, value = getChecked] { const auto newFlags = (value() | ChatAdminRight::Other) & ((!channel || channel->amCreator()) @@ -442,7 +543,9 @@ void EditAdminBox::prepare() { _saveCallback( _oldRights, ChatAdminRightsInfo(newFlags), - _rank ? _rank->getLastText().trimmed() : QString()); + _tagControl + ? _tagControl->currentRank() + : QString()); }; _save = [=] { const auto show = uiShow(); @@ -497,56 +600,6 @@ void EditAdminBox::refreshButtons() { } } -not_null EditAdminBox::addRankInput( - not_null container) { - // Ui::AddDivider(container); - - container->add( - object_ptr( - container, - tr::lng_rights_edit_admin_rank_name(), - st::rightsHeaderLabel), - st::rightsHeaderMargin); - - const auto isOwner = [&] { - if (user()->isSelf() && amCreator()) { - return true; - } else if (const auto chat = peer()->asChat()) { - return chat->creator == peerToUser(user()->id); - } else if (const auto channel = peer()->asChannel()) { - return channel->mgInfo && channel->mgInfo->creator == user(); - } - Unexpected("Peer type in EditAdminBox::addRankInput."); - }(); - const auto result = container->add( - object_ptr( - container, - st::customBadgeField, - (isOwner ? tr::lng_owner_badge : tr::lng_admin_badge)(), - TextUtilities::RemoveEmoji(_oldRank)), - st::rightsAboutMargin); - result->setMaxLength(kAdminRoleLimit); - result->setInstantReplaces(Ui::InstantReplaces::TextOnly()); - result->changes( - ) | rpl::on_next([=] { - const auto text = result->getLastText(); - const auto removed = TextUtilities::RemoveEmoji(text); - if (removed != text) { - result->setText(removed); - } - }, result->lifetime()); - - Ui::AddSkip(container); - Ui::AddDividerText( - container, - tr::lng_rights_edit_admin_rank_about( - lt_title, - (isOwner ? tr::lng_owner_badge : tr::lng_admin_badge)())); - Ui::AddSkip(container); - - return result; -} - bool EditAdminBox::canTransferOwnership() const { if (user()->isInaccessible() || user()->isBot() || user()->isSelf()) { return false; @@ -599,10 +652,12 @@ EditRestrictedBox::EditRestrictedBox( not_null user, bool hasAdminRights, ChatRestrictionsInfo rights, + const QString &rank, UserData *by, TimeId since) : EditParticipantBox(nullptr, peer, user, hasAdminRights) , _oldRights(rights) +, _oldRank(rank) , _by(by) , _since(since) { } @@ -613,6 +668,11 @@ void EditRestrictedBox::prepare() { EditParticipantBox::prepare(); + setCoverMode(true); + addControl( + object_ptr(this, user(), hasAdminRights()), + style::margins()); + setTitle(tr::lng_rights_user_restrictions()); Ui::AddDivider(verticalLayout()); @@ -661,6 +721,17 @@ void EditRestrictedBox::prepare() { { .isForum = peer()->isForum() }); addControl(std::move(checkboxes), QMargins()); + if (canSave() && peer()->canManageRanks()) { + _tagControl = addControl( + object_ptr( + this, + &peer()->session(), + user(), + _oldRank, + HistoryView::BadgeRole::User), + style::margins()); + } + _until = prepareRights.until; addControl( object_ptr(this, st::defaultVerticalListSkip)); @@ -710,6 +781,18 @@ void EditRestrictedBox::prepare() { _saveCallback( _oldRights, ChatRestrictionsInfo{ value(), getRealUntilValue() }); + if (_tagControl) { + const auto rank = _tagControl->currentRank(); + if (rank != _oldRank) { + SaveMemberRank( + uiShow(), + peer(), + user(), + rank, + nullptr, + nullptr); + } + } }; addButton(tr::lng_settings_save(), save); addButton(tr::lng_cancel(), [=] { closeBox(); }); diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.h b/Telegram/SourceFiles/boxes/peers/edit_participant_box.h index 79f9f6294b..a791efeb3c 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.h @@ -23,6 +23,7 @@ class SlideWrap; } // namespace Ui class ChannelOwnershipTransfer; +class EditTagControl; class EditParticipantBox : public Ui::BoxContent { public: @@ -48,6 +49,8 @@ protected: template Widget *addControl(object_ptr widget, QMargins margin = {}); + void setCoverMode(bool enabled); + bool hasAdminRights() const { return _hasAdminRights; } @@ -93,8 +96,6 @@ protected: private: [[nodiscard]] ChatAdminRightsInfo defaultRights() const; - not_null addRankInput( - not_null container); void transferOwnership(); bool canSave() const { return _saveCallback != nullptr; @@ -116,7 +117,7 @@ private: base::weak_qptr _confirmBox; Ui::Checkbox *_addAsAdmin = nullptr; Ui::SlideWrap *_adminControlsWrap = nullptr; - Ui::InputField *_rank = nullptr; + EditTagControl *_tagControl = nullptr; Fn _save, _finishSave; @@ -139,6 +140,7 @@ public: not_null user, bool hasAdminRights, ChatRestrictionsInfo rights, + const QString &rank, UserData *by, TimeId since); @@ -164,6 +166,8 @@ private: TimeId getRealUntilValue() const; const ChatRestrictionsInfo _oldRights; + const QString _oldRank; + EditTagControl *_tagControl = nullptr; UserData *_by = nullptr; TimeId _since = 0; TimeId _until = 0; diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp index 8973c0835d..d09a97af6a 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_participants.h" #include "boxes/peers/edit_participant_box.h" +#include "boxes/peers/edit_tag_control.h" #include "boxes/peers/add_participants_box.h" #include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox #include "boxes/peers/edit_members_visible.h" @@ -39,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/profile/info_profile_values.h" #include "window/window_session_controller.h" #include "history/history.h" +#include "history/view/history_view_message.h" #include "styles/style_boxes.h" #include "styles/style_chat.h" #include "styles/style_menu_icons.h" @@ -172,6 +174,29 @@ void SaveChannelAdmin( }).send(); } +void SaveChatParticipantKick( + not_null chat, + not_null user, + Fn onDone, + Fn onFail) { + chat->session().api().request(MTPmessages_DeleteChatUser( + MTP_flags(0), + chat->inputChat(), + user->inputUser() + )).done([=](const MTPUpdates &result) { + chat->session().api().applyUpdates(result); + if (onDone) { + onDone(); + } + }).fail([=] { + if (onFail) { + onFail(); + } + }).send(); +} + +} // namespace + void SaveMemberRank( std::shared_ptr show, not_null peer, @@ -222,29 +247,6 @@ void SaveMemberRank( }).send(); } -void SaveChatParticipantKick( - not_null chat, - not_null user, - Fn onDone, - Fn onFail) { - chat->session().api().request(MTPmessages_DeleteChatUser( - MTP_flags(0), - chat->inputChat(), - user->inputUser() - )).done([=](const MTPUpdates &result) { - chat->session().api().applyUpdates(result); - if (onDone) { - onDone(); - } - }).fail([=] { - if (onFail) { - onFail(); - } - }).send(); -} - -} // namespace - Fn user) { user, hasAdminRights, currentRights, + _additional.memberRank(user), _additional.restrictedBy(user), _additional.restrictedSince(user)); if (_additional.canRestrictParticipant(user)) { @@ -2662,7 +2665,6 @@ void EditCustomRankBox( const QString ¤tRank, bool isSelf, Fn onSaved) { - constexpr auto kRankLimit = 16; struct State { bool saving = false; }; @@ -2677,21 +2679,16 @@ void EditCustomRankBox( ? tr::lng_context_edit_member_tag() : tr::lng_context_add_member_tag())); - const auto field = box->addRow(object_ptr( - box, - st::customBadgeField, - tr::lng_rights_edit_admin_rank_name(), - TextUtilities::RemoveEmoji(currentRank))); - field->setMaxLength(kRankLimit); - field->setInstantReplaces(Ui::InstantReplaces::TextOnly()); - field->changes( - ) | rpl::on_next([=] { - const auto text = field->getLastText(); - const auto removed = TextUtilities::RemoveEmoji(text); - if (removed != text) { - field->setText(removed); - } - }, field->lifetime()); + const auto role = LookupBadgeRole(peer, user); + const auto control = box->addRow( + object_ptr( + box, + &peer->session(), + user, + currentRank, + role), + style::margins()); + const auto field = control->field(); box->setFocusCallback([=] { field->setFocusFast(); }); @@ -2701,8 +2698,7 @@ void EditCustomRankBox( return; } state->saving = true; - const auto rank = TextUtilities::RemoveEmoji( - TextUtilities::SingleLine(field->getLastText().trimmed())); + const auto rank = control->currentRank(); SaveMemberRank( show, peer, diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.h b/Telegram/SourceFiles/boxes/peers/edit_participants_box.h index 0a10b24538..e1599db0ea 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.h @@ -58,6 +58,14 @@ void EditCustomRankBox( bool isSelf, Fn onSaved); +void SaveMemberRank( + std::shared_ptr show, + not_null peer, + not_null user, + const QString &rank, + Fn onDone, + Fn onFail); + void SubscribeToMigration( not_null peer, rpl::lifetime &lifetime, diff --git a/Telegram/SourceFiles/boxes/peers/edit_tag_control.cpp b/Telegram/SourceFiles/boxes/peers/edit_tag_control.cpp new file mode 100644 index 0000000000..1e3e24f43a --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/edit_tag_control.cpp @@ -0,0 +1,401 @@ +/* +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/edit_tag_control.h" + +#include "base/unixtime.h" +#include "data/data_channel.h" +#include "data/data_chat.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "history/admin_log/history_admin_log_item.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_message.h" +#include "history/view/media/history_view_media_generic.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/effects/path_shift_gradient.h" +#include "ui/painter.h" +#include "ui/text/text_utilities.h" +#include "ui/widgets/fields/input_field.h" +#include "window/section_widget.h" +#include "window/themes/window_theme.h" +#include "styles/style_boxes.h" +#include "styles/style_chat.h" +#include "styles/style_layers.h" + +namespace { + +constexpr auto kRankLimit = 16; +constexpr auto kTextLinesAlpha = 0.1; + +using namespace HistoryView; + +class TextLinesPart final : public MediaGenericPart { +public: + TextLinesPart(QMargins margins); + + void draw( + Painter &p, + not_null owner, + const PaintContext &context, + int outerWidth) const override; + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + +private: + QMargins _margins; + +}; + +TextLinesPart::TextLinesPart(QMargins margins) +: _margins(margins) { +} + +QSize TextLinesPart::countOptimalSize() { + const auto h = _margins.top() + + 4 * st::tagPreviewLineHeight + + 3 * st::tagPreviewLineSpacing + + _margins.bottom(); + return { st::msgMinWidth, h }; +} + +QSize TextLinesPart::countCurrentSize(int newWidth) { + return { newWidth, minHeight() }; +} + +void TextLinesPart::draw( + Painter &p, + not_null owner, + const PaintContext &context, + int outerWidth) const { + const auto &stm = context.messageStyle(); + auto color = stm->historyTextFg->c; + color.setAlphaF(color.alphaF() * kTextLinesAlpha); + p.setPen(Qt::NoPen); + p.setBrush(color); + auto hq = PainterHighQualityEnabler(p); + + const auto available = outerWidth - _margins.left() - _margins.right(); + const auto lineHeight = st::tagPreviewLineHeight; + const auto radius = lineHeight / 2.0; + const auto fractions = { 1.0, 0.85, 0.65, 0.4 }; + auto y = double(_margins.top()); + for (const auto fraction : fractions) { + const auto w = available * fraction; + p.drawRoundedRect( + QRectF(_margins.left(), y, w, lineHeight), + radius, + radius); + y += lineHeight + st::tagPreviewLineSpacing; + } +} + +class TagPreviewDelegate final : public DefaultElementDelegate { +public: + TagPreviewDelegate( + not_null parent, + not_null st, + Fn update); + + void setTagText(const QString &text); + [[nodiscard]] const QString &tagText() const; + + bool elementAnimationsPaused() override; + not_null elementPathShiftGradient() override; + Context elementContext() override; + QString elementAuthorRank(not_null view) override; + +private: + const not_null _parent; + const std::unique_ptr _pathGradient; + QString _tagText; + +}; + +TagPreviewDelegate::TagPreviewDelegate( + not_null parent, + not_null st, + Fn update) +: _parent(parent) +, _pathGradient(MakePathShiftGradient(st, std::move(update))) { +} + +void TagPreviewDelegate::setTagText(const QString &text) { + _tagText = text; +} + +const QString &TagPreviewDelegate::tagText() const { + return _tagText; +} + +bool TagPreviewDelegate::elementAnimationsPaused() { + return _parent->window()->isActiveWindow(); +} + +auto TagPreviewDelegate::elementPathShiftGradient() +-> not_null { + return _pathGradient.get(); +} + +Context TagPreviewDelegate::elementContext() { + return Context::AdminLog; +} + +QString TagPreviewDelegate::elementAuthorRank( + not_null view) { + return _tagText; +} + +} // namespace + +HistoryView::BadgeRole LookupBadgeRole( + not_null peer, + not_null user) { + if (const auto channel = peer->asMegagroup()) { + const auto info = channel->mgInfo.get(); + if (info && info->creator == user) { + return HistoryView::BadgeRole::Creator; + } + const auto userId = peerToUser(user->id); + if (info && info->admins.contains(userId)) { + return HistoryView::BadgeRole::Admin; + } + } else if (const auto chat = peer->asChat()) { + if (peerToUser(user->id) == chat->creator) { + return HistoryView::BadgeRole::Creator; + } else if (chat->admins.contains(user)) { + return HistoryView::BadgeRole::Admin; + } + } + return HistoryView::BadgeRole::User; +} + +class EditTagControl::PreviewWidget final : public Ui::RpWidget { +public: + PreviewWidget( + QWidget *parent, + not_null session, + not_null user, + const QString &initialText, + HistoryView::BadgeRole role); + ~PreviewWidget(); + + void setTagText(const QString &text); + +private: + void paintEvent(QPaintEvent *e) override; + void createItem(); + void applyBadge(const QString &text); + + const not_null _history; + const not_null _user; + const HistoryView::BadgeRole _role; + const std::unique_ptr _theme; + const std::unique_ptr _style; + const std::unique_ptr _delegate; + AdminLog::OwnedItem _item; + QPoint _position; + Ui::PeerUserpicView _userpic; + +}; + +EditTagControl::PreviewWidget::PreviewWidget( + QWidget *parent, + not_null session, + not_null user, + const QString &initialText, + HistoryView::BadgeRole role) +: RpWidget(parent) +, _history(session->data().history(PeerData::kServiceNotificationsId)) +, _user(user) +, _role(role) +, _theme(Window::Theme::DefaultChatThemeOn(lifetime())) +, _style(std::make_unique( + session->colorIndicesValue())) +, _delegate(std::make_unique( + this, + _style.get(), + [=] { update(); })) +, _position(0, st::msgMargin.bottom()) { + _style->apply(_theme.get()); + _delegate->setTagText(initialText); + + _history->owner().viewRepaintRequest( + ) | rpl::on_next([=](Data::RequestViewRepaint data) { + if (data.view == _item.get()) { + update(); + } + }, lifetime()); + + createItem(); + + widthValue( + ) | rpl::filter([=](int w) { + return w >= st::msgMinWidth; + }) | rpl::on_next([=](int w) { + const auto h = _position.y() + + _item->resizeGetHeight(w) + + _position.y(); + resize(w, h); + }, lifetime()); + + _history->owner().itemResizeRequest( + ) | rpl::on_next([=](not_null item) { + if (_item && item == _item->data() && width() >= st::msgMinWidth) { + const auto h = _position.y() + + _item->resizeGetHeight(width()) + + _position.y(); + resize(width(), h); + } + }, lifetime()); +} + +EditTagControl::PreviewWidget::~PreviewWidget() { + _item = {}; +} + +void EditTagControl::PreviewWidget::createItem() { + const auto item = _history->addNewLocalMessage({ + .id = _history->nextNonHistoryEntryId(), + .flags = (MessageFlag::FakeHistoryItem + | MessageFlag::HasFromId), + .from = _user->id, + .date = base::unixtime::now(), + }, TextWithEntities(), MTP_messageMediaEmpty()); + + auto owned = AdminLog::OwnedItem(_delegate.get(), item); + owned->overrideMedia(std::make_unique( + owned.get(), + [](not_null, + Fn)> push) { + push(std::make_unique(st::msgPadding)); + })); + _item = std::move(owned); + applyBadge(_delegate->tagText()); + if (width() >= st::msgMinWidth) { + const auto h = _position.y() + + _item->resizeGetHeight(width()) + + _position.y(); + resize(width(), h); + } +} + +void EditTagControl::PreviewWidget::applyBadge(const QString &text) { + if (_item) { + _item->overrideRightBadge(text, _role); + } +} + +void EditTagControl::PreviewWidget::setTagText(const QString &text) { + _delegate->setTagText(text); + applyBadge(text); + update(); +} + +void EditTagControl::PreviewWidget::paintEvent(QPaintEvent *e) { + auto p = Painter(this); + + const auto clip = e->rect(); + if (!clip.isEmpty()) { + p.setClipRect(clip); + Window::SectionWidget::PaintBackground( + p, + _theme.get(), + QSize(width(), window()->height()), + clip); + } + + auto context = _theme->preparePaintContext( + _style.get(), + rect(), + rect(), + e->rect(), + !window()->isActiveWindow()); + p.translate(_position); + _item->draw(p, context); + + if (_item->displayFromPhoto()) { + auto userpicBottom = height() + - _item->marginBottom() + - _item->marginTop(); + const auto userpicTop = userpicBottom - st::msgPhotoSize; + _user->paintUserpicLeft( + p, + _userpic, + st::historyPhotoLeft, + userpicTop, + width(), + st::msgPhotoSize); + } +} + +EditTagControl::EditTagControl( + QWidget *parent, + not_null session, + not_null user, + const QString ¤tRank, + HistoryView::BadgeRole role) +: RpWidget(parent) +, _preview(Ui::CreateChild( + this, + session, + user, + TextUtilities::RemoveEmoji(currentRank), + role)) +, _field(Ui::CreateChild( + this, + st::customBadgeField, + tr::lng_rights_edit_admin_rank_name(), + TextUtilities::RemoveEmoji(currentRank))) { + _field->setMaxLength(kRankLimit); + _field->setInstantReplaces(Ui::InstantReplaces::TextOnly()); + + _field->changes( + ) | rpl::on_next([=] { + const auto text = _field->getLastText(); + const auto removed = TextUtilities::RemoveEmoji(text); + if (removed != text) { + _field->setText(removed); + } + _preview->setTagText(_field->getLastText()); + }, _field->lifetime()); + + widthValue( + ) | rpl::on_next([=](int w) { + _preview->resizeToWidth(w); + const auto inputMargins = st::boxRowPadding; + _field->resizeToWidth(w - inputMargins.left() - inputMargins.right()); + _field->moveToLeft( + inputMargins.left(), + _preview->height() + st::tagPreviewInputSkip); + resize(w, _field->y() + _field->height()); + }, lifetime()); + + _preview->heightValue( + ) | rpl::skip(1) | rpl::on_next([=] { + _field->moveToLeft( + st::boxRowPadding.left(), + _preview->height() + st::tagPreviewInputSkip); + resize(width(), _field->y() + _field->height()); + }, lifetime()); +} + +EditTagControl::~EditTagControl() = default; + +QString EditTagControl::currentRank() const { + return TextUtilities::RemoveEmoji( + TextUtilities::SingleLine(_field->getLastText().trimmed())); +} + +not_null EditTagControl::field() const { + return _field; +} diff --git a/Telegram/SourceFiles/boxes/peers/edit_tag_control.h b/Telegram/SourceFiles/boxes/peers/edit_tag_control.h new file mode 100644 index 0000000000..ba6e641d61 --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/edit_tag_control.h @@ -0,0 +1,50 @@ +/* +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 "ui/rp_widget.h" + +namespace Ui { +class InputField; +} // namespace Ui + +namespace Main { +class Session; +} // namespace Main + +class PeerData; +class UserData; + +namespace HistoryView { +enum class BadgeRole : uchar; +} // namespace HistoryView + +[[nodiscard]] HistoryView::BadgeRole LookupBadgeRole( + not_null peer, + not_null user); + +class EditTagControl final : public Ui::RpWidget { +public: + EditTagControl( + QWidget *parent, + not_null session, + not_null user, + const QString ¤tRank, + HistoryView::BadgeRole role); + ~EditTagControl(); + + [[nodiscard]] QString currentRank() const; + [[nodiscard]] not_null field() const; + +private: + class PreviewWidget; + + not_null _preview; + not_null _field; + +}; diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index 5c47c4976f..471b94da9b 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -1579,6 +1579,7 @@ void InnerWidget::suggestRestrictParticipant( user, hasAdminRights, currentRights, + QString(), by, since); box->setSaveCallback([=]( diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 9c4c90a898..d9502f9bfa 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -1422,6 +1422,40 @@ void Element::overrideMedia(std::unique_ptr media) { _flags |= Flag::MediaOverriden; } +void Element::overrideRightBadge(const QString &text, BadgeRole role) { + if (text.isEmpty()) { + if (Has()) { + Get()->overridden = false; + RemoveComponents(RightBadge::Bit()); + } + return; + } + if (!Has()) { + AddComponents(RightBadge::Bit()); + } + const auto badge = Get(); + badge->overridden = true; + badge->role = role; + badge->tag.setMarkedText( + st::defaultTextStyle, + { text }, + Ui::NameTextOptions()); + badge->boosts.clear(); + if (role == BadgeRole::User) { + badge->width = badge->tag.maxWidth(); + } else { + const auto &padding = st::msgTagBadgePadding; + const auto tagTextWidth = badge->tag.maxWidth(); + const auto contentWidth = padding.left() + + tagTextWidth + + padding.right(); + const auto pillHeight = padding.top() + + st::msgFont->height + + padding.bottom(); + badge->width = std::max(contentWidth, pillHeight); + } +} + not_null Element::enforcePurchasedTag() { if (const auto purchased = Get()) { return purchased; diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index f252fe9a78..596db502be 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -49,6 +49,7 @@ class InlineList; namespace HistoryView { using PaintContext = Ui::ChatPaintContext; +enum class BadgeRole : uchar; enum class PointState : char; enum class InfoDisplayType : char; struct StateRequest; @@ -661,6 +662,7 @@ public: -> std::unique_ptr; void overrideMedia(std::unique_ptr media); + void overrideRightBadge(const QString &text, BadgeRole role); [[nodiscard]] not_null enforcePurchasedTag(); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index d7e3600a99..1be6fddd74 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -257,6 +257,9 @@ void Message::initPaidInformation() { } void Message::refreshRightBadge() { + if (const auto badge = Get(); badge && badge->overridden) { + return; + } const auto item = data(); const auto [text, role] = [&]() -> std::pair { if (item->isDiscussionPost()) { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 45ef8458cb..5afb46fdcd 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -70,6 +70,7 @@ struct RightBadge : RuntimeComponent { Ui::Text::String boosts; int width = 0; BadgeRole role = BadgeRole::User; + bool overridden = false; }; struct BottomRippleMask { diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 560586058b..8cef42bbd2 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -1459,4 +1459,4 @@ earnTonIconMargin: margins(0px, 2px, 0px, 0px); infoMusicButtonRipple: universalRippleAnimation; infoMusicButtonPadding: margins(16px, 6px, 13px, 6px); -memberTagPillPadding: margins(5px, 1px, 5px, 1px); +memberTagPillPadding: margins(5px, -1px, 5px, 0px); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index bfcc07ecd9..034c991deb 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -23,7 +23,7 @@ msgMinWidth: 160px; msgPhotoSize: 33px; msgPhotoSkip: 40px; msgPadding: margins(11px, 8px, 11px, 8px); -msgTagBadgePadding: margins(5px, 0px, 5px, 0px); +msgTagBadgePadding: margins(5px, -1px, 5px, 0px); msgTagBadgeBoostSkip: 2px; msgMargin: margins(16px, 6px, 56px, 2px); msgMarginTopAttached: 0px;