Improve folder link chats list edit design.

This commit is contained in:
John Preston 2023-03-30 17:51:52 +04:00
parent 0faadc8fa0
commit 7a9961b0e9
7 changed files with 195 additions and 35 deletions

View File

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

View File

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

View File

@ -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*> row) = 0;
virtual void rowUpdateRow(not_null<LinkRow*> 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<RowDelegate*> delegate, const InviteLinkData &data);
LinkRow(not_null<LinkRowDelegate*> delegate, const InviteLinkData &data);
void update(const InviteLinkData &data);
@ -215,13 +215,28 @@ public:
bool actionSelected) override;
private:
const not_null<RowDelegate*> _delegate;
const not_null<LinkRowDelegate*> _delegate;
InviteLinkData _data;
QString _status;
Color _color = Color::Permanent;
};
class ChatRow final : public PeerListRow {
public:
ChatRow(not_null<PeerData*> 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<RowDelegate*> delegate, const InviteLinkData &data)
LinkRow::LinkRow(
not_null<LinkRowDelegate*> delegate,
const InviteLinkData &data)
: PeerListRow(ComputeRowId(data))
, _delegate(delegate)
, _data(data)
@ -246,7 +263,7 @@ Row::Row(not_null<RowDelegate*> 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<PeerData*> 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<qreal>{ 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<PeerListRow*> row) override;
Main::Session &session() const override;
void rowUpdateRow(not_null<Row*> row) override;
void rowUpdateRow(not_null<LinkRow*> row) override;
void rowPaintIcon(
QPainter &p,
int x,
@ -454,7 +569,7 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> 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<PeerData*> 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<PeerListRow>(peer);
auto row = std::make_unique<ChatRow>(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<PeerListRow>(peer);
const auto error = ErrorForSharing(history);
auto row = std::make_unique<ChatRow>(
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<InviteLinkData> &rows) {
while (i < rows.size()) {
if (i < count) {
const auto row = delegate()->peerListRowAt(i);
static_cast<Row*>(row.get())->update(rows[i]);
static_cast<LinkRow*>(row.get())->update(rows[i]);
} else {
appendRow(rows[i]);
}
@ -716,7 +856,7 @@ void LinksController::rebuild(const std::vector<InviteLinkData> &rows) {
}
void LinksController::rowClicked(not_null<PeerListRow*> row) {
const auto link = static_cast<Row*>(row.get())->data();
const auto link = static_cast<LinkRow*>(row.get())->data();
delegate()->peerListShowBox(
ShowLinkBox(_window, _currentFilter(), link),
Ui::LayerOption::KeepOther);
@ -746,7 +886,7 @@ base::unique_qptr<Ui::PopupMenu> LinksController::rowContextMenu(
base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) {
const auto real = static_cast<Row*>(row.get());
const auto real = static_cast<LinkRow*>(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<Row>(this, data));
delegate()->peerListAppendRow(std::make_unique<LinkRow>(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*> row) {
void LinksController::rowUpdateRow(not_null<LinkRow*> row) {
delegate()->peerListUpdateRow(row);
}
@ -927,7 +1067,8 @@ bool GoodForExportFilterLink(
void ExportFilterLink(
FilterId id,
const std::vector<not_null<PeerData*>> &peers,
Fn<void(Data::ChatFilterLink)> done) {
Fn<void(Data::ChatFilterLink)> done,
Fn<void(QString)> 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<Ui::BoxContent> ShowLinkBox(
const Data::ChatFilter &filter,
const Data::ChatFilterLink &link) {
auto controller = std::make_unique<LinkController>(window, filter, link);
controller->setStyleOverrides(&st::inviteLinkChatList);
const auto raw = controller.get();
auto initBox = [=](not_null<Ui::BoxContent*> box) {
box->setTitle(!link.title.isEmpty()
@ -995,6 +1137,8 @@ object_ptr<Ui::BoxContent> 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(), [=] {

View File

@ -33,7 +33,8 @@ class SessionController;
void ExportFilterLink(
FilterId id,
const std::vector<not_null<PeerData*>> &peers,
Fn<void(Data::ChatFilterLink)> done);
Fn<void(Data::ChatFilterLink)> done,
Fn<void(QString)> fail);
object_ptr<Ui::BoxContent> ShowLinkBox(
not_null<Window::SessionController*> window,

View File

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

View File

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

View File

@ -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<Ui::VerticalLayout*> container) {
auto &lifetime = container->lifetime();
const auto weak = Ui::MakeWeak(container);
const auto session = &controller->session();
const auto limit = [=] {
return Data::PremiumLimits(session).dialogFiltersCurrent();