2017-09-25 16:06:53 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
|
|
|
the official desktop version of Telegram messaging app, see https://telegram.org
|
|
|
|
|
|
|
|
Telegram Desktop is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
It is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
In addition, as a special exception, the copyright holders give permission
|
|
|
|
to link the code of portions of this program with the OpenSSL library.
|
|
|
|
|
|
|
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|
|
|
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|
|
|
*/
|
|
|
|
#include "info/profile/info_profile_members.h"
|
|
|
|
|
|
|
|
#include <rpl/combine.h>
|
|
|
|
#include "info/profile/info_profile_values.h"
|
|
|
|
#include "info/profile/info_profile_icon.h"
|
|
|
|
#include "info/profile/info_profile_values.h"
|
2017-09-26 17:57:01 +00:00
|
|
|
#include "info/profile/info_profile_members_controllers.h"
|
2017-09-25 16:06:53 +00:00
|
|
|
#include "info/info_memento.h"
|
|
|
|
#include "profile/profile_block_group_members.h"
|
|
|
|
#include "ui/widgets/labels.h"
|
|
|
|
#include "ui/widgets/buttons.h"
|
|
|
|
#include "ui/widgets/input_fields.h"
|
2017-09-26 17:57:01 +00:00
|
|
|
#include "ui/widgets/scroll_area.h"
|
|
|
|
#include "styles/style_boxes.h"
|
2017-09-25 16:06:53 +00:00
|
|
|
#include "styles/style_info.h"
|
|
|
|
#include "lang/lang_keys.h"
|
|
|
|
#include "boxes/confirm_box.h"
|
|
|
|
#include "boxes/peer_list_controllers.h"
|
|
|
|
|
|
|
|
namespace Info {
|
|
|
|
namespace Profile {
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
constexpr auto kEnableSearchMembersAfterCount = 50;
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
Members::Members(
|
|
|
|
QWidget *parent,
|
2017-09-26 17:57:01 +00:00
|
|
|
not_null<Window::Controller*> controller,
|
2017-09-25 16:06:53 +00:00
|
|
|
rpl::producer<Wrap> &&wrapValue,
|
|
|
|
not_null<PeerData*> peer)
|
|
|
|
: RpWidget(parent)
|
|
|
|
, _peer(peer)
|
2017-09-26 17:57:01 +00:00
|
|
|
, _controller(CreateMembersController(_peer))
|
2017-09-25 16:06:53 +00:00
|
|
|
, _labelWrap(this)
|
|
|
|
, _label(setupHeader())
|
|
|
|
, _addMember(this, st::infoMembersAddMember)
|
|
|
|
, _searchField(
|
|
|
|
this,
|
|
|
|
st::infoMembersSearchField,
|
|
|
|
langFactory(lng_participant_filter))
|
|
|
|
, _search(this, st::infoMembersSearch)
|
|
|
|
, _cancelSearch(this, st::infoMembersCancelSearch)
|
2017-09-26 17:57:01 +00:00
|
|
|
, _list(setupList(this, _controller.get())) {
|
2017-09-25 16:06:53 +00:00
|
|
|
setupButtons();
|
|
|
|
std::move(wrapValue)
|
2017-09-27 08:43:35 +00:00
|
|
|
| rpl::start_with_next([this](Wrap wrap) {
|
2017-09-25 16:06:53 +00:00
|
|
|
_wrap = wrap;
|
|
|
|
updateSearchOverrides();
|
|
|
|
}, lifetime());
|
2017-09-26 17:57:01 +00:00
|
|
|
setContent(_list.data());
|
|
|
|
_controller->setDelegate(static_cast<PeerListDelegate*>(this));
|
|
|
|
}
|
|
|
|
|
|
|
|
int Members::desiredHeight() const {
|
|
|
|
auto desired = st::infoMembersHeader;
|
|
|
|
auto count = [this] {
|
|
|
|
if (auto chat = _peer->asChat()) {
|
|
|
|
return chat->count;
|
|
|
|
} else if (auto channel = _peer->asChannel()) {
|
|
|
|
return channel->membersCount();
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}();
|
|
|
|
desired += qMax(count, _list->fullRowsCount())
|
|
|
|
* st::infoMembersList.item.height;
|
|
|
|
return qMax(height(), desired);
|
2017-09-25 16:06:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
object_ptr<Ui::FlatLabel> Members::setupHeader() {
|
|
|
|
auto result = object_ptr<Ui::FlatLabel>(
|
|
|
|
_labelWrap,
|
|
|
|
MembersCountValue(_peer)
|
|
|
|
| rpl::map([](int count) {
|
|
|
|
return lng_chat_status_members(lt_count, count);
|
|
|
|
})
|
|
|
|
| ToUpperValue(),
|
|
|
|
st::infoBlockHeaderLabel);
|
|
|
|
result->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Members::setupButtons() {
|
|
|
|
using namespace rpl::mappers;
|
|
|
|
|
|
|
|
_searchField->hide();
|
|
|
|
_cancelSearch->hideFast();
|
|
|
|
|
|
|
|
auto addMemberShown = CanAddMemberValue(_peer)
|
|
|
|
| rpl::start_spawning(lifetime());
|
|
|
|
widthValue()
|
2017-09-27 08:43:35 +00:00
|
|
|
| rpl::start_with_next([button = _addMember.data()](int newWidth) {
|
2017-09-25 16:06:53 +00:00
|
|
|
button->moveToRight(
|
|
|
|
st::infoMembersButtonPosition.x(),
|
|
|
|
st::infoMembersButtonPosition.y(),
|
|
|
|
newWidth);
|
|
|
|
}, _addMember->lifetime());
|
|
|
|
_addMember->showOn(rpl::duplicate(addMemberShown));
|
|
|
|
_addMember->clicks() // TODO throttle(ripple duration)
|
2017-09-27 08:43:35 +00:00
|
|
|
| rpl::start_with_next([this](auto&&) {
|
2017-09-25 16:06:53 +00:00
|
|
|
this->addMember();
|
|
|
|
}, _addMember->lifetime());
|
|
|
|
|
|
|
|
auto searchShown = MembersCountValue(_peer)
|
|
|
|
| rpl::map($1 >= kEnableSearchMembersAfterCount)
|
|
|
|
| rpl::distinct_until_changed()
|
|
|
|
| rpl::start_spawning(lifetime());
|
|
|
|
_search->showOn(rpl::duplicate(searchShown));
|
|
|
|
_search->clicks()
|
2017-09-27 08:43:35 +00:00
|
|
|
| rpl::start_with_next([this](auto&&) {
|
2017-09-25 16:06:53 +00:00
|
|
|
this->showSearch();
|
|
|
|
}, _search->lifetime());
|
|
|
|
_cancelSearch->clicks()
|
2017-09-27 08:43:35 +00:00
|
|
|
| rpl::start_with_next([this](auto&&) {
|
2017-09-25 16:06:53 +00:00
|
|
|
this->cancelSearch();
|
|
|
|
}, _cancelSearch->lifetime());
|
|
|
|
|
|
|
|
rpl::combine(
|
|
|
|
std::move(addMemberShown),
|
|
|
|
std::move(searchShown))
|
2017-09-27 08:43:35 +00:00
|
|
|
| rpl::start_with_next([this](auto&&) {
|
2017-09-25 16:06:53 +00:00
|
|
|
this->resizeToWidth(width());
|
|
|
|
}, lifetime());
|
|
|
|
|
|
|
|
object_ptr<FloatingIcon>(
|
|
|
|
this,
|
|
|
|
st::infoIconMembers,
|
2017-09-26 19:42:58 +00:00
|
|
|
st::infoIconPosition)->lower();
|
2017-09-25 16:06:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
object_ptr<Members::ListWidget> Members::setupList(
|
2017-09-26 17:57:01 +00:00
|
|
|
RpWidget *parent,
|
|
|
|
not_null<PeerListController*> controller) const {
|
2017-09-25 16:06:53 +00:00
|
|
|
auto result = object_ptr<ListWidget>(
|
|
|
|
parent,
|
2017-09-26 17:57:01 +00:00
|
|
|
controller,
|
|
|
|
st::infoMembersList);
|
|
|
|
result->scrollToRequests()
|
2017-09-27 08:43:35 +00:00
|
|
|
| rpl::start_with_next([this](Ui::ScrollToRequest request) {
|
2017-09-26 17:57:01 +00:00
|
|
|
auto addmin = (request.ymin < 0)
|
|
|
|
? 0
|
|
|
|
: st::infoMembersHeader;
|
|
|
|
auto addmax = (request.ymax < 0)
|
|
|
|
? 0
|
|
|
|
: st::infoMembersHeader;
|
|
|
|
_scrollToRequests.fire({
|
|
|
|
request.ymin + addmin,
|
|
|
|
request.ymax + addmax });
|
|
|
|
}, result->lifetime());
|
2017-09-25 16:06:53 +00:00
|
|
|
result->moveToLeft(0, st::infoMembersHeader);
|
|
|
|
parent->widthValue()
|
2017-09-27 08:43:35 +00:00
|
|
|
| rpl::start_with_next([list = result.data()](int newWidth) {
|
2017-09-25 16:06:53 +00:00
|
|
|
list->resizeToWidth(newWidth);
|
|
|
|
}, result->lifetime());
|
|
|
|
result->heightValue()
|
2017-09-27 08:43:35 +00:00
|
|
|
| rpl::start_with_next([parent](int listHeight) {
|
2017-09-26 17:57:01 +00:00
|
|
|
auto newHeight = (listHeight > st::membersMarginBottom)
|
2017-09-25 16:06:53 +00:00
|
|
|
? (st::infoMembersHeader + listHeight)
|
|
|
|
: 0;
|
|
|
|
parent->resize(parent->width(), newHeight);
|
|
|
|
}, result->lifetime());
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Members::resizeGetHeight(int newWidth) {
|
|
|
|
auto availableWidth = newWidth
|
|
|
|
- st::infoMembersButtonPosition.x();
|
|
|
|
if (!_addMember->isHidden()) {
|
|
|
|
availableWidth -= st::infoMembersAddMember.width;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto cancelLeft = availableWidth - _cancelSearch->width();
|
|
|
|
_cancelSearch->moveToLeft(
|
|
|
|
cancelLeft,
|
|
|
|
st::infoMembersButtonPosition.y());
|
|
|
|
|
2017-09-26 19:42:58 +00:00
|
|
|
auto searchShownLeft = st::infoIconPosition.x()
|
2017-09-25 16:06:53 +00:00
|
|
|
- st::infoMembersSearch.iconPosition.x();
|
|
|
|
auto searchHiddenLeft = availableWidth - _search->width();
|
|
|
|
auto searchShown = _searchShownAnimation.current(_searchShown ? 1. : 0.);
|
|
|
|
auto searchCurrentLeft = anim::interpolate(
|
|
|
|
searchHiddenLeft,
|
|
|
|
searchShownLeft,
|
|
|
|
searchShown);
|
|
|
|
_search->moveToLeft(
|
|
|
|
searchCurrentLeft,
|
|
|
|
st::infoMembersButtonPosition.y());
|
|
|
|
|
|
|
|
auto fieldLeft = anim::interpolate(
|
|
|
|
cancelLeft,
|
|
|
|
st::infoBlockHeaderPosition.x(),
|
|
|
|
searchShown);
|
|
|
|
_searchField->setGeometryToLeft(
|
|
|
|
fieldLeft,
|
|
|
|
st::infoMembersSearchTop,
|
|
|
|
cancelLeft - fieldLeft,
|
|
|
|
_searchField->height());
|
2017-09-26 17:57:01 +00:00
|
|
|
connect(_searchField, &Ui::FlatInput::cancelled, this, [this] {
|
|
|
|
cancelSearch();
|
|
|
|
});
|
|
|
|
connect(_searchField, &Ui::FlatInput::changed, this, [this] {
|
|
|
|
applySearch();
|
|
|
|
});
|
|
|
|
connect(_searchField, &Ui::FlatInput::submitted, this, [this] {
|
|
|
|
forceSearchSubmit();
|
|
|
|
});
|
2017-09-25 16:06:53 +00:00
|
|
|
|
|
|
|
_labelWrap->resize(
|
|
|
|
searchCurrentLeft - st::infoBlockHeaderPosition.x(),
|
|
|
|
_label->height());
|
|
|
|
_labelWrap->moveToLeft(
|
|
|
|
st::infoBlockHeaderPosition.x(),
|
|
|
|
st::infoBlockHeaderPosition.y(),
|
|
|
|
newWidth);
|
|
|
|
|
|
|
|
_label->resizeToWidth(searchHiddenLeft);
|
|
|
|
_label->moveToLeft(0, 0);
|
|
|
|
|
|
|
|
return heightNoMargins();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Members::addMember() {
|
|
|
|
if (auto chat = _peer->asChat()) {
|
|
|
|
if (chat->count >= Global::ChatSizeMax() && chat->amCreator()) {
|
|
|
|
Ui::show(Box<ConvertToSupergroupBox>(chat));
|
|
|
|
} else {
|
|
|
|
AddParticipantsBoxController::Start(chat);
|
|
|
|
}
|
|
|
|
} else if (auto channel = _peer->asChannel()) {
|
|
|
|
if (channel->mgInfo) {
|
|
|
|
auto &participants = channel->mgInfo->lastParticipants;
|
|
|
|
AddParticipantsBoxController::Start(channel, { participants.cbegin(), participants.cend() });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Members::showSearch() {
|
|
|
|
if (!_searchShown) {
|
|
|
|
toggleSearch();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Members::toggleSearch() {
|
|
|
|
_searchShown = !_searchShown;
|
|
|
|
_cancelSearch->toggleAnimated(_searchShown);
|
|
|
|
_searchShownAnimation.start(
|
|
|
|
[this] { searchAnimationCallback(); },
|
|
|
|
_searchShown ? 0. : 1.,
|
|
|
|
_searchShown ? 1. : 0.,
|
|
|
|
st::slideWrapDuration);
|
|
|
|
_search->setDisabled(_searchShown);
|
|
|
|
if (_searchShown) {
|
|
|
|
_searchField->show();
|
|
|
|
_searchField->setFocus();
|
|
|
|
} else {
|
|
|
|
setFocus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Members::searchAnimationCallback() {
|
|
|
|
if (!_searchShownAnimation.animating()) {
|
|
|
|
_searchField->setVisible(_searchShown);
|
|
|
|
updateSearchOverrides();
|
|
|
|
_search->setPointerCursor(!_searchShown);
|
|
|
|
}
|
|
|
|
resizeToWidth(width());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Members::updateSearchOverrides() {
|
|
|
|
auto iconOverride = !_searchShown
|
|
|
|
? nullptr
|
|
|
|
: (_wrap == Wrap::Layer)
|
|
|
|
? &st::infoMembersSearchActiveLayer
|
|
|
|
: &st::infoMembersSearchActive;
|
|
|
|
_search->setIconOverride(iconOverride, iconOverride);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Members::cancelSearch() {
|
|
|
|
if (_searchShown) {
|
|
|
|
if (!_searchField->getLastText().isEmpty()) {
|
|
|
|
_searchField->setText(QString());
|
|
|
|
_searchField->updatePlaceholder();
|
|
|
|
_searchField->setFocus();
|
|
|
|
applySearch();
|
|
|
|
} else {
|
|
|
|
toggleSearch();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Members::applySearch() {
|
2017-09-26 17:57:01 +00:00
|
|
|
peerListScrollToTop();
|
|
|
|
content()->searchQueryChanged(_searchField->getLastText());
|
|
|
|
}
|
2017-09-25 16:06:53 +00:00
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void Members::forceSearchSubmit() {
|
|
|
|
content()->submitted();
|
2017-09-25 16:06:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Members::visibleTopBottomUpdated(
|
|
|
|
int visibleTop,
|
|
|
|
int visibleBottom) {
|
|
|
|
setChildVisibleTopBottom(_list, visibleTop, visibleBottom);
|
|
|
|
}
|
|
|
|
|
2017-09-26 17:57:01 +00:00
|
|
|
void Members::peerListSetTitle(base::lambda<QString()> title) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void Members::peerListSetAdditionalTitle(
|
|
|
|
base::lambda<QString()> title) {
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Members::peerListIsRowSelected(not_null<PeerData*> peer) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Members::peerListSelectedRowsCount() {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<not_null<PeerData*>> Members::peerListCollectSelectedRows() {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
void Members::peerListScrollToTop() {
|
|
|
|
_scrollToRequests.fire({ -1, -1 });
|
|
|
|
}
|
|
|
|
|
|
|
|
void Members::peerListAddSelectedRowInBunch(not_null<PeerData*> peer) {
|
|
|
|
Unexpected("Item selection in Info::Profile::Members.");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Members::peerListFinishSelectedRowsBunch() {
|
|
|
|
}
|
|
|
|
|
|
|
|
void Members::peerListSetDescription(
|
|
|
|
object_ptr<Ui::FlatLabel> description) {
|
|
|
|
description.destroy();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-09-25 16:06:53 +00:00
|
|
|
} // namespace Profile
|
|
|
|
} // namespace Info
|
|
|
|
|