diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index e8939f11a..4ca467980 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -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(nullptr, text, computeListSt().about)); } } @@ -340,6 +339,10 @@ rpl::producer PeerListController::boxHeightValue() const { return rpl::single(st::boxMaxListHeight); } +int PeerListController::descriptionTopSkipMin() const { + return computeListSt().item.height; +} + void PeerListBox::addSelectItem( not_null peer, anim::type animated) { @@ -959,6 +962,7 @@ int PeerListContent::fullRowsCount() const { not_null 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); diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index aafbdd5df..2fca8547e 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -429,29 +429,30 @@ public: virtual void restoreState( std::unique_ptr state); - virtual int contentWidth() const; - virtual rpl::producer boxHeightValue() const; + [[nodiscard]] virtual int contentWidth() const; + [[nodiscard]] virtual rpl::producer boxHeightValue() const; + [[nodiscard]] virtual int descriptionTopSkipMin() const; - bool isRowSelected(not_null row) { + [[nodiscard]] bool isRowSelected(not_null 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 peer) override; void peerListSearchRefreshRows() override; - virtual bool respectSavedMessagesChat() const { + [[nodiscard]] virtual bool respectSavedMessagesChat() const { return false; } - virtual rpl::producer onlineCountValue() const; + [[nodiscard]] virtual rpl::producer onlineCountValue() const; - rpl::lifetime &lifetime() { + [[nodiscard]] rpl::lifetime &lifetime() { return _lifetime; } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp index 036f39ab0..9688f6d16 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp @@ -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 @@ -54,27 +54,37 @@ using LinkData = Api::InviteLink; class Controller final : public PeerListController { public: - Controller(not_null peer, const LinkData &data); + Controller( + not_null peer, + not_null admin, + rpl::producer data); void prepare() override; void loadMoreRows() override; void rowClicked(not_null row) override; Main::Session &session() const override; - //rpl::producer boxHeightValue() const override; + rpl::producer boxHeightValue() const override; + int descriptionTopSkipMin() const override; private: void appendSlice(const Api::JoinedByLinkSlice &slice); - [[nodiscard]] object_ptr prepareHeader(); + void addHeaderBlock(not_null container); + + [[nodiscard]] rpl::producer dataValue() const; const not_null _peer; - LinkData _data; + rpl::variable _data; + + QString _link; + bool _revoked = false; mtpRequestId _requestId = 0; std::optional _lastUser; bool _allLoaded = false; Ui::RpWidget *_headerWidget = nullptr; + rpl::variable _addedHeight; MTP::Sender _api; rpl::lifetime _lifetime; @@ -99,24 +109,48 @@ private: }; -void AddHeaderBlock( - not_null container, - not_null 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 peer, + not_null admin, + rpl::producer 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 Controller::dataValue() const { + return _data.value( + ) | rpl::filter([=](const LinkData &data) { + return !ClosingLinkBox(data, _revoked); + }); +} + +void Controller::addHeaderBlock(not_null 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>( + container, + object_ptr( + container))); + const auto copyShareWrap = container->add( + object_ptr>( + container, + object_ptr( + 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>( + container, + object_ptr( + container, + object_ptr( + container, + tr::lng_group_invite_expired_about(), + st::boxAttentionDividerLabel), + st::settingsDividerLabelPadding))); + const auto grayLabelWrap = container->add( + object_ptr>( + container, + object_ptr( + container, + object_ptr( + container, + std::move(grayLabelText), + st::boxDividerLabel), + st::settingsDividerLabelPadding))); + const auto justDividerWrap = container->add( + object_ptr>( + container, + object_ptr(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 container, - not_null 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( - container, - object_ptr( - 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((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 peer, const LinkData &data) -: _peer(peer) -, _data(data) -, _api(&_peer->session().api().instance()) { -} + const auto listHeaderWrap = container->add( + object_ptr>( + container, + object_ptr( + container))); + const auto listHeader = listHeaderWrap->entity(); -object_ptr Controller::prepareHeader() { - using namespace Settings; + // Make this container occupy full width. + listHeader->add(object_ptr(listHeader)); - const auto now = base::unixtime::now(); + AddDivider(listHeader); + AddSkip(listHeader); - auto result = object_ptr((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( + 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( + 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(user.user)); } delegate()->peerListRefreshRows(); + if (delegate()->peerListFullRowsCount() > 0) { + _addedHeight = std::max( + _data.current().usage, + delegate()->peerListFullRowsCount() + ) * computeListSt().item.height; + } } void Controller::rowClicked(not_null row) { @@ -286,11 +426,25 @@ Main::Session &Controller::session() const { return _peer->session(); } -//rpl::producer Controller::boxHeightValue() const { -// Expects(_headerWidget != nullptr); -// -// return _headerWidget->heightValue(); -//} +rpl::producer 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 peer, @@ -685,35 +839,44 @@ void RevokeLink( void ShowInviteLinkBox( not_null peer, const Api::InviteLink &link) { - const auto now = base::unixtime::now(); - auto initBox = [=](not_null 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 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( - std::make_unique(peer, link), - std::move(initBox)), - Ui::LayerOption::KeepOther); - } else { - Ui::show(Box([=](not_null box) { - initBox(box); - const auto container = box->verticalLayout(); - AddHeader(container, peer, link, now); - }), Ui::LayerOption::KeepOther); - } + Ui::show( + Box( + std::make_unique(peer, link.admin, std::move(data)), + std::move(initBox)), + Ui::LayerOption::KeepOther); } diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index d2582c0c3..eca3f69bf 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -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;