Allow editing links from ShowInviteLinkBox.

This commit is contained in:
John Preston 2021-02-08 22:14:51 +04:00
parent 3399a05f1f
commit 047bf467b5
4 changed files with 301 additions and 127 deletions

View file

@ -296,7 +296,6 @@ void PeerListController::setDescriptionText(const QString &text) {
if (text.isEmpty()) {
setDescription(nullptr);
} else {
const auto &st = _listSt ? *_listSt : st::peerListBox;
setDescription(object_ptr<Ui::FlatLabel>(nullptr, text, computeListSt().about));
}
}
@ -340,6 +339,10 @@ rpl::producer<int> PeerListController::boxHeightValue() const {
return rpl::single(st::boxMaxListHeight);
}
int PeerListController::descriptionTopSkipMin() const {
return computeListSt().item.height;
}
void PeerListBox::addSelectItem(
not_null<PeerData*> peer,
anim::type animated) {
@ -959,6 +962,7 @@ int PeerListContent::fullRowsCount() const {
not_null<PeerListRow*> PeerListContent::rowAt(int index) const {
Expects(index >= 0 && index < _rows.size());
return _rows[index].get();
}
@ -1128,7 +1132,10 @@ int PeerListContent::resizeGetHeight(int newWidth) {
_aboveHeight = _aboveSearchWidget->height();
}
}
const auto labelTop = rowsTop() + qMax(1, shownRowsCount()) * _rowHeight;
const auto labelTop = rowsTop()
+ std::max(
shownRowsCount() * _rowHeight,
_controller->descriptionTopSkipMin());
const auto labelWidth = newWidth - 2 * st::contactsPadding.left();
if (_description) {
_description->resizeToWidth(labelWidth);

View file

@ -429,29 +429,30 @@ public:
virtual void restoreState(
std::unique_ptr<PeerListState> state);
virtual int contentWidth() const;
virtual rpl::producer<int> boxHeightValue() const;
[[nodiscard]] virtual int contentWidth() const;
[[nodiscard]] virtual rpl::producer<int> boxHeightValue() const;
[[nodiscard]] virtual int descriptionTopSkipMin() const;
bool isRowSelected(not_null<PeerListRow*> row) {
[[nodiscard]] bool isRowSelected(not_null<PeerListRow*> row) {
return delegate()->peerListIsRowChecked(row);
}
virtual bool searchInLocal() {
return true;
}
bool hasComplexSearch() const;
[[nodiscard]] bool hasComplexSearch() const;
void search(const QString &query);
void peerListSearchAddRow(not_null<PeerData*> peer) override;
void peerListSearchRefreshRows() override;
virtual bool respectSavedMessagesChat() const {
[[nodiscard]] virtual bool respectSavedMessagesChat() const {
return false;
}
virtual rpl::producer<int> onlineCountValue() const;
[[nodiscard]] virtual rpl::producer<int> onlineCountValue() const;
rpl::lifetime &lifetime() {
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}

View file

@ -41,7 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_boxes.h"
#include "styles/style_layers.h" // st::boxDividerLabel.
#include "styles/style_info.h"
#include "styles/style_settings.h" // st::settingsDividerLabelPadding.
#include "styles/style_settings.h"
#include <QtGui/QGuiApplication>
@ -54,27 +54,37 @@ using LinkData = Api::InviteLink;
class Controller final : public PeerListController {
public:
Controller(not_null<PeerData*> peer, const LinkData &data);
Controller(
not_null<PeerData*> peer,
not_null<UserData*> admin,
rpl::producer<LinkData> data);
void prepare() override;
void loadMoreRows() override;
void rowClicked(not_null<PeerListRow*> row) override;
Main::Session &session() const override;
//rpl::producer<int> boxHeightValue() const override;
rpl::producer<int> boxHeightValue() const override;
int descriptionTopSkipMin() const override;
private:
void appendSlice(const Api::JoinedByLinkSlice &slice);
[[nodiscard]] object_ptr<Ui::RpWidget> prepareHeader();
void addHeaderBlock(not_null<Ui::VerticalLayout*> container);
[[nodiscard]] rpl::producer<LinkData> dataValue() const;
const not_null<PeerData*> _peer;
LinkData _data;
rpl::variable<LinkData> _data;
QString _link;
bool _revoked = false;
mtpRequestId _requestId = 0;
std::optional<Api::JoinedByLinkUser> _lastUser;
bool _allLoaded = false;
Ui::RpWidget *_headerWidget = nullptr;
rpl::variable<int> _addedHeight;
MTP::Sender _api;
rpl::lifetime _lifetime;
@ -99,24 +109,48 @@ private:
};
void AddHeaderBlock(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
const LinkData &data,
TimeId now) {
const auto link = data.link;
[[nodiscard]] bool ClosingLinkBox(const LinkData &updated, bool revoked) {
return updated.link.isEmpty() || (!revoked && updated.revoked);
}
Controller::Controller(
not_null<PeerData*> peer,
not_null<UserData*> admin,
rpl::producer<LinkData> data)
: _peer(peer)
, _data(LinkData{ .admin = admin })
, _api(&_peer->session().api().instance()) {
_data = std::move(data);
const auto current = _data.current();
_link = current.link;
_revoked = current.revoked;
}
rpl::producer<LinkData> Controller::dataValue() const {
return _data.value(
) | rpl::filter([=](const LinkData &data) {
return !ClosingLinkBox(data, _revoked);
});
}
void Controller::addHeaderBlock(not_null<Ui::VerticalLayout*> container) {
using namespace Settings;
const auto current = _data.current();
const auto link = current.link;
const auto admin = current.admin;
const auto weak = Ui::MakeWeak(container);
const auto copyLink = crl::guard(weak, [=] {
CopyInviteLink(link);
});
const auto shareLink = crl::guard(weak, [=] {
ShareInviteLinkBox(peer, link);
ShareInviteLinkBox(_peer, link);
});
const auto revokeLink = crl::guard(weak, [=] {
RevokeLink(peer, data.admin, data.link);
RevokeLink(_peer, admin, link);
});
const auto editLink = crl::guard(weak, [=] {
EditLink(peer, data);
EditLink(_peer, _data.current());
});
const auto createMenu = [=] {
@ -150,98 +184,198 @@ void AddHeaderBlock(
label->clicks(
) | rpl::start_with_next(copyLink, label->lifetime());
if (IsExpiredLink(data, now)) {
AddReactivateLinkButton(container, editLink);
} else {
AddCopyShareLinkButtons(container, copyLink, shareLink);
}
}
const auto reactivateWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(
container)));
const auto copyShareWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(
container)));
AddReactivateLinkButton(reactivateWrap->entity(), editLink);
AddCopyShareLinkButtons(copyShareWrap->entity(), copyLink, shareLink);
AddSkip(container, st::inviteLinkJoinedRowPadding.bottom() * 2);
auto grayLabelText = dataValue(
) | rpl::map([=](const LinkData &data) {
const auto usageExpired = (data.usageLimit > 0)
&& (data.usageLimit <= data.usage);
return usageExpired
? tr::lng_group_invite_used_about()
: tr::lng_group_invite_expires_at(
lt_when,
rpl::single(langDateTime(
base::unixtime::parse(data.expireDate))));
}) | rpl::flatten_latest();
const auto redLabelWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::DividerLabel>>(
container,
object_ptr<Ui::DividerLabel>(
container,
object_ptr<Ui::FlatLabel>(
container,
tr::lng_group_invite_expired_about(),
st::boxAttentionDividerLabel),
st::settingsDividerLabelPadding)));
const auto grayLabelWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::DividerLabel>>(
container,
object_ptr<Ui::DividerLabel>(
container,
object_ptr<Ui::FlatLabel>(
container,
std::move(grayLabelText),
st::boxDividerLabel),
st::settingsDividerLabelPadding)));
const auto justDividerWrap = container->add(
object_ptr<Ui::SlideWrap<>>(
container,
object_ptr<Ui::BoxContentDivider>(container)));
AddSkip(container);
dataValue(
) | rpl::start_with_next([=](const LinkData &data) {
const auto now = base::unixtime::now();
const auto expired = IsExpiredLink(data, now);
reactivateWrap->toggle(expired, anim::type::instant);
copyShareWrap->toggle(!expired, anim::type::instant);
void AddHeader(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
const LinkData &data,
TimeId now) {
using namespace Settings;
if (!data.revoked && !data.permanent) {
AddHeaderBlock(container, peer, data, now);
AddSkip(container, st::inviteLinkJoinedRowPadding.bottom() * 2);
const auto timeExpired = (data.expireDate > 0)
&& (data.expireDate <= now);
const auto usageExpired = (data.usageLimit > 0)
&& (data.usageLimit <= data.usage);
if (data.expireDate > 0 || usageExpired) {
container->add(object_ptr<Ui::DividerLabel>(
container,
object_ptr<Ui::FlatLabel>(
container,
(timeExpired
? tr::lng_group_invite_expired_about()
: usageExpired
? tr::lng_group_invite_used_about()
: tr::lng_group_invite_expires_at(
lt_when,
rpl::single(langDateTime(
base::unixtime::parse(data.expireDate))))),
(timeExpired
? st::boxAttentionDividerLabel
: st::boxDividerLabel)),
st::settingsDividerLabelPadding));
} else {
AddDivider(container);
}
AddSkip(container);
redLabelWrap->toggle(timeExpired, anim::type::instant);
grayLabelWrap->toggle(
!timeExpired && (data.expireDate > 0 || usageExpired),
anim::type::instant);
justDividerWrap->toggle(
!data.expireDate && !expired,
anim::type::instant);
}, lifetime());
}
void Controller::prepare() {
using namespace Settings;
auto header = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
const auto container = header.data();
const auto current = _data.current();
if (!current.revoked && !current.permanent) {
addHeaderBlock(container);
}
AddSubsectionTitle(
container,
tr::lng_group_invite_created_by());
AddSinglePeerRow(
container,
data.admin,
rpl::single(langDateTime(base::unixtime::parse(data.date))));
current.admin,
rpl::single(langDateTime(base::unixtime::parse(current.date))));
AddSkip(container, st::membersMarginBottom);
}
Controller::Controller(not_null<PeerData*> peer, const LinkData &data)
: _peer(peer)
, _data(data)
, _api(&_peer->session().api().instance()) {
}
const auto listHeaderWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(
container)));
const auto listHeader = listHeaderWrap->entity();
object_ptr<Ui::RpWidget> Controller::prepareHeader() {
using namespace Settings;
// Make this container occupy full width.
listHeader->add(object_ptr<Ui::RpWidget>(listHeader));
const auto now = base::unixtime::now();
AddDivider(listHeader);
AddSkip(listHeader);
auto result = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
const auto container = result.data();
AddHeader(container, _peer, _data, now);
AddDivider(container);
AddSkip(container);
AddSubsectionTitle(
container,
(_data.usage
auto listHeaderText = dataValue(
) | rpl::map([=](const LinkData &data) {
const auto now = base::unixtime::now();
const auto timeExpired = (data.expireDate > 0)
&& (data.expireDate <= now);
if (!data.usage && data.usageLimit > 0 && !timeExpired) {
auto description = object_ptr<Ui::FlatLabel>(
nullptr,
tr::lng_group_invite_can_join_via_link(
tr::now,
lt_count,
data.usageLimit),
computeListSt().about);
if (!delegate()->peerListFullRowsCount()) {
using namespace rpl::mappers;
_addedHeight = description->heightValue(
) | rpl::map(_1
+ st::membersAboutLimitPadding.top()
+ st::membersAboutLimitPadding.bottom());
}
delegate()->peerListSetDescription(std::move(description));
} else {
_addedHeight = std::max(
data.usage,
delegate()->peerListFullRowsCount()
) * computeListSt().item.height;
delegate()->peerListSetDescription(nullptr);
}
listHeaderWrap->toggle(
data.usage || (data.usageLimit > 0 && !timeExpired),
anim::type::instant);
delegate()->peerListRefreshRows();
return data.usage
? tr::lng_group_invite_joined(
lt_count,
rpl::single(float64(_data.usage)))
: tr::lng_group_invite_no_joined()));
_headerWidget = result.data();
return result;
}
void Controller::prepare() {
delegate()->peerListSetAboveWidget(prepareHeader());
if (!_data.usage && _data.usageLimit > 0) {
setDescriptionText(
tr::lng_group_invite_can_join_via_link(
rpl::single(float64(data.usage)))
: tr::lng_group_invite_no_joined();
}) | rpl::flatten_latest();
const auto listTitle = AddSubsectionTitle(
listHeader,
std::move(listHeaderText));
auto remainingText = dataValue(
) | rpl::map([=](const LinkData &data) {
return !data.usageLimit
? QString()
: tr::lng_group_invite_remaining(
tr::now,
lt_count,
_data.usageLimit));
}
_allLoaded = (_data.usage == 0);
lt_count_decimal,
std::max(data.usageLimit - data.usage, 0));
});
const auto remaining = Ui::CreateChild<Ui::FlatLabel>(
listHeader,
std::move(remainingText),
st::settingsSubsectionTitleRight);
dataValue(
) | rpl::start_with_next([=](const LinkData &data) {
remaining->setTextColorOverride(
(data.usageLimit && (data.usageLimit <= data.usage)
? std::make_optional(st::boxTextFgError->c)
: std::nullopt));
if (!data.usage && data.usageLimit > 0) {
remaining->hide();
} else {
remaining->show();
}
}, remaining->lifetime());
rpl::combine(
listTitle->positionValue(),
remaining->widthValue(),
listHeader->widthValue()
) | rpl::start_with_next([=](
QPoint position,
int width,
int outerWidth) {
remaining->moveToRight(position.x(), position.y(), outerWidth);
}, remaining->lifetime());
_headerWidget = header.data();
delegate()->peerListSetAboveWidget(std::move(header));
_allLoaded = (current.usage == 0);
const auto &inviteLinks = _peer->session().api().inviteLinks();
const auto slice = inviteLinks.joinedFirstSliceLoaded(_peer, _data.link);
const auto slice = inviteLinks.joinedFirstSliceLoaded(_peer, _link);
if (slice) {
appendSlice(*slice);
}
@ -254,7 +388,7 @@ void Controller::loadMoreRows() {
}
_requestId = _api.request(MTPmessages_GetChatInviteImporters(
_peer->input,
MTP_string(_data.link),
MTP_string(_link),
MTP_int(_lastUser ? _lastUser->date : 0),
_lastUser ? _lastUser->user->inputUser : MTP_inputUserEmpty(),
MTP_int(_lastUser ? kPerPage : kFirstPage)
@ -276,6 +410,12 @@ void Controller::appendSlice(const Api::JoinedByLinkSlice &slice) {
std::make_unique<PeerListRow>(user.user));
}
delegate()->peerListRefreshRows();
if (delegate()->peerListFullRowsCount() > 0) {
_addedHeight = std::max(
_data.current().usage,
delegate()->peerListFullRowsCount()
) * computeListSt().item.height;
}
}
void Controller::rowClicked(not_null<PeerListRow*> row) {
@ -286,11 +426,25 @@ Main::Session &Controller::session() const {
return _peer->session();
}
//rpl::producer<int> Controller::boxHeightValue() const {
// Expects(_headerWidget != nullptr);
//
// return _headerWidget->heightValue();
//}
rpl::producer<int> Controller::boxHeightValue() const {
Expects(_headerWidget != nullptr);
return rpl::combine(
_headerWidget->heightValue(),
_addedHeight.value()
) | rpl::map([=](int header, int description) {
const auto wrapped = description
? (computeListSt().padding.top()
+ description
+ computeListSt().padding.bottom())
: 0;
return std::min(header + wrapped, st::boxMaxListHeight);
});
}
int Controller::descriptionTopSkipMin() const {
return 0;
}
SingleRowController::SingleRowController(
not_null<PeerData*> peer,
@ -685,35 +839,44 @@ void RevokeLink(
void ShowInviteLinkBox(
not_null<PeerData*> peer,
const Api::InviteLink &link) {
const auto now = base::unixtime::now();
auto initBox = [=](not_null<Ui::BoxContent*> box) {
box->setTitle(IsExpiredLink(link, now)
? tr::lng_manage_peer_link_expired()
: (link.permanent && !link.revoked)
? tr::lng_manage_peer_link_permanent()
: tr::lng_manage_peer_link_invite());
peer->session().api().inviteLinks().updates(
peer,
link.admin
) | rpl::start_with_next([=](const Api::InviteLinkUpdate &update) {
if (update.was == link.link
&& (!update.now || (!link.revoked && update.now->revoked))) {
const auto admin = link.admin;
const auto linkText = link.link;
const auto revoked = link.revoked;
auto updates = peer->session().api().inviteLinks().updates(
peer,
admin
) | rpl::filter([=](const Api::InviteLinkUpdate &update) {
return (update.was == linkText);
}) | rpl::map([=](const Api::InviteLinkUpdate &update) {
return update.now ? *update.now : LinkData{ .admin = admin };
});
auto data = revoked
? rpl::single(link) | rpl::type_erased()
: rpl::single(link) | rpl::then(std::move(updates));
auto initBox = [=, data = rpl::duplicate(data)](
not_null<Ui::BoxContent*> box) {
rpl::duplicate(
data
) | rpl::start_with_next([=](const LinkData &link) {
if (ClosingLinkBox(link, revoked)) {
box->closeBox();
return;
}
const auto now = base::unixtime::now();
box->setTitle(IsExpiredLink(link, now)
? tr::lng_manage_peer_link_expired()
: (link.permanent && !link.revoked)
? tr::lng_manage_peer_link_permanent()
: tr::lng_manage_peer_link_invite());
}, box->lifetime());
box->addButton(tr::lng_about_done(), [=] { box->closeBox(); });
};
if (link.usage > 0) {
Ui::show(
Box<PeerListBox>(
std::make_unique<Controller>(peer, link),
std::move(initBox)),
Ui::LayerOption::KeepOther);
} else {
Ui::show(Box([=](not_null<Ui::GenericBox*> box) {
initBox(box);
const auto container = box->verticalLayout();
AddHeader(container, peer, link, now);
}), Ui::LayerOption::KeepOther);
}
Ui::show(
Box<PeerListBox>(
std::make_unique<Controller>(peer, link.admin, std::move(data)),
std::move(initBox)),
Ui::LayerOption::KeepOther);
}

View file

@ -83,6 +83,9 @@ settingsSubsectionTitle: FlatLabel(defaultFlatLabel) {
textFg: windowActiveTextFg;
minWidth: 240px;
}
settingsSubsectionTitleRight: FlatLabel(settingsSubsectionTitle) {
minWidth: 0px;
}
settingsSubsectionTitlePadding: margins(22px, 7px, 10px, 9px);
settingsBackgroundPadding: margins(22px, 11px, 10px, 12px);
settingsTileSkip: 15px;