info_profile_members.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "info/profile/info_profile_members.h"
  8. #include <rpl/combine.h>
  9. #include "info/profile/info_profile_widget.h"
  10. #include "info/profile/info_profile_values.h"
  11. #include "info/profile/info_profile_icon.h"
  12. #include "info/profile/info_profile_values.h"
  13. #include "info/profile/info_profile_members_controllers.h"
  14. #include "info/members/info_members_widget.h"
  15. #include "info/info_content_widget.h"
  16. #include "info/info_controller.h"
  17. #include "info/info_memento.h"
  18. #include "ui/widgets/labels.h"
  19. #include "ui/widgets/buttons.h"
  20. #include "ui/widgets/fields/input_field.h"
  21. #include "ui/widgets/scroll_area.h"
  22. #include "ui/wrap/padding_wrap.h"
  23. #include "ui/text/text_utilities.h" // Ui::Text::ToUpper
  24. #include "ui/search_field_controller.h"
  25. #include "lang/lang_keys.h"
  26. #include "ui/boxes/confirm_box.h"
  27. #include "boxes/peers/add_participants_box.h"
  28. #include "window/window_session_controller.h"
  29. #include "data/data_channel.h"
  30. #include "data/data_chat.h"
  31. #include "data/data_user.h"
  32. #include "styles/style_boxes.h"
  33. #include "styles/style_info.h"
  34. namespace Info {
  35. namespace Profile {
  36. namespace {
  37. constexpr auto kEnableSearchMembersAfterCount = 20;
  38. } // namespace
  39. Members::Members(
  40. QWidget *parent,
  41. not_null<Controller*> controller)
  42. : RpWidget(parent)
  43. , _show(controller->uiShow())
  44. , _controller(controller)
  45. , _peer(_controller->key().peer())
  46. , _listController(CreateMembersController(controller, _peer)) {
  47. _listController->setStoriesShown(true);
  48. setupHeader();
  49. setupList();
  50. setContent(_list.data());
  51. _listController->setDelegate(static_cast<PeerListDelegate*>(this));
  52. _controller->searchFieldController()->queryValue(
  53. ) | rpl::start_with_next([this](QString &&query) {
  54. peerListScrollToTop();
  55. content()->searchQueryChanged(std::move(query));
  56. }, lifetime());
  57. MembersCountValue(
  58. _peer
  59. ) | rpl::start_with_next([this](int count) {
  60. const auto enabled = (count >= kEnableSearchMembersAfterCount);
  61. _controller->setSearchEnabledByContent(enabled);
  62. }, lifetime());
  63. }
  64. int Members::desiredHeight() const {
  65. auto desired = _header ? _header->height() : 0;
  66. auto count = [this] {
  67. if (auto chat = _peer->asChat()) {
  68. return chat->count;
  69. } else if (auto channel = _peer->asChannel()) {
  70. return channel->membersCount();
  71. }
  72. return 0;
  73. }();
  74. desired += qMax(count, _list->fullRowsCount())
  75. * st::infoMembersList.item.height;
  76. return qMax(height(), desired);
  77. }
  78. rpl::producer<int> Members::onlineCountValue() const {
  79. return _listController->onlineCountValue();
  80. }
  81. rpl::producer<int> Members::fullCountValue() const {
  82. return _listController->fullCountValue();
  83. }
  84. rpl::producer<Ui::ScrollToRequest> Members::scrollToRequests() const {
  85. return _scrollToRequests.events();
  86. }
  87. std::unique_ptr<MembersState> Members::saveState() {
  88. auto result = std::make_unique<MembersState>();
  89. result->list = _listController->saveState();
  90. //if (_searchShown) {
  91. // result->search = _searchField->getLastText();
  92. //}
  93. return result;
  94. }
  95. void Members::restoreState(std::unique_ptr<MembersState> state) {
  96. if (!state) {
  97. return;
  98. }
  99. _listController->restoreState(std::move(state->list));
  100. //updateSearchEnabledByContent();
  101. //if (!_controller->searchFieldController()->query().isEmpty()) {
  102. // if (!_searchShown) {
  103. // toggleSearch(anim::type::instant);
  104. // }
  105. //} else if (_searchShown) {
  106. // toggleSearch(anim::type::instant);
  107. //}
  108. }
  109. void Members::setupHeader() {
  110. if (_controller->section().type() == Section::Type::Members) {
  111. return;
  112. }
  113. _header = object_ptr<Ui::FixedHeightWidget>(
  114. this,
  115. st::infoMembersHeader);
  116. auto parent = _header.data();
  117. _openMembers = Ui::CreateChild<Ui::SettingsButton>(
  118. parent,
  119. rpl::single(QString()));
  120. object_ptr<FloatingIcon>(
  121. parent,
  122. st::infoIconMembers,
  123. st::infoGroupMembersIconPosition);
  124. _titleWrap = Ui::CreateChild<Ui::RpWidget>(parent);
  125. _title = setupTitle();
  126. _addMember = Ui::CreateChild<Ui::IconButton>(
  127. _openMembers,
  128. st::infoMembersAddMember);
  129. //_searchField = _controller->searchFieldController()->createField(
  130. // parent,
  131. // st::infoMembersSearchField);
  132. _search = Ui::CreateChild<Ui::IconButton>(
  133. _openMembers,
  134. st::infoMembersSearch);
  135. //_cancelSearch = Ui::CreateChild<Ui::CrossButton>(
  136. // parent,
  137. // st::infoMembersCancelSearch);
  138. setupButtons();
  139. //_controller->wrapValue(
  140. //) | rpl::start_with_next([this](Wrap wrap) {
  141. // _wrap = wrap;
  142. // updateSearchOverrides();
  143. //}, lifetime());
  144. widthValue(
  145. ) | rpl::start_with_next([this](int width) {
  146. _header->resizeToWidth(width);
  147. }, _header->lifetime());
  148. }
  149. object_ptr<Ui::FlatLabel> Members::setupTitle() {
  150. auto visible = _peer->isMegagroup()
  151. ? CanViewParticipantsValue(_peer->asMegagroup())
  152. : rpl::single(true);
  153. auto result = object_ptr<Ui::FlatLabel>(
  154. _titleWrap,
  155. rpl::conditional(
  156. std::move(visible),
  157. tr::lng_chat_status_members(
  158. lt_count_decimal,
  159. MembersCountValue(_peer) | tr::to_count(),
  160. Ui::Text::Upper),
  161. tr::lng_channel_admins(Ui::Text::Upper)),
  162. st::infoBlockHeaderLabel);
  163. result->setAttribute(Qt::WA_TransparentForMouseEvents);
  164. return result;
  165. }
  166. void Members::setupButtons() {
  167. using namespace rpl::mappers;
  168. _openMembers->addClickHandler([this] {
  169. showMembersWithSearch(false);
  170. });
  171. //_searchField->hide();
  172. //_cancelSearch->setVisible(false);
  173. auto visible = _peer->isMegagroup()
  174. ? CanViewParticipantsValue(_peer->asMegagroup())
  175. : rpl::single(true);
  176. rpl::duplicate(visible) | rpl::start_with_next([=](bool visible) {
  177. _openMembers->setVisible(visible);
  178. }, lifetime());
  179. auto addMemberShown = CanAddMemberValue(
  180. _peer
  181. ) | rpl::start_spawning(lifetime());
  182. _addMember->showOn(rpl::duplicate(addMemberShown));
  183. _addMember->addClickHandler([this] { // TODO throttle(ripple duration)
  184. this->addMember();
  185. });
  186. auto searchShown = MembersCountValue(_peer)
  187. | rpl::map(_1 >= kEnableSearchMembersAfterCount)
  188. | rpl::distinct_until_changed()
  189. | rpl::start_spawning(lifetime());
  190. _search->showOn(rpl::duplicate(searchShown));
  191. _search->addClickHandler([this] { // TODO throttle(ripple duration)
  192. this->showMembersWithSearch(true);
  193. });
  194. //_cancelSearch->addClickHandler([this] {
  195. // this->cancelSearch();
  196. //});
  197. rpl::combine(
  198. std::move(addMemberShown),
  199. std::move(searchShown),
  200. std::move(visible)
  201. ) | rpl::start_with_next([this] {
  202. updateHeaderControlsGeometry(width());
  203. }, lifetime());
  204. }
  205. void Members::setupList() {
  206. auto topSkip = _header ? _header->height() : 0;
  207. _listController->setStyleOverrides(&st::infoMembersList);
  208. _listController->setStoriesShown(true);
  209. _list = object_ptr<ListWidget>(
  210. this,
  211. _listController.get());
  212. _list->scrollToRequests(
  213. ) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
  214. auto addmin = (request.ymin < 0 || !_header)
  215. ? 0
  216. : _header->height();
  217. auto addmax = (request.ymax < 0 || !_header)
  218. ? 0
  219. : _header->height();
  220. _scrollToRequests.fire({
  221. request.ymin + addmin,
  222. request.ymax + addmax });
  223. }, _list->lifetime());
  224. widthValue(
  225. ) | rpl::start_with_next([this](int newWidth) {
  226. _list->resizeToWidth(newWidth);
  227. }, _list->lifetime());
  228. _list->heightValue(
  229. ) | rpl::start_with_next([=](int listHeight) {
  230. auto newHeight = (listHeight > st::membersMarginBottom)
  231. ? (topSkip
  232. + listHeight
  233. + st::membersMarginBottom)
  234. : 0;
  235. resize(width(), newHeight);
  236. }, _list->lifetime());
  237. _list->moveToLeft(0, topSkip);
  238. }
  239. int Members::resizeGetHeight(int newWidth) {
  240. if (_header) {
  241. updateHeaderControlsGeometry(newWidth);
  242. }
  243. return heightNoMargins();
  244. }
  245. //void Members::updateSearchEnabledByContent() {
  246. // _controller->setSearchEnabledByContent(
  247. // peerListFullRowsCount() >= kEnableSearchMembersAfterCount);
  248. //}
  249. void Members::updateHeaderControlsGeometry(int newWidth) {
  250. _openMembers->setGeometry(0, st::infoProfileSkip, newWidth, st::infoMembersButton.height);
  251. auto availableWidth = newWidth
  252. - st::infoMembersButtonPosition.x();
  253. //auto cancelLeft = availableWidth - _cancelSearch->width();
  254. //_cancelSearch->moveToLeft(
  255. // cancelLeft,
  256. // st::infoMembersButtonPosition.y());
  257. //auto searchShownLeft = st::infoIconPosition.x()
  258. // - st::infoMembersSearch.iconPosition.x();
  259. //auto searchHiddenLeft = availableWidth - _search->width();
  260. //auto searchShown = _searchShownAnimation.value(_searchShown ? 1. : 0.);
  261. //auto searchCurrentLeft = anim::interpolate(
  262. // searchHiddenLeft,
  263. // searchShownLeft,
  264. // searchShown);
  265. //_search->moveToLeft(
  266. // searchCurrentLeft,
  267. // st::infoMembersButtonPosition.y());
  268. //if (!_search->isHidden()) {
  269. // availableWidth -= st::infoMembersSearch.width;
  270. //}
  271. _addMember->moveToLeft(
  272. availableWidth - _addMember->width(),
  273. st::infoMembersButtonPosition.y(),
  274. newWidth);
  275. if (!_addMember->isHidden()) {
  276. availableWidth -= st::infoMembersSearch.width;
  277. }
  278. _search->moveToLeft(
  279. availableWidth - _search->width(),
  280. st::infoMembersButtonPosition.y(),
  281. newWidth);
  282. //auto fieldLeft = anim::interpolate(
  283. // cancelLeft,
  284. // st::infoBlockHeaderPosition.x(),
  285. // searchShown);
  286. //_searchField->setGeometryToLeft(
  287. // fieldLeft,
  288. // st::infoMembersSearchTop,
  289. // cancelLeft - fieldLeft,
  290. // _searchField->height());
  291. //_titleWrap->resize(
  292. // searchCurrentLeft - st::infoBlockHeaderPosition.x(),
  293. // _title->height());
  294. _titleWrap->resize(
  295. availableWidth - _addMember->width() - st::infoBlockHeaderPosition.x(),
  296. _title->height());
  297. _titleWrap->moveToLeft(
  298. st::infoBlockHeaderPosition.x(),
  299. st::infoBlockHeaderPosition.y(),
  300. newWidth);
  301. _titleWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
  302. //_title->resizeToWidth(searchHiddenLeft);
  303. _title->resizeToWidth(_titleWrap->width());
  304. _title->moveToLeft(0, 0);
  305. }
  306. void Members::addMember() {
  307. if (const auto chat = _peer->asChat()) {
  308. AddParticipantsBoxController::Start(_controller, chat);
  309. } else if (const auto channel = _peer->asChannel()) {
  310. const auto state = _listController->saveState();
  311. const auto users = ranges::views::all(
  312. state->list
  313. ) | ranges::views::transform([](not_null<PeerData*> peer) {
  314. return peer->asUser();
  315. }) | ranges::to_vector;
  316. AddParticipantsBoxController::Start(
  317. _controller,
  318. channel,
  319. { users.begin(), users.end() });
  320. }
  321. }
  322. void Members::showMembersWithSearch(bool withSearch) {
  323. //if (!_searchShown) {
  324. // toggleSearch();
  325. //}
  326. auto contentMemento = std::make_shared<Info::Members::Memento>(
  327. _controller);
  328. contentMemento->setState(saveState());
  329. contentMemento->setSearchStartsFocused(withSearch);
  330. auto mementoStack = std::vector<std::shared_ptr<ContentMemento>>();
  331. mementoStack.push_back(std::move(contentMemento));
  332. _controller->showSection(
  333. std::make_shared<Info::Memento>(std::move(mementoStack)));
  334. }
  335. //void Members::toggleSearch(anim::type animated) {
  336. // _searchShown = !_searchShown;
  337. // _cancelSearch->toggle(_searchShown, animated);
  338. // if (animated == anim::type::normal) {
  339. // _searchShownAnimation.start(
  340. // [this] { searchAnimationCallback(); },
  341. // _searchShown ? 0. : 1.,
  342. // _searchShown ? 1. : 0.,
  343. // st::slideWrapDuration);
  344. // } else {
  345. // _searchShownAnimation.finish();
  346. // searchAnimationCallback();
  347. // }
  348. // _search->setDisabled(_searchShown);
  349. // if (_searchShown) {
  350. // _searchField->show();
  351. // _searchField->setFocus();
  352. // } else {
  353. // setFocus();
  354. // }
  355. //}
  356. //
  357. //void Members::searchAnimationCallback() {
  358. // if (!_searchShownAnimation.animating()) {
  359. // _searchField->setVisible(_searchShown);
  360. // updateSearchOverrides();
  361. // _search->setPointerCursor(!_searchShown);
  362. // }
  363. // updateHeaderControlsGeometry(width());
  364. //}
  365. //
  366. //void Members::updateSearchOverrides() {
  367. // auto iconOverride = !_searchShown
  368. // ? nullptr
  369. // : (_wrap == Wrap::Layer)
  370. // ? &st::infoMembersSearchActiveLayer
  371. // : &st::infoMembersSearchActive;
  372. // _search->setIconOverride(iconOverride, iconOverride);
  373. //}
  374. //
  375. //void Members::cancelSearch() {
  376. // if (_searchShown) {
  377. // if (!_searchField->getLastText().isEmpty()) {
  378. // _searchField->setText(QString());
  379. // _searchField->setFocus();
  380. // } else {
  381. // toggleSearch();
  382. // }
  383. // }
  384. //}
  385. void Members::visibleTopBottomUpdated(
  386. int visibleTop,
  387. int visibleBottom) {
  388. setChildVisibleTopBottom(_list, visibleTop, visibleBottom);
  389. }
  390. void Members::peerListSetTitle(rpl::producer<QString> title) {
  391. }
  392. void Members::peerListSetAdditionalTitle(rpl::producer<QString> title) {
  393. }
  394. bool Members::peerListIsRowChecked(not_null<PeerListRow*> row) {
  395. return false;
  396. }
  397. int Members::peerListSelectedRowsCount() {
  398. return 0;
  399. }
  400. void Members::peerListScrollToTop() {
  401. _scrollToRequests.fire({ -1, -1 });
  402. }
  403. void Members::peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) {
  404. Unexpected("Item selection in Info::Profile::Members.");
  405. }
  406. void Members::peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) {
  407. Unexpected("Item selection in Info::Profile::Members.");
  408. }
  409. void Members::peerListFinishSelectedRowsBunch() {
  410. }
  411. std::shared_ptr<Main::SessionShow> Members::peerListUiShow() {
  412. return _show;
  413. }
  414. void Members::peerListSetDescription(
  415. object_ptr<Ui::FlatLabel> description) {
  416. description.destroy();
  417. }
  418. } // namespace Profile
  419. } // namespace Info