diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b41c7d502..46f9e69dc 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3566,9 +3566,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_filters_link_chats#one" = "{count} chat selected"; "lng_filters_link_chats#other" = "{count} chats selected"; "lng_filters_link_bot_status" = "you can't share chats with bots"; -"lng_filters_link_bot_error" = "Chat's with bots can't be shared You can't share chats with bots"; +"lng_filters_link_bot_error" = "Chats with bots can't be shared."; "lng_filters_link_private_status" = "you can't share private chats"; -"lng_filters_link_private_error" = "You can't share private chats"; +"lng_filters_link_private_error" = "Private chats can't be shared."; "lng_filters_link_noadmin_status" = "you can't invite others here"; "lng_filters_link_noadmin_group_error" = "You don't have the admin rights to share invite links to this group chat."; "lng_filters_link_noadmin_channel_error" = "You don't have the admin rights to share invite links to this channel."; diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index 003efe022..9c519991f 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -751,13 +751,20 @@ void EditFilterBox( box->setTitle(tr::lng_filters_edit()); nameEditing->custom = true; - *data = updated; + // Comparison of ChatFilter-s don't take id into account! + data->force_assign(updated); const auto id = updated.id(); state->links = owner->chatsFilters().communityLinks(id); ExportFilterLink(id, shared, [=](Data::ChatFilterLink link) { Expects(link.id == id); window->show(ShowLinkBox(window, updated, link)); + }, [=](QString error) { + if (error == "COMMUNITIES_TOO_MUCH") { + // #TODO filters + } else { + window->show(ShowLinkBox(window, updated, { .id = id })); + } }); })); }, createLink->lifetime()); diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp index d9f01e55f..0f7333a02 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp @@ -43,7 +43,7 @@ namespace { constexpr auto kMaxLinkTitleLength = 32; using InviteLinkData = Data::ChatFilterLink; -class Row; +class LinkRow; enum class Color { Permanent, @@ -89,7 +89,7 @@ struct Errors { } return std::nullopt; } else if (const auto channel = history->peer->asChannel()) { - if (!channel->canHaveInviteLink()) { + if (!channel->canHaveInviteLink() && !channel->hasUsername()) { return result( tr::lng_filters_link_noadmin_status(tr::now), (channel->isMegagroup() @@ -180,9 +180,9 @@ void ChatFilterLinkBox( box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } -class RowDelegate { +class LinkRowDelegate { public: - virtual void rowUpdateRow(not_null row) = 0; + virtual void rowUpdateRow(not_null row) = 0; virtual void rowPaintIcon( QPainter &p, int x, @@ -191,9 +191,9 @@ public: Color color) = 0; }; -class Row final : public PeerListRow { +class LinkRow final : public PeerListRow { public: - Row(not_null delegate, const InviteLinkData &data); + LinkRow(not_null delegate, const InviteLinkData &data); void update(const InviteLinkData &data); @@ -215,13 +215,28 @@ public: bool actionSelected) override; private: - const not_null _delegate; + const not_null _delegate; InviteLinkData _data; QString _status; Color _color = Color::Permanent; }; +class ChatRow final : public PeerListRow { +public: + ChatRow(not_null peer, const QString &status, bool disabled); + + PaintRoundImageCallback generatePaintUserpicCallback( + bool forceRound) override; + +private: + const bool _disabled = false; + QImage _disabledFrame; + InMemoryKey _userpicKey; + int _paletteVersion = 0; + +}; + [[nodiscard]] uint64 ComputeRowId(const QString &link) { return XXH64(link.data(), link.size() * sizeof(ushort), 0); } @@ -238,7 +253,9 @@ private: return tr::lng_filters_chats_count(tr::now, lt_count, link.chats.size()); } -Row::Row(not_null delegate, const InviteLinkData &data) +LinkRow::LinkRow( + not_null delegate, + const InviteLinkData &data) : PeerListRow(ComputeRowId(data)) , _delegate(delegate) , _data(data) @@ -246,7 +263,7 @@ Row::Row(not_null delegate, const InviteLinkData &data) setCustomStatus(ComputeStatus(data)); } -void Row::update(const InviteLinkData &data) { +void LinkRow::update(const InviteLinkData &data) { _data = data; _color = ComputeColor(data); setCustomStatus(ComputeStatus(data)); @@ -254,11 +271,11 @@ void Row::update(const InviteLinkData &data) { _delegate->rowUpdateRow(this); } -InviteLinkData Row::data() const { +InviteLinkData LinkRow::data() const { return _data; } -QString Row::generateName() { +QString LinkRow::generateName() { if (!_data.title.isEmpty()) { return _data.title; } @@ -275,11 +292,12 @@ QString Row::generateName() { ); } -QString Row::generateShortName() { +QString LinkRow::generateShortName() { return generateName(); } -PaintRoundImageCallback Row::generatePaintUserpicCallback(bool forceRound) { +PaintRoundImageCallback LinkRow::generatePaintUserpicCallback( + bool forceRound) { return [=]( QPainter &p, int x, @@ -290,13 +308,13 @@ PaintRoundImageCallback Row::generatePaintUserpicCallback(bool forceRound) { }; } -QSize Row::rightActionSize() const { +QSize LinkRow::rightActionSize() const { return QSize( st::inviteLinkThreeDotsIcon.width(), st::inviteLinkThreeDotsIcon.height()); } -QMargins Row::rightActionMargins() const { +QMargins LinkRow::rightActionMargins() const { return QMargins( 0, (st::inviteLinkList.item.height - rightActionSize().height()) / 2, @@ -304,7 +322,7 @@ QMargins Row::rightActionMargins() const { 0); } -void Row::rightActionPaint( +void LinkRow::rightActionPaint( Painter &p, int x, int y, @@ -316,9 +334,106 @@ void Row::rightActionPaint( : st::inviteLinkThreeDotsIcon).paint(p, x, y, outerWidth); } +ChatRow::ChatRow( + not_null peer, + const QString &status, + bool disabled) +: PeerListRow(peer) +, _disabled(disabled) { + if (!status.isEmpty()) { + setCustomStatus(status); + } +} + +PaintRoundImageCallback ChatRow::generatePaintUserpicCallback( + bool forceRound) { + const auto peer = this->peer(); + const auto saved = peer->isSelf(); + const auto replies = peer->isRepliesChat(); + auto userpic = (saved || replies) + ? Ui::PeerUserpicView() + : ensureUserpicView(); + auto paint = [=]( + Painter &p, + int x, + int y, + int outerWidth, + int size) mutable { + if (forceRound && peer->isForum()) { + ForceRoundUserpicCallback(peer)(p, x, y, outerWidth, size); + } else if (saved) { + Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size); + } else if (replies) { + Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size); + } else { + peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size); + } + }; + return [=]( + Painter &p, + int x, + int y, + int outerWidth, + int size) mutable { + if (!_disabled) { + paint(p, x, y, outerWidth, size); + return; + } + const auto wide = size + style::ConvertScale(3); + const auto full = QSize(wide, wide) * style::DevicePixelRatio(); + auto repaint = false; + if (_disabledFrame.size() != full) { + repaint = true; + _disabledFrame = QImage( + full, + QImage::Format_ARGB32_Premultiplied); + _disabledFrame.setDevicePixelRatio(style::DevicePixelRatio()); + } else { + repaint = (_paletteVersion != style::PaletteVersion()) + || (!saved + && !replies + && (_userpicKey != peer->userpicUniqueKey(userpic))); + } + if (repaint) { + _paletteVersion = style::PaletteVersion(); + _userpicKey = peer->userpicUniqueKey(userpic); + + _disabledFrame.fill(Qt::transparent); + auto p = Painter(&_disabledFrame); + paint(p, 0, 0, wide, size); + + auto hq = PainterHighQualityEnabler(p); + p.setBrush(st::boxBg); + p.setPen(Qt::NoPen); + const auto two = style::ConvertScaleExact(2.5); + const auto half = size / 2.; + const auto rect = QRectF(half, half, half, half).translated( + { two, two }); + p.drawEllipse(rect); + + auto pen = st::windowSubTextFg->p; + const auto width = style::ConvertScaleExact(1.5); + const auto dash = 0.55; + const auto dashWithCaps = dash + 1.; + pen.setWidthF(width); + // 11 parts = M_PI * half / ((dashWithCaps + space) * width) + // 11 = M_PI * half / ((dashWithCaps + space) * width) + // space = (M_PI * half / (11 * width)) - dashWithCaps + const auto space = M_PI * half / (11 * width) - dashWithCaps; + pen.setDashPattern(QVector{ dash, space }); + pen.setDashOffset(1.); + pen.setCapStyle(Qt::RoundCap); + p.setBrush(Qt::NoBrush); + p.setPen(pen); + p.drawEllipse(rect.marginsRemoved({ two, two, two, two })); + } + p.drawImage(x, y, _disabledFrame); + }; +} + class LinksController final : public PeerListController - , public RowDelegate + , public LinkRowDelegate , public base::has_weak_ptr { public: LinksController( @@ -334,7 +449,7 @@ public: not_null row) override; Main::Session &session() const override; - void rowUpdateRow(not_null row) override; + void rowUpdateRow(not_null row) override; void rowPaintIcon( QPainter &p, int x, @@ -454,7 +569,7 @@ void LinkController::addHeader(not_null container) { rpl::single(Ui::Text::Bold(_filterTitle)), Ui::Text::WithEntities)), st::settingsFilterDividerLabel)), - st::settingsFilterDividerLabelPadding); + st::filterLinkDividerLabelPadding); verticalLayout->geometryValue( ) | rpl::start_with_next([=](const QRect &r) { @@ -564,9 +679,26 @@ void LinkController::prepare() { setupAboveWidget(); setupBelowWidget(); + const auto countStatus = [&](not_null peer) { + if (const auto chat = peer->asChat()) { + if (const auto count = chat->count; count > 0) { + return tr::lng_chat_status_members(tr::now, lt_count, count); + } + } else if (const auto channel = peer->asChannel()) { + if (channel->membersCountKnown()) { + return (channel->isBroadcast() + ? tr::lng_chat_status_subscribers + : tr::lng_chat_status_members)( + tr::now, + lt_count, + channel->membersCount()); + } + } + return QString(); + }; for (const auto &history : _data.chats) { const auto peer = history->peer; - auto row = std::make_unique(peer); + auto row = std::make_unique(peer, countStatus(peer), false); const auto raw = row.get(); delegate()->peerListAppendRow(std::move(row)); delegate()->peerListSetRowChecked(raw, true); @@ -577,11 +709,14 @@ void LinkController::prepare() { continue; } const auto peer = history->peer; - auto row = std::make_unique(peer); + const auto error = ErrorForSharing(history); + auto row = std::make_unique( + peer, + error ? error->status : countStatus(peer), + error.has_value()); const auto raw = row.get(); delegate()->peerListAppendRow(std::move(row)); - if (const auto error = ErrorForSharing(history)) { - raw->setCustomStatus(error->status); + if (error) { _denied.emplace(peer, error->toast); } else if (_data.url.isEmpty()) { _denied.emplace(peer); @@ -646,6 +781,11 @@ void LinkController::setupAboveWidget() { std::move(subtitle), st::filterLinkSubsectionTitlePadding); + // Fix label cutting on text change from smaller to longer. + _selected.changes() | rpl::start_with_next([=] { + container->resizeToWidth(container->widthNoMargins()); + }, container->lifetime()); + delegate()->peerListSetAboveWidget(std::move(wrap)); } @@ -702,7 +842,7 @@ void LinksController::rebuild(const std::vector &rows) { while (i < rows.size()) { if (i < count) { const auto row = delegate()->peerListRowAt(i); - static_cast(row.get())->update(rows[i]); + static_cast(row.get())->update(rows[i]); } else { appendRow(rows[i]); } @@ -716,7 +856,7 @@ void LinksController::rebuild(const std::vector &rows) { } void LinksController::rowClicked(not_null row) { - const auto link = static_cast(row.get())->data(); + const auto link = static_cast(row.get())->data(); delegate()->peerListShowBox( ShowLinkBox(_window, _currentFilter(), link), Ui::LayerOption::KeepOther); @@ -746,7 +886,7 @@ base::unique_qptr LinksController::rowContextMenu( base::unique_qptr LinksController::createRowContextMenu( QWidget *parent, not_null row) { - const auto real = static_cast(row.get()); + const auto real = static_cast(row.get()); const auto data = real->data(); const auto link = data.url; const auto copyLink = [=] { @@ -803,7 +943,7 @@ Main::Session &LinksController::session() const { } void LinksController::appendRow(const InviteLinkData &data) { - delegate()->peerListAppendRow(std::make_unique(this, data)); + delegate()->peerListAppendRow(std::make_unique(this, data)); } bool LinksController::removeRow(const QString &link) { @@ -814,7 +954,7 @@ bool LinksController::removeRow(const QString &link) { return false; } -void LinksController::rowUpdateRow(not_null row) { +void LinksController::rowUpdateRow(not_null row) { delegate()->peerListUpdateRow(row); } @@ -927,7 +1067,8 @@ bool GoodForExportFilterLink( void ExportFilterLink( FilterId id, const std::vector> &peers, - Fn done) { + Fn done, + Fn fail) { Expects(!peers.empty()); const auto front = peers.front(); @@ -950,7 +1091,7 @@ void ExportFilterLink( data.vinvite()); done(link); }).fail([=](const MTP::Error &error) { - done({ .id = id }); + fail(error.type()); }).send(); } @@ -987,6 +1128,7 @@ object_ptr ShowLinkBox( const Data::ChatFilter &filter, const Data::ChatFilterLink &link) { auto controller = std::make_unique(window, filter, link); + controller->setStyleOverrides(&st::inviteLinkChatList); const auto raw = controller.get(); auto initBox = [=](not_null box) { box->setTitle(!link.title.isEmpty() @@ -995,6 +1137,8 @@ object_ptr ShowLinkBox( raw->hasChangesValue( ) | rpl::start_with_next([=](bool has) { + box->setCloseByOutsideClick(!has); + box->setCloseByEscape(!has); box->clearButtons(); if (has) { box->addButton(tr::lng_settings_save(), [=] { diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_links.h b/Telegram/SourceFiles/boxes/filters/edit_filter_links.h index d632d3472..a2024faa4 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_links.h +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_links.h @@ -33,7 +33,8 @@ class SessionController; void ExportFilterLink( FilterId id, const std::vector> &peers, - Fn done); + Fn done, + Fn fail); object_ptr ShowLinkBox( not_null window, diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index dfafc3ef6..d4c146ce7 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -800,6 +800,9 @@ inviteLinkList: PeerList(defaultPeerList) { item: inviteLinkListItem; padding: margins(0px, 4px, 0px, 0px); } +inviteLinkChatList: PeerList(peerListBox) { + padding: margins(0px, 4px, 0px, 6px); +} inviteLinkAdminsList: PeerList(inviteLinkList) { item: PeerListItem(inviteLinkListItem) { photoPosition: point(16px, 9px); diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 2af9de650..e5da33b9b 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -560,6 +560,7 @@ filterInviteButtonBadgeStyle: TextStyle(defaultTextStyle) { } filterInviteButtonBadgePadding: margins(5px, 0px, 5px, 2px); filterInviteButtonBadgeSkip: 5px; +filterLinkDividerLabelPadding: margins(0px, 10px, 0px, 17px); filterLinkTitlePadding: margins(0px, 15px, 0px, 17px); filterLinkAboutTextStyle: TextStyle(defaultTextStyle) { font: font(12px); diff --git a/Telegram/SourceFiles/settings/settings_folders.cpp b/Telegram/SourceFiles/settings/settings_folders.cpp index a34a584aa..0dd60090e 100644 --- a/Telegram/SourceFiles/settings/settings_folders.cpp +++ b/Telegram/SourceFiles/settings/settings_folders.cpp @@ -150,9 +150,12 @@ struct FilterRow { const Data::ChatFilter &filter, bool check = false) { const auto count = ComputeCount(session, filter, check); - return count + const auto result = count ? tr::lng_filters_chats_count(tr::now, lt_count_short, count) : tr::lng_filters_no_chats(tr::now); + return filter.community() + ? result + QString::fromUtf8(" \xE2\x80\xA2 shareable folder") + : result; } FilterRowButton::FilterRowButton( @@ -327,6 +330,7 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { not_null container) { auto &lifetime = container->lifetime(); + const auto weak = Ui::MakeWeak(container); const auto session = &controller->session(); const auto limit = [=] { return Data::PremiumLimits(session).dialogFiltersCurrent();