| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "dialogs/ui/dialogs_suggestions.h"
- #include "api/api_chat_participants.h"
- #include "apiwrap.h"
- #include "base/unixtime.h"
- #include "base/qt/qt_key_modifiers.h"
- #include "boxes/peer_list_box.h"
- #include "core/application.h"
- #include "data/components/recent_peers.h"
- #include "data/components/top_peers.h"
- #include "data/data_changes.h"
- #include "data/data_channel.h"
- #include "data/data_chat.h"
- #include "data/data_download_manager.h"
- #include "data/data_folder.h"
- #include "data/data_peer_values.h"
- #include "data/data_session.h"
- #include "data/data_user.h"
- #include "dialogs/ui/chat_search_empty.h"
- #include "history/history.h"
- #include "info/downloads/info_downloads_widget.h"
- #include "info/media/info_media_widget.h"
- #include "info/info_controller.h"
- #include "info/info_memento.h"
- #include "info/info_wrap_widget.h"
- #include "inline_bots/bot_attach_web_view.h"
- #include "lang/lang_keys.h"
- #include "main/main_session.h"
- #include "settings/settings_common.h"
- #include "storage/storage_shared_media.h"
- #include "ui/boxes/confirm_box.h"
- #include "ui/effects/ripple_animation.h"
- #include "ui/text/text_utilities.h"
- #include "ui/widgets/menu/menu_add_action_callback_factory.h"
- #include "ui/widgets/buttons.h"
- #include "ui/widgets/discrete_sliders.h"
- #include "ui/widgets/elastic_scroll.h"
- #include "ui/widgets/labels.h"
- #include "ui/widgets/popup_menu.h"
- #include "ui/widgets/shadow.h"
- #include "ui/wrap/vertical_layout.h"
- #include "ui/wrap/slide_wrap.h"
- #include "ui/delayed_activation.h"
- #include "ui/dynamic_thumbnails.h"
- #include "ui/painter.h"
- #include "ui/search_field_controller.h"
- #include "ui/unread_badge_paint.h"
- #include "ui/ui_utility.h"
- #include "window/window_separate_id.h"
- #include "window/window_session_controller.h"
- #include "window/window_peer_menu.h"
- #include "styles/style_chat.h"
- #include "styles/style_dialogs.h"
- #include "styles/style_layers.h"
- #include "styles/style_menu_icons.h"
- #include "styles/style_window.h"
- namespace Dialogs {
- namespace {
- constexpr auto kCollapsedChannelsCount = 5;
- constexpr auto kProbablyMaxChannels = 1000;
- constexpr auto kCollapsedAppsCount = 5;
- constexpr auto kProbablyMaxApps = 100;
- constexpr auto kSearchQueryDelay = crl::time(900);
- class RecentRow final : public PeerListRow {
- public:
- explicit RecentRow(not_null<PeerData*> peer);
- bool refreshBadge();
- QSize rightActionSize() const override;
- QMargins rightActionMargins() const override;
- void rightActionPaint(
- Painter &p,
- int x,
- int y,
- int outerWidth,
- bool selected,
- bool actionSelected) override;
- bool rightActionDisabled() const override;
- void rightActionAddRipple(
- QPoint point,
- Fn<void()> updateCallback) override;
- void rightActionStopLastRipple() override;
- const style::PeerListItem &computeSt(
- const style::PeerListItem &st) const override;
- private:
- const not_null<History*> _history;
- std::unique_ptr<Ui::Text::String> _mainAppText;
- std::unique_ptr<Ui::RippleAnimation> _actionRipple;
- QString _badgeString;
- QSize _badgeSize;
- uint32 _counter : 30 = 0;
- uint32 _unread : 1 = 0;
- uint32 _muted : 1 = 0;
- };
- class ChannelRow final : public PeerListRow {
- public:
- using PeerListRow::PeerListRow;
- void setActive(bool active);
- const style::PeerListItem &computeSt(
- const style::PeerListItem &st) const override;
- private:
- bool _active = false;
- };
- struct EntryMenuDescriptor {
- not_null<Window::SessionController*> controller;
- not_null<PeerData*> peer;
- QString removeOneText;
- Fn<void()> removeOne;
- QString removeAllText;
- QString removeAllConfirm;
- Fn<void()> removeAll;
- };
- [[nodiscard]] Fn<void()> RemoveAllConfirm(
- not_null<Window::SessionController*> controller,
- QString removeAllConfirm,
- Fn<void()> removeAll) {
- return [=] {
- controller->show(Ui::MakeConfirmBox({
- .text = removeAllConfirm,
- .confirmed = [=](Fn<void()> close) { removeAll(); close(); }
- }));
- };
- }
- void FillEntryMenu(
- const Ui::Menu::MenuCallback &add,
- EntryMenuDescriptor &&descriptor) {
- const auto peer = descriptor.peer;
- const auto controller = descriptor.controller;
- const auto group = peer->isMegagroup();
- const auto channel = peer->isChannel();
- add(tr::lng_context_new_window(tr::now), [=] {
- Ui::PreventDelayedActivation();
- controller->showInNewWindow(peer);
- }, &st::menuIconNewWindow);
- Window::AddSeparatorAndShiftUp(add);
- const auto showHistoryText = group
- ? tr::lng_context_open_group(tr::now)
- : channel
- ? tr::lng_context_open_channel(tr::now)
- : tr::lng_profile_send_message(tr::now);
- add(showHistoryText, [=] {
- controller->showPeerHistory(peer);
- }, channel ? &st::menuIconChannel : &st::menuIconChatBubble);
- const auto viewProfileText = group
- ? tr::lng_context_view_group(tr::now)
- : channel
- ? tr::lng_context_view_channel(tr::now)
- : tr::lng_context_view_profile(tr::now);
- add(viewProfileText, [=] {
- controller->showPeerInfo(peer);
- }, channel ? &st::menuIconInfo : &st::menuIconProfile);
- add({ .separatorSt = &st::expandedMenuSeparator });
- add({
- .text = descriptor.removeOneText,
- .handler = descriptor.removeOne,
- .icon = &st::menuIconDeleteAttention,
- .isAttention = true,
- });
- if (!descriptor.removeAllText.isEmpty()) {
- add({
- .text = descriptor.removeAllText,
- .handler = RemoveAllConfirm(
- descriptor.controller,
- descriptor.removeAllConfirm,
- descriptor.removeAll),
- .icon = &st::menuIconCancelAttention,
- .isAttention = true,
- });
- }
- }
- RecentRow::RecentRow(not_null<PeerData*> peer)
- : PeerListRow(peer)
- , _history(peer->owner().history(peer))
- , _mainAppText([&]() -> std::unique_ptr<Ui::Text::String> {
- if (const auto user = peer->asUser()) {
- if (user->botInfo && user->botInfo->hasMainApp) {
- return std::make_unique<Ui::Text::String>(
- st::dialogRowOpenBotTextStyle,
- tr::lng_profile_open_app_short(tr::now));
- }
- }
- return nullptr;
- }()) {
- if (peer->isSelf() || peer->isRepliesChat() || peer->isVerifyCodes()) {
- setCustomStatus(u" "_q);
- } else if (const auto chat = peer->asChat()) {
- if (chat->count > 0) {
- setCustomStatus(
- tr::lng_chat_status_members(
- tr::now,
- lt_count_decimal,
- chat->count));
- }
- } else if (const auto channel = peer->asChannel()) {
- if (channel->membersCountKnown()) {
- setCustomStatus((channel->isBroadcast()
- ? tr::lng_chat_status_subscribers
- : tr::lng_chat_status_members)(
- tr::now,
- lt_count_decimal,
- channel->membersCount()));
- }
- }
- refreshBadge();
- }
- bool RecentRow::refreshBadge() {
- if (_history->peer->isSelf()) {
- return false;
- }
- auto result = false;
- const auto muted = _history->muted() ? 1 : 0;
- if (_muted != muted) {
- _muted = muted;
- if (_counter || _unread) {
- result = true;
- }
- }
- const auto badges = _history->chatListBadgesState();
- const auto unread = badges.unread ? 1 : 0;
- if (_counter != badges.unreadCounter || _unread != unread) {
- _counter = badges.unreadCounter;
- _unread = unread;
- result = true;
- _badgeString = !_counter
- ? (_unread ? u" "_q : QString())
- : (_counter < 1000)
- ? QString::number(_counter)
- : (QString::number(_counter / 1000) + 'K');
- if (_badgeString.isEmpty()) {
- _badgeSize = QSize();
- } else {
- auto st = Ui::UnreadBadgeStyle();
- const auto unreadRectHeight = st.size;
- const auto unreadWidth = st.font->width(_badgeString);
- _badgeSize = QSize(
- std::max(unreadWidth + 2 * st.padding, unreadRectHeight),
- unreadRectHeight);
- }
- }
- return result;
- }
- QSize RecentRow::rightActionSize() const {
- if (_mainAppText && _badgeSize.isEmpty()) {
- return QSize(
- _mainAppText->maxWidth() + _mainAppText->minHeight(),
- st::dialogRowOpenBotHeight);
- }
- return _badgeSize;
- }
- QMargins RecentRow::rightActionMargins() const {
- if (_mainAppText && _badgeSize.isEmpty()) {
- return QMargins(
- 0,
- st::dialogRowOpenBotRecentTop,
- st::dialogRowOpenBotRight,
- 0);
- }
- if (_badgeSize.isEmpty()) {
- return {};
- }
- const auto x = st::recentPeersItem.photoPosition.x();
- const auto y = (st::recentPeersItem.height - _badgeSize.height()) / 2;
- return QMargins(x, y, x, y);
- }
- void RecentRow::rightActionPaint(
- Painter &p,
- int x,
- int y,
- int outerWidth,
- bool selected,
- bool actionSelected) {
- if (_mainAppText && _badgeSize.isEmpty()) {
- const auto size = RecentRow::rightActionSize();
- p.setPen(Qt::NoPen);
- p.setBrush(actionSelected
- ? st::activeButtonBgOver
- : st::activeButtonBg);
- const auto radius = size.height() / 2;
- auto hq = PainterHighQualityEnabler(p);
- p.drawRoundedRect(QRect(QPoint(x, y), size), radius, radius);
- if (_actionRipple) {
- _actionRipple->paint(p, x, y, outerWidth);
- if (_actionRipple->empty()) {
- _actionRipple.reset();
- }
- }
- p.setPen(actionSelected
- ? st::activeButtonFgOver
- : st::activeButtonFg);
- const auto top = 0
- + (st::dialogRowOpenBotHeight - _mainAppText->minHeight()) / 2;
- _mainAppText->draw(p, {
- .position = QPoint(x + size.height() / 2, y + top),
- .outerWidth = outerWidth,
- .availableWidth = outerWidth,
- .elisionLines = 1,
- });
- }
- if (!_counter && !_unread) {
- return;
- } else if (_badgeString.isEmpty()) {
- _badgeString = !_counter
- ? u" "_q
- : (_counter < 1000)
- ? QString::number(_counter)
- : (QString::number(_counter / 1000) + 'K');
- }
- auto st = Ui::UnreadBadgeStyle();
- st.selected = selected;
- st.muted = _muted;
- const auto &counter = _badgeString;
- PaintUnreadBadge(p, counter, x + _badgeSize.width(), y, st);
- }
- bool RecentRow::rightActionDisabled() const {
- return !_mainAppText || !_badgeSize.isEmpty();
- }
- void RecentRow::rightActionAddRipple(
- QPoint point,
- Fn<void()> updateCallback) {
- if (!_mainAppText || !_badgeSize.isEmpty()) {
- return;
- }
- if (!_actionRipple) {
- const auto size = rightActionSize();
- const auto radius = size.height() / 2;
- auto mask = Ui::RippleAnimation::RoundRectMask(size, radius);
- _actionRipple = std::make_unique<Ui::RippleAnimation>(
- st::defaultActiveButton.ripple,
- std::move(mask),
- std::move(updateCallback));
- }
- _actionRipple->add(point);
- }
- void RecentRow::rightActionStopLastRipple() {
- if (_actionRipple) {
- _actionRipple->lastStop();
- }
- }
- const style::PeerListItem &RecentRow::computeSt(
- const style::PeerListItem &st) const {
- return (peer()->isSelf()
- || peer()->isRepliesChat()
- || peer()->isVerifyCodes())
- ? st::recentPeersSpecialName
- : st;
- }
- void ChannelRow::setActive(bool active) {
- _active = active;
- }
- const style::PeerListItem &ChannelRow::computeSt(
- const style::PeerListItem &st) const {
- return _active ? st::recentPeersItemActive : st::recentPeersItem;
- }
- } // namespace
- class Suggestions::ObjectListController
- : public PeerListController
- , public base::has_weak_ptr {
- public:
- explicit ObjectListController(
- not_null<Window::SessionController*> window);
- [[nodiscard]] not_null<Window::SessionController*> window() const {
- return _window;
- }
- [[nodiscard]] rpl::producer<int> count() const {
- return _count.value();
- }
- [[nodiscard]] rpl::producer<not_null<PeerData*>> chosen() const {
- return _chosen.events();
- }
- Main::Session &session() const override {
- return _window->session();
- }
- void rowClicked(not_null<PeerListRow*> row) override;
- void rowMiddleClicked(not_null<PeerListRow*> row) override;
- bool rowTrackPress(not_null<PeerListRow*> row) override;
- void rowTrackPressCancel() override;
- bool rowTrackPressSkipMouseSelection() override;
- bool processTouchEvent(not_null<QTouchEvent*> e);
- void setupTouchChatPreview(not_null<Ui::ElasticScroll*> scroll);
- protected:
- [[nodiscard]] int countCurrent() const;
- void setCount(int count);
- [[nodiscard]] bool expandedCurrent() const;
- [[nodiscard]] rpl::producer<bool> expanded() const;
- void setupPlainDivider(rpl::producer<QString> title);
- void setupExpandDivider(rpl::producer<QString> title);
- private:
- const not_null<Window::SessionController*> _window;
- std::optional<QPoint> _chatPreviewTouchGlobal;
- rpl::event_stream<> _touchCancelRequests;
- rpl::event_stream<not_null<PeerData*>> _chosen;
- rpl::variable<int> _count;
- rpl::variable<Ui::RpWidget*> _toggleExpanded = nullptr;
- rpl::variable<bool> _expanded = false;
- };
- class RecentsController final : public Suggestions::ObjectListController {
- public:
- using RightActionCallback = Fn<void(not_null<PeerData*>)>;
- RecentsController(
- not_null<Window::SessionController*> window,
- RecentPeersList list,
- RightActionCallback rightActionCallback);
- void prepare() override;
- base::unique_qptr<Ui::PopupMenu> rowContextMenu(
- QWidget *parent,
- not_null<PeerListRow*> row) override;
- void rowRightActionClicked(not_null<PeerListRow*> row) override;
- QString savedMessagesChatStatus() const override;
- private:
- void setupDivider();
- void subscribeToEvents();
- [[nodiscard]] Fn<void()> removeAllCallback();
- RecentPeersList _recent;
- RightActionCallback _rightActionCallback;
- rpl::lifetime _lifetime;
- };
- class MyChannelsController final
- : public Suggestions::ObjectListController {
- public:
- explicit MyChannelsController(
- not_null<Window::SessionController*> window);
- void prepare() override;
- base::unique_qptr<Ui::PopupMenu> rowContextMenu(
- QWidget *parent,
- not_null<PeerListRow*> row) override;
- private:
- void appendRow(not_null<ChannelData*> channel);
- void fill(bool force = false);
- std::vector<not_null<History*>> _channels;
- rpl::lifetime _lifetime;
- };
- class RecommendationsController final
- : public Suggestions::ObjectListController {
- public:
- explicit RecommendationsController(
- not_null<Window::SessionController*> window);
- void prepare() override;
- void load();
- private:
- void fill();
- void appendRow(not_null<ChannelData*> channel);
- History *_activeHistory = nullptr;
- bool _requested = false;
- rpl::lifetime _lifetime;
- };
- class RecentAppsController final
- : public Suggestions::ObjectListController {
- public:
- explicit RecentAppsController(
- not_null<Window::SessionController*> window);
- void prepare() override;
- base::unique_qptr<Ui::PopupMenu> rowContextMenu(
- QWidget *parent,
- not_null<PeerListRow*> row) override;
- void load();
- [[nodiscard]] rpl::producer<> refreshed() const;
- [[nodiscard]] bool shown(not_null<PeerData*> peer) const;
- private:
- void appendRow(not_null<UserData*> bot);
- void fill();
- std::vector<not_null<UserData*>> _bots;
- rpl::event_stream<> _refreshed;
- rpl::lifetime _lifetime;
- };
- class PopularAppsController final
- : public Suggestions::ObjectListController {
- public:
- PopularAppsController(
- not_null<Window::SessionController*> window,
- Fn<bool(not_null<PeerData*>)> filterOut,
- rpl::producer<> filterOutRefreshes);
- void prepare() override;
- void load();
- private:
- void fill();
- void appendRow(not_null<UserData*> bot);
- Fn<bool(not_null<PeerData*>)> _filterOut;
- rpl::producer<> _filterOutRefreshes;
- History *_activeHistory = nullptr;
- bool _requested = false;
- rpl::lifetime _lifetime;
- };
- Suggestions::ObjectListController::ObjectListController(
- not_null<Window::SessionController*> window)
- : _window(window) {
- }
- bool Suggestions::ObjectListController::rowTrackPress(
- not_null<PeerListRow*> row) {
- const auto peer = row->peer();
- const auto history = peer->owner().history(peer);
- const auto callback = crl::guard(this, [=](bool shown) {
- delegate()->peerListPressLeftToContextMenu(shown);
- });
- if (base::IsAltPressed()) {
- _window->showChatPreview(
- { history, FullMsgId() },
- callback,
- nullptr,
- _chatPreviewTouchGlobal);
- return false;
- }
- const auto point = delegate()->peerListLastRowMousePosition();
- const auto &st = computeListSt().item;
- if (point && point->x() < st.photoPosition.x() + st.photoSize) {
- _window->scheduleChatPreview(
- { history, FullMsgId() },
- callback,
- nullptr,
- _chatPreviewTouchGlobal);
- return true;
- }
- return false;
- }
- void Suggestions::ObjectListController::rowTrackPressCancel() {
- _chatPreviewTouchGlobal = {};
- _window->cancelScheduledPreview();
- }
- bool Suggestions::ObjectListController::rowTrackPressSkipMouseSelection() {
- return _chatPreviewTouchGlobal.has_value();
- }
- bool Suggestions::ObjectListController::processTouchEvent(
- not_null<QTouchEvent*> e) {
- const auto point = e->touchPoints().empty()
- ? std::optional<QPoint>()
- : e->touchPoints().front().screenPos().toPoint();
- switch (e->type()) {
- case QEvent::TouchBegin: {
- if (!point) {
- return false;
- }
- _chatPreviewTouchGlobal = point;
- if (!delegate()->peerListTrackRowPressFromGlobal(*point)) {
- _chatPreviewTouchGlobal = {};
- }
- } break;
- case QEvent::TouchUpdate: {
- if (!point) {
- return false;
- }
- if (_chatPreviewTouchGlobal) {
- const auto delta = (*_chatPreviewTouchGlobal - *point);
- if (delta.manhattanLength() > computeListSt().item.photoSize) {
- rowTrackPressCancel();
- }
- }
- } break;
- case QEvent::TouchEnd:
- case QEvent::TouchCancel: {
- if (_chatPreviewTouchGlobal) {
- rowTrackPressCancel();
- }
- } break;
- }
- return false;
- }
- void Suggestions::ObjectListController::setupTouchChatPreview(
- not_null<Ui::ElasticScroll*> scroll) {
- _touchCancelRequests.events() | rpl::start_with_next([=] {
- QTouchEvent ev(QEvent::TouchCancel);
- ev.setTimestamp(crl::now());
- QGuiApplication::sendEvent(scroll, &ev);
- }, lifetime());
- }
- int Suggestions::ObjectListController::countCurrent() const {
- return _count.current();
- }
- void Suggestions::ObjectListController::setCount(int count) {
- _count = count;
- }
- bool Suggestions::ObjectListController::expandedCurrent() const {
- return _expanded.current();
- }
- rpl::producer<bool> Suggestions::ObjectListController::expanded() const {
- return _expanded.value();
- }
- void Suggestions::ObjectListController::rowClicked(
- not_null<PeerListRow*> row) {
- _chosen.fire(row->peer());
- }
- void Suggestions::ObjectListController::rowMiddleClicked(
- not_null<PeerListRow*> row) {
- window()->showInNewWindow(row->peer());
- }
- void Suggestions::ObjectListController::setupPlainDivider(
- rpl::producer<QString> title) {
- auto result = object_ptr<Ui::FixedHeightWidget>(
- (QWidget*)nullptr,
- st::searchedBarHeight);
- const auto raw = result.data();
- const auto label = Ui::CreateChild<Ui::FlatLabel>(
- raw,
- std::move(title),
- st::searchedBarLabel);
- raw->sizeValue(
- ) | rpl::start_with_next([=](QSize size) {
- const auto x = st::searchedBarPosition.x();
- const auto y = st::searchedBarPosition.y();
- label->resizeToWidth(size.width() - x * 2);
- label->moveToLeft(x, y, size.width());
- }, raw->lifetime());
- raw->paintRequest() | rpl::start_with_next([=](QRect clip) {
- QPainter(raw).fillRect(clip, st::searchedBarBg);
- }, raw->lifetime());
- delegate()->peerListSetAboveWidget(std::move(result));
- }
- void Suggestions::ObjectListController::setupExpandDivider(
- rpl::producer<QString> title) {
- auto result = object_ptr<Ui::FixedHeightWidget>(
- (QWidget*)nullptr,
- st::searchedBarHeight);
- const auto raw = result.data();
- const auto label = Ui::CreateChild<Ui::FlatLabel>(
- raw,
- std::move(title),
- st::searchedBarLabel);
- count(
- ) | rpl::map(
- rpl::mappers::_1 > kCollapsedChannelsCount
- ) | rpl::distinct_until_changed() | rpl::start_with_next([=](bool more) {
- _expanded = false;
- if (!more) {
- const auto toggle = _toggleExpanded.current();
- _toggleExpanded = nullptr;
- delete toggle;
- return;
- } else if (_toggleExpanded.current()) {
- return;
- }
- const auto toggle = Ui::CreateChild<Ui::LinkButton>(
- raw,
- tr::lng_channels_your_more(tr::now),
- st::searchedBarLink);
- toggle->show();
- toggle->setClickedCallback([=] {
- const auto expand = !_expanded.current();
- toggle->setText(expand
- ? tr::lng_channels_your_less(tr::now)
- : tr::lng_channels_your_more(tr::now));
- _expanded = expand;
- });
- rpl::combine(
- raw->sizeValue(),
- toggle->widthValue()
- ) | rpl::start_with_next([=](QSize size, int width) {
- const auto x = st::searchedBarPosition.x();
- const auto y = st::searchedBarPosition.y();
- toggle->moveToRight(0, 0, size.width());
- label->resizeToWidth(size.width() - x - width);
- label->moveToLeft(x, y, size.width());
- }, toggle->lifetime());
- _toggleExpanded = toggle;
- }, raw->lifetime());
- rpl::combine(
- raw->sizeValue(),
- _toggleExpanded.value()
- ) | rpl::filter(
- rpl::mappers::_2 == nullptr
- ) | rpl::start_with_next([=](QSize size, const auto) {
- const auto x = st::searchedBarPosition.x();
- const auto y = st::searchedBarPosition.y();
- label->resizeToWidth(size.width() - x * 2);
- label->moveToLeft(x, y, size.width());
- }, raw->lifetime());
- raw->paintRequest() | rpl::start_with_next([=](QRect clip) {
- QPainter(raw).fillRect(clip, st::searchedBarBg);
- }, raw->lifetime());
- delegate()->peerListSetAboveWidget(std::move(result));
- }
- RecentsController::RecentsController(
- not_null<Window::SessionController*> window,
- RecentPeersList list,
- RightActionCallback rightActionCallback)
- : ObjectListController(window)
- , _recent(std::move(list))
- , _rightActionCallback(std::move(rightActionCallback)) {
- }
- void RecentsController::prepare() {
- setupDivider();
- for (const auto &peer : _recent.list) {
- delegate()->peerListAppendRow(std::make_unique<RecentRow>(peer));
- }
- delegate()->peerListRefreshRows();
- setCount(_recent.list.size());
- subscribeToEvents();
- }
- Fn<void()> RecentsController::removeAllCallback() {
- const auto weak = base::make_weak(this);
- const auto session = &this->session();
- return crl::guard(session, [=] {
- if (weak) {
- setCount(0);
- while (delegate()->peerListFullRowsCount() > 0) {
- delegate()->peerListRemoveRow(delegate()->peerListRowAt(0));
- }
- delegate()->peerListRefreshRows();
- }
- session->recentPeers().clear();
- });
- }
- base::unique_qptr<Ui::PopupMenu> RecentsController::rowContextMenu(
- QWidget *parent,
- not_null<PeerListRow*> row) {
- auto result = base::make_unique_q<Ui::PopupMenu>(
- parent,
- st::popupMenuWithIcons);
- const auto peer = row->peer();
- const auto weak = base::make_weak(this);
- const auto session = &this->session();
- const auto removeOne = crl::guard(session, [=] {
- if (weak) {
- const auto rowId = peer->id.value;
- if (const auto row = delegate()->peerListFindRow(rowId)) {
- setCount(std::max(0, countCurrent() - 1));
- delegate()->peerListRemoveRow(row);
- delegate()->peerListRefreshRows();
- }
- }
- session->recentPeers().remove(peer);
- });
- FillEntryMenu(Ui::Menu::CreateAddActionCallback(result), {
- .controller = window(),
- .peer = peer,
- .removeOneText = tr::lng_recent_remove(tr::now),
- .removeOne = removeOne,
- .removeAllText = tr::lng_recent_clear_all(tr::now),
- .removeAllConfirm = tr::lng_recent_clear_sure(tr::now),
- .removeAll = removeAllCallback(),
- });
- return result;
- }
- void RecentsController::rowRightActionClicked(not_null<PeerListRow*> row) {
- if (_rightActionCallback) {
- if (const auto peer = row->peer()) {
- _rightActionCallback(peer);
- }
- }
- }
- QString RecentsController::savedMessagesChatStatus() const {
- return tr::lng_saved_forward_here(tr::now);
- }
- void RecentsController::setupDivider() {
- auto result = object_ptr<Ui::FixedHeightWidget>(
- (QWidget*)nullptr,
- st::searchedBarHeight);
- const auto raw = result.data();
- const auto label = Ui::CreateChild<Ui::FlatLabel>(
- raw,
- tr::lng_recent_title(),
- st::searchedBarLabel);
- const auto clear = Ui::CreateChild<Ui::LinkButton>(
- raw,
- tr::lng_recent_clear(tr::now),
- st::searchedBarLink);
- clear->setClickedCallback(RemoveAllConfirm(
- window(),
- tr::lng_recent_clear_sure(tr::now),
- removeAllCallback()));
- rpl::combine(
- raw->sizeValue(),
- clear->widthValue()
- ) | rpl::start_with_next([=](QSize size, int width) {
- const auto x = st::searchedBarPosition.x();
- const auto y = st::searchedBarPosition.y();
- clear->moveToRight(0, 0, size.width());
- label->resizeToWidth(size.width() - x - width);
- label->moveToLeft(x, y, size.width());
- }, raw->lifetime());
- raw->paintRequest() | rpl::start_with_next([=](QRect clip) {
- QPainter(raw).fillRect(clip, st::searchedBarBg);
- }, raw->lifetime());
- delegate()->peerListSetAboveWidget(std::move(result));
- }
- void RecentsController::subscribeToEvents() {
- using Flag = Data::PeerUpdate::Flag;
- session().changes().peerUpdates(
- Flag::Notifications
- | Flag::OnlineStatus
- ) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
- const auto peer = update.peer;
- if (peer->isSelf()) {
- return;
- }
- auto refreshed = false;
- const auto row = delegate()->peerListFindRow(update.peer->id.value);
- if (!row) {
- return;
- } else if (update.flags & Flag::Notifications) {
- refreshed = static_cast<RecentRow*>(row)->refreshBadge();
- }
- if (!peer->isRepliesChat()
- && !peer->isVerifyCodes()
- && (update.flags & Flag::OnlineStatus)) {
- row->clearCustomStatus();
- refreshed = true;
- }
- if (refreshed) {
- delegate()->peerListUpdateRow(row);
- }
- }, _lifetime);
- session().data().unreadBadgeChanges(
- ) | rpl::start_with_next([=] {
- for (auto i = 0; i != countCurrent(); ++i) {
- const auto row = delegate()->peerListRowAt(i);
- if (static_cast<RecentRow*>(row.get())->refreshBadge()) {
- delegate()->peerListUpdateRow(row);
- }
- }
- }, _lifetime);
- }
- MyChannelsController::MyChannelsController(
- not_null<Window::SessionController*> window)
- : ObjectListController(window) {
- }
- void MyChannelsController::prepare() {
- setupExpandDivider(tr::lng_channels_your_title());
- session().changes().peerUpdates(
- Data::PeerUpdate::Flag::ChannelAmIn
- ) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
- const auto channel = update.peer->asBroadcast();
- if (!channel || channel->amIn()) {
- return;
- }
- const auto history = channel->owner().history(channel);
- const auto i = ranges::remove(_channels, history);
- if (i == end(_channels)) {
- return;
- }
- _channels.erase(i, end(_channels));
- const auto row = delegate()->peerListFindRow(channel->id.value);
- if (row) {
- delegate()->peerListRemoveRow(row);
- }
- setCount(_channels.size());
- fill(true);
- }, _lifetime);
- _channels.reserve(kProbablyMaxChannels);
- const auto owner = &session().data();
- const auto add = [&](not_null<Dialogs::MainList*> list) {
- for (const auto &row : list->indexed()->all()) {
- if (const auto history = row->history()) {
- if (const auto channel = history->peer->asBroadcast()) {
- _channels.push_back(history);
- }
- }
- }
- };
- add(owner->chatsList());
- if (const auto folder = owner->folderLoaded(Data::Folder::kId)) {
- add(owner->chatsList(folder));
- }
- ranges::sort(_channels, ranges::greater(), &History::chatListTimeId);
- setCount(_channels.size());
- expanded() | rpl::start_with_next([=] {
- fill();
- }, _lifetime);
- auto loading = owner->chatsListChanges(
- ) | rpl::take_while([=](Data::Folder *folder) {
- return !owner->chatsListLoaded(folder);
- });
- rpl::merge(
- std::move(loading),
- owner->chatsListLoadedEvents()
- ) | rpl::start_with_next([=](Data::Folder *folder) {
- const auto list = owner->chatsList(folder);
- for (const auto &row : list->indexed()->all()) {
- if (const auto history = row->history()) {
- if (const auto channel = history->peer->asBroadcast()) {
- if (ranges::contains(_channels, not_null(history))) {
- _channels.push_back(history);
- }
- }
- }
- }
- const auto was = countCurrent();
- const auto now = int(_channels.size());
- if (was != now) {
- setCount(now);
- fill();
- }
- }, _lifetime);
- }
- void MyChannelsController::fill(bool force) {
- const auto count = countCurrent();
- const auto limit = expandedCurrent()
- ? count
- : std::min(count, kCollapsedChannelsCount);
- const auto already = delegate()->peerListFullRowsCount();
- const auto delta = limit - already;
- if (!delta && !force) {
- return;
- } else if (delta > 0) {
- for (auto i = already; i != limit; ++i) {
- appendRow(_channels[i]->peer->asBroadcast());
- }
- } else if (delta < 0) {
- for (auto i = already; i != limit;) {
- delegate()->peerListRemoveRow(delegate()->peerListRowAt(--i));
- }
- }
- delegate()->peerListRefreshRows();
- }
- void MyChannelsController::appendRow(not_null<ChannelData*> channel) {
- auto row = std::make_unique<PeerListRow>(channel);
- if (channel->membersCountKnown()) {
- row->setCustomStatus((channel->isBroadcast()
- ? tr::lng_chat_status_subscribers
- : tr::lng_chat_status_members)(
- tr::now,
- lt_count_decimal,
- channel->membersCount()));
- }
- delegate()->peerListAppendRow(std::move(row));
- }
- base::unique_qptr<Ui::PopupMenu> MyChannelsController::rowContextMenu(
- QWidget *parent,
- not_null<PeerListRow*> row) {
- auto result = base::make_unique_q<Ui::PopupMenu>(
- parent,
- st::popupMenuWithIcons);
- const auto peer = row->peer();
- const auto addAction = Ui::Menu::CreateAddActionCallback(result);
- Window::FillDialogsEntryMenu(
- window(),
- Dialogs::EntryState{
- .key = peer->owner().history(peer),
- .section = Dialogs::EntryState::Section::ContextMenu,
- },
- addAction);
- return result;
- }
- RecommendationsController::RecommendationsController(
- not_null<Window::SessionController*> window)
- : ObjectListController(window) {
- }
- void RecommendationsController::prepare() {
- setupPlainDivider(tr::lng_channels_recommended());
- fill();
- }
- void RecommendationsController::load() {
- if (_requested || countCurrent()) {
- return;
- }
- _requested = true;
- const auto participants = &session().api().chatParticipants();
- participants->loadRecommendations();
- participants->recommendationsLoaded(
- ) | rpl::take(1) | rpl::start_with_next([=] {
- fill();
- }, _lifetime);
- }
- void RecommendationsController::fill() {
- const auto participants = &session().api().chatParticipants();
- const auto &list = participants->recommendations().list;
- if (list.empty()) {
- return;
- }
- for (const auto &peer : list) {
- if (const auto channel = peer->asBroadcast()) {
- appendRow(channel);
- }
- }
- delegate()->peerListRefreshRows();
- setCount(delegate()->peerListFullRowsCount());
- window()->activeChatValue() | rpl::start_with_next([=](const Key &key) {
- const auto history = key.history();
- if (_activeHistory == history) {
- return;
- } else if (_activeHistory) {
- const auto id = _activeHistory->peer->id.value;
- if (const auto row = delegate()->peerListFindRow(id)) {
- static_cast<ChannelRow*>(row)->setActive(false);
- delegate()->peerListUpdateRow(row);
- }
- }
- _activeHistory = history;
- if (_activeHistory) {
- const auto id = _activeHistory->peer->id.value;
- if (const auto row = delegate()->peerListFindRow(id)) {
- static_cast<ChannelRow*>(row)->setActive(true);
- delegate()->peerListUpdateRow(row);
- }
- }
- }, _lifetime);
- }
- void RecommendationsController::appendRow(not_null<ChannelData*> channel) {
- auto row = std::make_unique<ChannelRow>(channel);
- if (channel->membersCountKnown()) {
- row->setCustomStatus((channel->isBroadcast()
- ? tr::lng_chat_status_subscribers
- : tr::lng_chat_status_members)(
- tr::now,
- lt_count_decimal,
- channel->membersCount()));
- }
- delegate()->peerListAppendRow(std::move(row));
- }
- RecentAppsController::RecentAppsController(
- not_null<Window::SessionController*> window)
- : ObjectListController(window) {
- }
- void RecentAppsController::prepare() {
- setupExpandDivider(tr::lng_bot_apps_your());
- _bots.reserve(kProbablyMaxApps);
- rpl::single() | rpl::then(
- session().topBotApps().updates()
- ) | rpl::start_with_next([=] {
- _bots.clear();
- for (const auto &peer : session().topBotApps().list()) {
- if (const auto bot = peer->asUser()) {
- if (bot->isBot() && !bot->isInaccessible()) {
- _bots.push_back(bot);
- }
- }
- }
- setCount(_bots.size());
- while (delegate()->peerListFullRowsCount()) {
- delegate()->peerListRemoveRow(delegate()->peerListRowAt(0));
- }
- fill();
- }, _lifetime);
- expanded() | rpl::skip(1) | rpl::start_with_next([=] {
- fill();
- }, _lifetime);
- }
- base::unique_qptr<Ui::PopupMenu> RecentAppsController::rowContextMenu(
- QWidget *parent,
- not_null<PeerListRow*> row) {
- auto result = base::make_unique_q<Ui::PopupMenu>(
- parent,
- st::popupMenuWithIcons);
- const auto peer = row->peer();
- const auto weak = base::make_weak(this);
- const auto session = &this->session();
- const auto removeOne = crl::guard(session, [=] {
- if (weak) {
- const auto rowId = peer->id.value;
- if (const auto row = delegate()->peerListFindRow(rowId)) {
- setCount(std::max(0, countCurrent() - 1));
- delegate()->peerListRemoveRow(row);
- delegate()->peerListRefreshRows();
- }
- }
- session->topBotApps().remove(peer);
- });
- FillEntryMenu(Ui::Menu::CreateAddActionCallback(result), {
- .controller = window(),
- .peer = peer,
- .removeOneText = tr::lng_recent_remove(tr::now),
- .removeOne = removeOne,
- });
- return result;
- }
- void RecentAppsController::load() {
- session().topBotApps().reload();
- }
- rpl::producer<> RecentAppsController::refreshed() const {
- return _refreshed.events();
- }
- bool RecentAppsController::shown(not_null<PeerData*> peer) const {
- return delegate()->peerListFindRow(peer->id.value) != nullptr;
- }
- void RecentAppsController::fill() {
- const auto count = countCurrent();
- const auto limit = expandedCurrent()
- ? count
- : std::min(count, kCollapsedAppsCount);
- const auto already = delegate()->peerListFullRowsCount();
- const auto delta = limit - already;
- if (!delta) {
- return;
- } else if (delta > 0) {
- for (auto i = already; i != limit; ++i) {
- appendRow(_bots[i]);
- }
- } else if (delta < 0) {
- for (auto i = already; i != limit;) {
- delegate()->peerListRemoveRow(delegate()->peerListRowAt(--i));
- }
- }
- delegate()->peerListRefreshRows();
- _refreshed.fire({});
- }
- void RecentAppsController::appendRow(not_null<UserData*> bot) {
- auto row = std::make_unique<PeerListRow>(bot);
- if (const auto count = bot->botInfo->activeUsers) {
- row->setCustomStatus(
- tr::lng_bot_status_users(tr::now, lt_count_decimal, count));
- }
- delegate()->peerListAppendRow(std::move(row));
- }
- PopularAppsController::PopularAppsController(
- not_null<Window::SessionController*> window,
- Fn<bool(not_null<PeerData*>)> filterOut,
- rpl::producer<> filterOutRefreshes)
- : ObjectListController(window)
- , _filterOut(std::move(filterOut))
- , _filterOutRefreshes(std::move(filterOutRefreshes)) {
- }
- void PopularAppsController::prepare() {
- if (_filterOut) {
- setupPlainDivider(tr::lng_bot_apps_popular());
- }
- rpl::single() | rpl::then(
- std::move(_filterOutRefreshes)
- ) | rpl::start_with_next([=] {
- fill();
- }, _lifetime);
- }
- void PopularAppsController::load() {
- if (_requested || countCurrent()) {
- return;
- }
- _requested = true;
- const auto attachWebView = &session().attachWebView();
- attachWebView->loadPopularAppBots();
- attachWebView->popularAppBotsLoaded(
- ) | rpl::take(1) | rpl::start_with_next([=] {
- fill();
- }, _lifetime);
- }
- void PopularAppsController::fill() {
- while (delegate()->peerListFullRowsCount()) {
- delegate()->peerListRemoveRow(delegate()->peerListRowAt(0));
- }
- for (const auto &bot : session().attachWebView().popularAppBots()) {
- if (!_filterOut || !_filterOut(bot)) {
- appendRow(bot);
- }
- }
- const auto count = delegate()->peerListFullRowsCount();
- setCount(count);
- if (count > 0) {
- delegate()->peerListSetBelowWidget(object_ptr<Ui::DividerLabel>(
- (QWidget*)nullptr,
- object_ptr<Ui::FlatLabel>(
- (QWidget*)nullptr,
- tr::lng_bot_apps_which(
- lt_link,
- tr::lng_bot_apps_which_link(
- ) | Ui::Text::ToLink(u"internal:about_popular_apps"_q),
- Ui::Text::WithEntities),
- st::dialogsPopularAppsAbout),
- st::dialogsPopularAppsPadding));
- }
- delegate()->peerListRefreshRows();
- }
- void PopularAppsController::appendRow(not_null<UserData*> bot) {
- auto row = std::make_unique<PeerListRow>(bot);
- if (bot->isBot()) {
- if (!bot->botInfo->activeUsers && !bot->username().isEmpty()) {
- row->setCustomStatus('@' + bot->username());
- }
- }
- delegate()->peerListAppendRow(std::move(row));
- }
- Suggestions::Suggestions(
- not_null<QWidget*> parent,
- not_null<Window::SessionController*> controller,
- rpl::producer<TopPeersList> topPeers,
- RecentPeersList recentPeers)
- : RpWidget(parent)
- , _controller(controller)
- , _tabsScroll(
- std::make_unique<Ui::ScrollArea>(this, st::dialogsTabsScroll, true))
- , _tabs(
- _tabsScroll->setOwnedWidget(
- object_ptr<Ui::SettingsSlider>(this, st::dialogsSearchTabs)))
- , _tabKeys(TabKeysFor(controller))
- , _chatsScroll(std::make_unique<Ui::ElasticScroll>(this))
- , _chatsContent(
- _chatsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
- , _topPeersWrap(
- _chatsContent->add(object_ptr<Ui::SlideWrap<TopPeersStrip>>(
- this,
- object_ptr<TopPeersStrip>(this, std::move(topPeers)))))
- , _topPeers(_topPeersWrap->entity())
- , _recent(setupRecentPeers(std::move(recentPeers)))
- , _emptyRecent(_chatsContent->add(setupEmptyRecent()))
- , _channelsScroll(std::make_unique<Ui::ElasticScroll>(this))
- , _channelsContent(
- _channelsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
- , _myChannels(setupMyChannels())
- , _recommendations(setupRecommendations())
- , _emptyChannels(_channelsContent->add(setupEmptyChannels()))
- , _appsScroll(std::make_unique<Ui::ElasticScroll>(this))
- , _appsContent(
- _appsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
- , _recentApps(setupRecentApps())
- , _popularApps(setupPopularApps())
- , _searchQueryTimer([=] { applySearchQuery(); }) {
- setupTabs();
- setupChats();
- setupChannels();
- setupApps();
- }
- Suggestions::~Suggestions() = default;
- void Suggestions::setupTabs() {
- _tabsScroll->setCustomWheelProcess([=](not_null<QWheelEvent*> e) {
- const auto pixelDelta = e->pixelDelta();
- const auto angleDelta = e->angleDelta();
- if (std::abs(pixelDelta.x()) + std::abs(angleDelta.x())) {
- return false;
- }
- const auto y = pixelDelta.y() ? pixelDelta.y() : angleDelta.y();
- _tabsScroll->scrollToX(_tabsScroll->scrollLeft() - y);
- return true;
- });
- const auto scrollToIndex = [=](int index, anim::type type) {
- const auto to = index
- ? (_tabs->centerOfSection(index) - _tabsScroll->width() / 2)
- : 0;
- _tabsScrollAnimation.stop();
- if (type == anim::type::instant) {
- _tabsScroll->scrollToX(to);
- } else {
- _tabsScrollAnimation.start(
- [=](float64 v) { _tabsScroll->scrollToX(v); },
- _tabsScroll->scrollLeft(),
- std::min(to, _tabsScroll->scrollLeftMax()),
- st::defaultTabsSlider.duration);
- }
- };
- rpl::single(-1) | rpl::then(
- _tabs->sectionActivated()
- ) | rpl::combine_previous(
- ) | rpl::start_with_next([=](int was, int index) {
- if (was != index) {
- scrollToIndex(index, anim::type::normal);
- }
- }, _tabs->lifetime());
- const auto shadow = Ui::CreateChild<Ui::PlainShadow>(this);
- shadow->lower();
- _tabsScroll->move(0, 0);
- _tabs->move(0, 0);
- rpl::combine(
- widthValue(),
- _tabs->heightValue()
- ) | rpl::start_with_next([=](int width, int height) {
- const auto line = st::lineWidth;
- shadow->setGeometry(0, height - line, width, line);
- }, shadow->lifetime());
- shadow->showOn(_tabsScroll->shownValue());
- const auto labels = base::flat_map<Key, QString>{
- { Key{ Tab::Chats }, tr::lng_recent_chats(tr::now) },
- { Key{ Tab::Channels }, tr::lng_recent_channels(tr::now) },
- { Key{ Tab::Apps }, tr::lng_recent_apps(tr::now) },
- { Key{ Tab::Media, MediaType::Photo }, tr::lng_all_photos(tr::now) },
- { Key{ Tab::Media, MediaType::Video }, tr::lng_all_videos(tr::now) },
- { Key{ Tab::Downloads }, tr::lng_all_downloads(tr::now) },
- { Key{ Tab::Media, MediaType::Link }, tr::lng_all_links(tr::now) },
- { Key{ Tab::Media, MediaType::File }, tr::lng_all_files(tr::now) },
- {
- Key{ Tab::Media, MediaType::MusicFile },
- tr::lng_all_music(tr::now),
- },
- {
- Key{ Tab::Media, MediaType::RoundVoiceFile },
- tr::lng_all_voice(tr::now),
- },
- };
- auto sections = std::vector<QString>();
- for (const auto key : _tabKeys) {
- const auto i = labels.find(key);
- Assert(i != end(labels));
- sections.push_back(i->second);
- }
- _tabs->setSections(sections);
- _tabs->sectionActivated(
- ) | rpl::start_with_next([=](int section) {
- Assert(section >= 0 && section < _tabKeys.size());
- switchTab(_tabKeys[section]);
- }, _tabs->lifetime());
- }
- void Suggestions::setupChats() {
- _recent->count.value() | rpl::start_with_next([=](int count) {
- _recent->wrap->toggle(count > 0, anim::type::instant);
- _emptyRecent->toggle(count == 0, anim::type::instant);
- }, _recent->wrap->lifetime());
- _topPeers->emptyValue() | rpl::start_with_next([=](bool empty) {
- _topPeersWrap->toggle(!empty, anim::type::instant);
- }, _topPeers->lifetime());
- _topPeers->clicks() | rpl::start_with_next([=](uint64 peerIdRaw) {
- const auto peerId = PeerId(peerIdRaw);
- _topPeerChosen.fire(_controller->session().data().peer(peerId));
- }, _topPeers->lifetime());
- _topPeers->pressed() | rpl::start_with_next([=](uint64 peerIdRaw) {
- handlePressForChatPreview(PeerId(peerIdRaw), [=](bool shown) {
- _topPeers->pressLeftToContextMenu(shown);
- });
- }, _topPeers->lifetime());
- _topPeers->pressCancelled() | rpl::start_with_next([=] {
- _controller->cancelScheduledPreview();
- }, _topPeers->lifetime());
- _topPeers->showMenuRequests(
- ) | rpl::start_with_next([=](const ShowTopPeerMenuRequest &request) {
- const auto weak = Ui::MakeWeak(this);
- const auto owner = &_controller->session().data();
- const auto peer = owner->peer(PeerId(request.id));
- const auto removeOne = [=] {
- peer->session().topPeers().remove(peer);
- if (weak) {
- _topPeers->removeLocally(peer->id.value);
- }
- };
- const auto session = &_controller->session();
- const auto removeAll = crl::guard(session, [=] {
- session->topPeers().toggleDisabled(true);
- if (weak) {
- _topPeers->removeLocally();
- }
- });
- FillEntryMenu(request.callback, {
- .controller = _controller,
- .peer = peer,
- .removeOneText = tr::lng_recent_remove(tr::now),
- .removeOne = removeOne,
- .removeAllText = tr::lng_recent_hide_top(
- tr::now,
- Ui::Text::FixAmpersandInAction),
- .removeAllConfirm = tr::lng_recent_hide_sure(tr::now),
- .removeAll = removeAll,
- });
- }, _topPeers->lifetime());
- _topPeers->scrollToRequests(
- ) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
- _chatsScroll->scrollToY(request.ymin, request.ymax);
- }, _topPeers->lifetime());
- _topPeers->verticalScrollEvents(
- ) | rpl::start_with_next([=](not_null<QWheelEvent*> e) {
- _chatsScroll->viewportEvent(e);
- }, _topPeers->lifetime());
- _chatsScroll->setVisible(_key.current().tab == Tab::Chats);
- _chatsScroll->setCustomTouchProcess(_recent->processTouch);
- }
- void Suggestions::handlePressForChatPreview(
- PeerId id,
- Fn<void(bool)> callback) {
- callback = crl::guard(this, callback);
- const auto row = RowDescriptor(
- _controller->session().data().history(id),
- FullMsgId());
- if (base::IsAltPressed()) {
- _controller->showChatPreview(row, callback);
- } else {
- _controller->scheduleChatPreview(row, callback);
- }
- }
- void Suggestions::setupChannels() {
- _myChannels->count.value() | rpl::start_with_next([=](int count) {
- _myChannels->wrap->toggle(count > 0, anim::type::instant);
- }, _myChannels->wrap->lifetime());
- _recommendations->count.value() | rpl::start_with_next([=](int count) {
- _recommendations->wrap->toggle(count > 0, anim::type::instant);
- }, _recommendations->wrap->lifetime());
- _emptyChannels->toggleOn(
- rpl::combine(
- _myChannels->count.value(),
- _recommendations->count.value(),
- rpl::mappers::_1 + rpl::mappers::_2 == 0),
- anim::type::instant);
- _channelsScroll->setVisible(_key.current().tab == Tab::Channels);
- _channelsScroll->setCustomTouchProcess([=](not_null<QTouchEvent*> e) {
- const auto myChannels = _myChannels->processTouch(e);
- const auto recommendations = _recommendations->processTouch(e);
- return myChannels || recommendations;
- });
- }
- void Suggestions::setupApps() {
- _recentApps->count.value() | rpl::start_with_next([=](int count) {
- _recentApps->wrap->toggle(count > 0, anim::type::instant);
- }, _recentApps->wrap->lifetime());
- _popularApps->count.value() | rpl::start_with_next([=](int count) {
- _popularApps->wrap->toggle(count > 0, anim::type::instant);
- }, _popularApps->wrap->lifetime());
- _appsScroll->setVisible(_key.current().tab == Tab::Apps);
- _appsScroll->setCustomTouchProcess([=](not_null<QTouchEvent*> e) {
- const auto recentApps = _recentApps->processTouch(e);
- const auto popularApps = _popularApps->processTouch(e);
- return recentApps || popularApps;
- });
- }
- void Suggestions::selectJump(Qt::Key direction, int pageSize) {
- switch (_key.current().tab) {
- case Tab::Chats: selectJumpChats(direction, pageSize); return;
- case Tab::Channels: selectJumpChannels(direction, pageSize); return;
- case Tab::Apps: selectJumpApps(direction, pageSize); return;
- }
- }
- void Suggestions::selectJumpChats(Qt::Key direction, int pageSize) {
- const auto recentHasSelection = [=] {
- return _recent->selectJump({}, 0) == JumpResult::Applied;
- };
- if (pageSize) {
- if (direction == Qt::Key_Down || direction == Qt::Key_Up) {
- _topPeers->deselectByKeyboard();
- if (!recentHasSelection()) {
- if (direction == Qt::Key_Down) {
- _recent->selectJump(direction, 0);
- } else {
- return;
- }
- }
- if (_recent->selectJump(direction, pageSize)
- == JumpResult::AppliedAndOut) {
- if (direction == Qt::Key_Up) {
- _chatsScroll->scrollTo(0);
- }
- }
- }
- } else if (direction == Qt::Key_Up) {
- if (_recent->selectJump(direction, pageSize)
- == JumpResult::AppliedAndOut) {
- _topPeers->selectByKeyboard(direction);
- } else if (_topPeers->selectedByKeyboard()) {
- _topPeers->selectByKeyboard(direction);
- }
- } else if (direction == Qt::Key_Down) {
- if (!_topPeersWrap->toggled() || recentHasSelection()) {
- _recent->selectJump(direction, pageSize);
- } else if (_topPeers->selectedByKeyboard()) {
- if (!_topPeers->selectByKeyboard(direction)
- && _recent->count.current() > 0) {
- _topPeers->deselectByKeyboard();
- _recent->selectJump(direction, pageSize);
- }
- } else {
- _topPeers->selectByKeyboard({});
- _chatsScroll->scrollTo(0);
- }
- } else if (direction == Qt::Key_Left || direction == Qt::Key_Right) {
- if (!recentHasSelection()) {
- _topPeers->selectByKeyboard(direction);
- }
- }
- }
- void Suggestions::selectJumpChannels(Qt::Key direction, int pageSize) {
- const auto myChannelsHasSelection = [=] {
- return _myChannels->selectJump({}, 0) == JumpResult::Applied;
- };
- const auto recommendationsHasSelection = [=] {
- return _recommendations->selectJump({}, 0) == JumpResult::Applied;
- };
- if (pageSize) {
- if (direction == Qt::Key_Down) {
- if (recommendationsHasSelection()) {
- _recommendations->selectJump(direction, pageSize);
- } else if (myChannelsHasSelection()) {
- if (_myChannels->selectJump(direction, pageSize)
- == JumpResult::AppliedAndOut) {
- _recommendations->selectJump(direction, 0);
- }
- } else if (_myChannels->count.current()) {
- _myChannels->selectJump(direction, 0);
- _myChannels->selectJump(direction, pageSize);
- } else if (_recommendations->count.current()) {
- _recommendations->selectJump(direction, 0);
- _recommendations->selectJump(direction, pageSize);
- }
- } else if (direction == Qt::Key_Up) {
- if (myChannelsHasSelection()) {
- if (_myChannels->selectJump(direction, pageSize)
- == JumpResult::AppliedAndOut) {
- _channelsScroll->scrollTo(0);
- }
- } else if (recommendationsHasSelection()) {
- if (_recommendations->selectJump(direction, pageSize)
- == JumpResult::AppliedAndOut) {
- _myChannels->selectJump(direction, -1);
- }
- }
- }
- } else if (direction == Qt::Key_Up) {
- if (myChannelsHasSelection()) {
- _myChannels->selectJump(direction, 0);
- } else if (_recommendations->selectJump(direction, 0)
- == JumpResult::AppliedAndOut) {
- _myChannels->selectJump(direction, -1);
- } else if (!recommendationsHasSelection()) {
- if (_myChannels->selectJump(direction, 0)
- == JumpResult::AppliedAndOut) {
- _channelsScroll->scrollTo(0);
- }
- }
- } else if (direction == Qt::Key_Down) {
- if (recommendationsHasSelection()) {
- _recommendations->selectJump(direction, 0);
- } else if (_myChannels->selectJump(direction, 0)
- == JumpResult::AppliedAndOut) {
- _recommendations->selectJump(direction, 0);
- } else if (!myChannelsHasSelection()) {
- if (_recommendations->selectJump(direction, 0)
- == JumpResult::AppliedAndOut) {
- _myChannels->selectJump(direction, 0);
- }
- }
- }
- }
- void Suggestions::selectJumpApps(Qt::Key direction, int pageSize) {
- const auto recentAppsHasSelection = [=] {
- return _recentApps->selectJump({}, 0) == JumpResult::Applied;
- };
- const auto popularAppsHasSelection = [=] {
- return _popularApps->selectJump({}, 0) == JumpResult::Applied;
- };
- if (pageSize) {
- if (direction == Qt::Key_Down) {
- if (popularAppsHasSelection()) {
- _popularApps->selectJump(direction, pageSize);
- } else if (recentAppsHasSelection()) {
- if (_recentApps->selectJump(direction, pageSize)
- == JumpResult::AppliedAndOut) {
- _popularApps->selectJump(direction, 0);
- }
- } else if (_recentApps->count.current()) {
- _recentApps->selectJump(direction, 0);
- _recentApps->selectJump(direction, pageSize);
- } else if (_popularApps->count.current()) {
- _popularApps->selectJump(direction, 0);
- _popularApps->selectJump(direction, pageSize);
- }
- } else if (direction == Qt::Key_Up) {
- if (recentAppsHasSelection()) {
- if (_recentApps->selectJump(direction, pageSize)
- == JumpResult::AppliedAndOut) {
- _channelsScroll->scrollTo(0);
- }
- } else if (popularAppsHasSelection()) {
- if (_popularApps->selectJump(direction, pageSize)
- == JumpResult::AppliedAndOut) {
- _recentApps->selectJump(direction, -1);
- }
- }
- }
- } else if (direction == Qt::Key_Up) {
- if (recentAppsHasSelection()) {
- _recentApps->selectJump(direction, 0);
- } else if (_popularApps->selectJump(direction, 0)
- == JumpResult::AppliedAndOut) {
- _recentApps->selectJump(direction, -1);
- } else if (!popularAppsHasSelection()) {
- if (_recentApps->selectJump(direction, 0)
- == JumpResult::AppliedAndOut) {
- _channelsScroll->scrollTo(0);
- }
- }
- } else if (direction == Qt::Key_Down) {
- if (popularAppsHasSelection()) {
- _popularApps->selectJump(direction, 0);
- } else if (_recentApps->selectJump(direction, 0)
- == JumpResult::AppliedAndOut) {
- _popularApps->selectJump(direction, 0);
- } else if (!recentAppsHasSelection()) {
- if (_popularApps->selectJump(direction, 0)
- == JumpResult::AppliedAndOut) {
- _recentApps->selectJump(direction, 0);
- }
- }
- }
- }
- void Suggestions::chooseRow() {
- switch (_key.current().tab) {
- case Tab::Chats:
- if (!_topPeers->chooseRow()) {
- _recent->choose();
- }
- break;
- case Tab::Channels:
- if (!_myChannels->choose()) {
- _recommendations->choose();
- }
- break;
- case Tab::Apps:
- if (!_recentApps->choose()) {
- _popularApps->choose();
- }
- break;
- }
- }
- bool Suggestions::consumeSearchQuery(const QString &query) {
- using Type = MediaType;
- const auto key = _key.current();
- const auto tab = key.tab;
- const auto type = (key.tab == Tab::Media) ? key.mediaType : Type::kCount;
- if (tab != Tab::Downloads
- && type != Type::File
- && type != Type::Link
- && type != Type::MusicFile) {
- return false;
- } else if (_searchQuery == query) {
- return false;
- }
- _searchQuery = query;
- _persist = !_searchQuery.isEmpty();
- if (query.isEmpty() || tab == Tab::Downloads) {
- _searchQueryTimer.cancel();
- applySearchQuery();
- } else {
- _searchQueryTimer.callOnce(kSearchQueryDelay);
- }
- return true;
- }
- void Suggestions::applySearchQuery() {
- const auto key = _key.current();
- const auto controller = _mediaLists[key].wrap->controller();
- const auto search = controller->searchFieldController();
- if (search->query() != _searchQuery) {
- search->setQuery(_searchQuery);
- }
- }
- rpl::producer<> Suggestions::clearSearchQueryRequests() const {
- return _clearSearchQueryRequests.events();
- }
- Data::Thread *Suggestions::updateFromParentDrag(QPoint globalPosition) {
- switch (_key.current().tab) {
- case Tab::Chats: return updateFromChatsDrag(globalPosition);
- case Tab::Channels: return updateFromChannelsDrag(globalPosition);
- }
- return nullptr;
- }
- Data::Thread *Suggestions::updateFromChatsDrag(QPoint globalPosition) {
- if (const auto top = _topPeers->updateFromParentDrag(globalPosition)) {
- return _controller->session().data().history(PeerId(top));
- }
- return fromListId(_recent->updateFromParentDrag(globalPosition));
- }
- Data::Thread *Suggestions::updateFromChannelsDrag(QPoint globalPosition) {
- if (const auto id = _myChannels->updateFromParentDrag(globalPosition)) {
- return fromListId(id);
- }
- return fromListId(_recommendations->updateFromParentDrag(globalPosition));
- }
- Data::Thread *Suggestions::updateFromAppsDrag(QPoint globalPosition) {
- if (const auto id = _recentApps->updateFromParentDrag(globalPosition)) {
- return fromListId(id);
- }
- return fromListId(_popularApps->updateFromParentDrag(globalPosition));
- }
- Data::Thread *Suggestions::fromListId(uint64 peerListRowId) {
- return peerListRowId
- ? _controller->session().data().history(PeerId(peerListRowId)).get()
- : nullptr;
- }
- void Suggestions::dragLeft() {
- _topPeers->dragLeft();
- _recent->dragLeft();
- _myChannels->dragLeft();
- _recommendations->dragLeft();
- _recentApps->dragLeft();
- _popularApps->dragLeft();
- }
- void Suggestions::show(anim::type animated, Fn<void()> finish) {
- RpWidget::show();
- _hidden = false;
- if (animated == anim::type::instant) {
- finishShow();
- } else {
- startShownAnimation(true, std::move(finish));
- }
- }
- void Suggestions::hide(anim::type animated, Fn<void()> finish) {
- _hidden = true;
- if (isHidden()) {
- return;
- } else if (animated == anim::type::instant) {
- RpWidget::hide();
- } else {
- startShownAnimation(false, std::move(finish));
- }
- }
- void Suggestions::switchTab(Key key) {
- const auto was = _key.current();
- if (was == key) {
- return;
- }
- consumeSearchQuery(QString());
- _key = key;
- _persist = false;
- _clearSearchQueryRequests.fire({});
- if (_tabs->isHidden()) {
- return;
- }
- startSlideAnimation(was, key);
- }
- void Suggestions::ensureContent(Key key) {
- if (key.tab != Tab::Downloads && key.tab != Tab::Media) {
- return;
- }
- auto &list = _mediaLists[key];
- if (list.wrap) {
- return;
- }
- const auto self = _controller->session().user();
- const auto memento = (key.tab == Tab::Downloads)
- ? Info::Downloads::Make(self)
- : std::make_shared<Info::Memento>(
- self,
- Info::Section(key.mediaType, Info::Section::Type::GlobalMedia));
- list.wrap = Ui::CreateChild<Info::WrapWidget>(
- this,
- _controller,
- Info::Wrap::Search,
- memento.get());
- list.wrap->show();
- updateControlsGeometry();
- }
- void Suggestions::startSlideAnimation(Key was, Key now) {
- ensureContent(now);
- const auto wasIndex = ranges::find(_tabKeys, was);
- const auto nowIndex = ranges::find(_tabKeys, now);
- if (!_slideAnimation.animating()) {
- const auto find = [&](Key key) -> not_null<QWidget*> {
- switch (key.tab) {
- case Tab::Chats: return _chatsScroll.get();
- case Tab::Channels: return _channelsScroll.get();
- case Tab::Apps: return _appsScroll.get();
- }
- return _mediaLists[key].wrap;
- };
- auto left = find(was);
- auto right = find(now);
- if (wasIndex > nowIndex) {
- std::swap(left, right);
- }
- _slideLeft = Ui::GrabWidget(left);
- _slideLeftTop = left->y();
- _slideRight = Ui::GrabWidget(right);
- _slideRightTop = right->y();
- left->hide();
- right->hide();
- }
- const auto from = (nowIndex > wasIndex) ? 0. : 1.;
- const auto to = (nowIndex > wasIndex) ? 1. : 0.;
- _slideAnimation.start([=] {
- update();
- if (!_slideAnimation.animating() && !_shownAnimation.animating()) {
- finishShow();
- }
- }, from, to, st::slideDuration, anim::sineInOut);
- }
- void Suggestions::startShownAnimation(bool shown, Fn<void()> finish) {
- const auto from = shown ? 0. : 1.;
- const auto to = shown ? 1. : 0.;
- _shownAnimation.start([=] {
- update();
- if (!_shownAnimation.animating() && finish) {
- finish();
- if (shown) {
- finishShow();
- }
- }
- }, from, to, st::slideDuration, anim::easeOutQuint);
- if (_cache.isNull()) {
- const auto now = width();
- if (now < st::columnMinimalWidthLeft) {
- resize(st::columnMinimalWidthLeft, height());
- }
- _cache = Ui::GrabWidget(this);
- if (now < st::columnMinimalWidthLeft) {
- resize(now, height());
- }
- }
- _tabsScroll->hide();
- _chatsScroll->hide();
- _channelsScroll->hide();
- _appsScroll->hide();
- for (const auto &[key, list] : _mediaLists) {
- list.wrap->hide();
- }
- _slideAnimation.stop();
- }
- void Suggestions::finishShow() {
- _slideAnimation.stop();
- _slideLeft = _slideRight = QPixmap();
- _slideLeftTop = _slideRightTop = 0;
- _shownAnimation.stop();
- _cache = QPixmap();
- _tabsScroll->show();
- const auto key = _key.current();
- _chatsScroll->setVisible(key == Key{ Tab::Chats });
- _channelsScroll->setVisible(key == Key{ Tab::Channels });
- _appsScroll->setVisible(key == Key{ Tab::Apps });
- for (const auto &[mediaKey, list] : _mediaLists) {
- list.wrap->setVisible(key == mediaKey);
- }
- }
- float64 Suggestions::shownOpacity() const {
- return _shownAnimation.value(_hidden ? 0. : 1.);
- }
- std::vector<Suggestions::Key> Suggestions::TabKeysFor(
- not_null<Window::SessionController*> controller) {
- auto result = std::vector<Key>{
- { Tab::Chats },
- { Tab::Channels },
- { Tab::Apps },
- { Tab::Media, MediaType::Photo },
- { Tab::Media, MediaType::Video },
- { Tab::Downloads },
- { Tab::Media, MediaType::Link },
- { Tab::Media, MediaType::File },
- { Tab::Media, MediaType::MusicFile },
- { Tab::Media, MediaType::RoundVoiceFile },
- };
- if (Core::App().downloadManager().empty()) {
- result.erase(ranges::find(result, Key{ Tab::Downloads }));
- }
- return result;
- }
- void Suggestions::paintEvent(QPaintEvent *e) {
- const auto opacity = shownOpacity();
- auto color = st::windowBg->c;
- color.setAlphaF(color.alphaF() * opacity);
- auto p = QPainter(this);
- p.fillRect(e->rect(), color);
- if (!_cache.isNull()) {
- const auto slide = st::topPeers.height + st::searchedBarHeight;
- p.setOpacity(opacity);
- p.drawPixmap(0, (opacity - 1.) * slide, _cache);
- } else if (!_slideLeft.isNull()) {
- const auto slide = st::topPeers.height + st::searchedBarHeight;
- const auto right = (_key.current().tab == Tab::Channels);
- const auto progress = _slideAnimation.value(right ? 1. : 0.);
- p.setOpacity(1. - progress);
- p.drawPixmap(
- anim::interpolate(0, -slide, progress),
- _slideLeftTop,
- _slideLeft);
- p.setOpacity(progress);
- p.drawPixmap(
- anim::interpolate(slide, 0, progress),
- _slideRightTop,
- _slideRight);
- }
- }
- void Suggestions::resizeEvent(QResizeEvent *e) {
- updateControlsGeometry();
- }
- void Suggestions::updateControlsGeometry() {
- const auto w = std::max(width(), st::columnMinimalWidthLeft);
- _tabs->fitWidthToSections();
- const auto tabs = _tabs->height();
- _tabsScroll->setGeometry(0, 0, w, tabs);
- const auto content = QRect(0, tabs, w, height() - tabs);
- _chatsScroll->setGeometry(content);
- _chatsContent->resizeToWidth(w);
- _channelsScroll->setGeometry(content);
- _channelsContent->resizeToWidth(w);
- _appsScroll->setGeometry(content);
- _appsContent->resizeToWidth(w);
- const auto expanding = false;
- for (const auto &[key, list] : _mediaLists) {
- const auto full = !list.wrap->scrollBottomSkip();
- const auto additionalScroll = (full ? st::boxRadius : 0);
- const auto height = content.height() - (full ? 0 : st::boxRadius);
- const auto wrapGeometry = QRect{ 0, tabs, w, height};
- list.wrap->updateGeometry(
- wrapGeometry,
- expanding,
- additionalScroll,
- content.height());
- }
- }
- auto Suggestions::setupRecentPeers(RecentPeersList recentPeers)
- -> std::unique_ptr<ObjectList> {
- const auto controller = lifetime().make_state<RecentsController>(
- _controller,
- std::move(recentPeers),
- [=](not_null<PeerData*> p) { _openBotMainAppRequests.fire_copy(p); });
- const auto addToScroll = [=] {
- return _topPeersWrap->toggled() ? _topPeers->height() : 0;
- };
- auto result = setupObjectList(
- _chatsScroll.get(),
- _chatsContent,
- controller,
- addToScroll);
- const auto raw = result.get();
- const auto list = raw->wrap->entity();
- raw->selectJump = [list](Qt::Key direction, int pageSize) {
- const auto had = list->hasSelection();
- if (direction == Qt::Key()) {
- return had ? JumpResult::Applied : JumpResult::NotApplied;
- } else if (direction == Qt::Key_Up && !had) {
- return JumpResult::NotApplied;
- } else if (direction == Qt::Key_Down || direction == Qt::Key_Up) {
- const auto delta = (direction == Qt::Key_Down) ? 1 : -1;
- if (pageSize > 0) {
- list->selectSkipPage(pageSize, delta);
- } else {
- list->selectSkip(delta);
- }
- return list->hasSelection()
- ? JumpResult::Applied
- : had
- ? JumpResult::AppliedAndOut
- : JumpResult::NotApplied;
- }
- return JumpResult::NotApplied;
- };
- raw->chosen.events(
- ) | rpl::start_with_next([=](not_null<PeerData*> peer) {
- _controller->session().recentPeers().bump(peer);
- }, list->lifetime());
- return result;
- }
- object_ptr<Ui::SlideWrap<>> Suggestions::setupEmptyRecent() {
- const auto icon = SearchEmptyIcon::Search;
- return setupEmpty(_chatsContent, icon, tr::lng_recent_none());
- }
- auto Suggestions::setupMyChannels() -> std::unique_ptr<ObjectList> {
- const auto controller = lifetime().make_state<MyChannelsController>(
- _controller);
- auto result = setupObjectList(
- _channelsScroll.get(),
- _channelsContent,
- controller);
- const auto raw = result.get();
- const auto list = raw->wrap->entity();
- raw->selectJump = [=](Qt::Key direction, int pageSize) {
- const auto had = list->hasSelection();
- if (direction == Qt::Key()) {
- return had ? JumpResult::Applied : JumpResult::NotApplied;
- } else if (direction == Qt::Key_Up && !had) {
- if (pageSize < 0) {
- list->selectLast();
- return list->hasSelection()
- ? JumpResult::Applied
- : JumpResult::NotApplied;
- }
- return JumpResult::NotApplied;
- } else if (direction == Qt::Key_Down || direction == Qt::Key_Up) {
- const auto was = list->selectedIndex();
- const auto delta = (direction == Qt::Key_Down) ? 1 : -1;
- if (pageSize > 0) {
- list->selectSkipPage(pageSize, delta);
- } else {
- list->selectSkip(delta);
- }
- if (had
- && delta > 0
- && raw->count.current()
- && list->selectedIndex() == was) {
- list->clearSelection();
- return JumpResult::AppliedAndOut;
- }
- return list->hasSelection()
- ? JumpResult::Applied
- : had
- ? JumpResult::AppliedAndOut
- : JumpResult::NotApplied;
- }
- return JumpResult::NotApplied;
- };
- raw->chosen.events(
- ) | rpl::start_with_next([=] {
- _persist = false;
- }, list->lifetime());
- return result;
- }
- auto Suggestions::setupRecommendations() -> std::unique_ptr<ObjectList> {
- const auto controller = lifetime().make_state<RecommendationsController>(
- _controller);
- const auto addToScroll = [=] {
- const auto wrap = _myChannels->wrap;
- return wrap->toggled() ? wrap->height() : 0;
- };
- auto result = setupObjectList(
- _channelsScroll.get(),
- _channelsContent,
- controller,
- addToScroll);
- const auto raw = result.get();
- const auto list = raw->wrap->entity();
- raw->selectJump = [list](Qt::Key direction, int pageSize) {
- const auto had = list->hasSelection();
- if (direction == Qt::Key()) {
- return had ? JumpResult::Applied : JumpResult::NotApplied;
- } else if (direction == Qt::Key_Up && !had) {
- return JumpResult::NotApplied;
- } else if (direction == Qt::Key_Down || direction == Qt::Key_Up) {
- const auto delta = (direction == Qt::Key_Down) ? 1 : -1;
- if (pageSize > 0) {
- list->selectSkipPage(pageSize, delta);
- } else {
- list->selectSkip(delta);
- }
- return list->hasSelection()
- ? JumpResult::Applied
- : had
- ? JumpResult::AppliedAndOut
- : JumpResult::NotApplied;
- }
- return JumpResult::NotApplied;
- };
- raw->chosen.events(
- ) | rpl::start_with_next([=] {
- _persist = true;
- }, list->lifetime());
- _key.value() | rpl::filter(
- rpl::mappers::_1 == Key{ Tab::Channels }
- ) | rpl::start_with_next([=] {
- controller->load();
- }, list->lifetime());
- return result;
- }
- auto Suggestions::setupRecentApps() -> std::unique_ptr<ObjectList> {
- const auto controller = lifetime().make_state<RecentAppsController>(
- _controller);
- _recentAppsShows = [=](not_null<PeerData*> peer) {
- return controller->shown(peer);
- };
- _recentAppsRefreshed = controller->refreshed();
- auto result = setupObjectList(
- _appsScroll.get(),
- _appsContent,
- controller);
- const auto raw = result.get();
- const auto list = raw->wrap->entity();
- raw->selectJump = [=](Qt::Key direction, int pageSize) {
- const auto had = list->hasSelection();
- if (direction == Qt::Key()) {
- return had ? JumpResult::Applied : JumpResult::NotApplied;
- } else if (direction == Qt::Key_Up && !had) {
- if (pageSize < 0) {
- list->selectLast();
- return list->hasSelection()
- ? JumpResult::Applied
- : JumpResult::NotApplied;
- }
- return JumpResult::NotApplied;
- } else if (direction == Qt::Key_Down || direction == Qt::Key_Up) {
- const auto was = list->selectedIndex();
- const auto delta = (direction == Qt::Key_Down) ? 1 : -1;
- if (pageSize > 0) {
- list->selectSkipPage(pageSize, delta);
- } else {
- list->selectSkip(delta);
- }
- if (had
- && delta > 0
- && raw->count.current()
- && list->selectedIndex() == was) {
- list->clearSelection();
- return JumpResult::AppliedAndOut;
- }
- return list->hasSelection()
- ? JumpResult::Applied
- : had
- ? JumpResult::AppliedAndOut
- : JumpResult::NotApplied;
- }
- return JumpResult::NotApplied;
- };
- raw->chosen.events(
- ) | rpl::start_with_next([=] {
- _persist = false;
- }, list->lifetime());
- controller->load();
- return result;
- }
- auto Suggestions::setupPopularApps() -> std::unique_ptr<ObjectList> {
- const auto controller = lifetime().make_state<PopularAppsController>(
- _controller,
- _recentAppsShows,
- rpl::duplicate(_recentAppsRefreshed));
- const auto addToScroll = [=] {
- const auto wrap = _recentApps->wrap;
- return wrap->toggled() ? wrap->height() : 0;
- };
- auto result = setupObjectList(
- _appsScroll.get(),
- _appsContent,
- controller,
- addToScroll);
- const auto raw = result.get();
- const auto list = raw->wrap->entity();
- raw->selectJump = [list](Qt::Key direction, int pageSize) {
- const auto had = list->hasSelection();
- if (direction == Qt::Key()) {
- return had ? JumpResult::Applied : JumpResult::NotApplied;
- } else if (direction == Qt::Key_Up && !had) {
- return JumpResult::NotApplied;
- } else if (direction == Qt::Key_Down || direction == Qt::Key_Up) {
- const auto delta = (direction == Qt::Key_Down) ? 1 : -1;
- if (pageSize > 0) {
- list->selectSkipPage(pageSize, delta);
- } else {
- list->selectSkip(delta);
- }
- return list->hasSelection()
- ? JumpResult::Applied
- : had
- ? JumpResult::AppliedAndOut
- : JumpResult::NotApplied;
- }
- return JumpResult::NotApplied;
- };
- raw->chosen.events(
- ) | rpl::start_with_next([=] {
- _persist = true;
- }, list->lifetime());
- _key.value() | rpl::filter(
- rpl::mappers::_1 == Key{ Tab::Apps }
- ) | rpl::start_with_next([=] {
- controller->load();
- }, list->lifetime());
- return result;
- }
- auto Suggestions::setupObjectList(
- not_null<Ui::ElasticScroll*> scroll,
- not_null<Ui::VerticalLayout*> parent,
- not_null<ObjectListController*> controller,
- Fn<int()> addToScroll)
- -> std::unique_ptr<ObjectList> {
- auto &lifetime = parent->lifetime();
- const auto delegate = lifetime.make_state<
- PeerListContentDelegateSimple
- >();
- controller->setStyleOverrides(&st::recentPeersList);
- auto content = object_ptr<PeerListContent>(parent, controller);
- const auto list = content.data();
- auto result = std::make_unique<ObjectList>(ObjectList{
- .wrap = parent->add(object_ptr<Ui::SlideWrap<PeerListContent>>(
- parent,
- std::move(content))),
- });
- const auto raw = result.get();
- raw->count = controller->count();
- raw->processTouch = [=](not_null<QTouchEvent*> e) {
- return controller->processTouchEvent(e);
- };
- controller->chosen(
- ) | rpl::start_with_next([=](not_null<PeerData*> peer) {
- raw->chosen.fire_copy(peer);
- }, lifetime);
- raw->choose = [=] {
- return list->submitted();
- };
- raw->updateFromParentDrag = [=](QPoint globalPosition) {
- return list->updateFromParentDrag(globalPosition);
- };
- raw->dragLeft = [=] {
- list->dragLeft();
- };
- list->scrollToRequests(
- ) | rpl::start_with_next([=](Ui::ScrollToRequest request) {
- const auto add = addToScroll ? addToScroll() : 0;
- scroll->scrollToY(request.ymin + add, request.ymax + add);
- }, list->lifetime());
- delegate->setContent(list);
- controller->setDelegate(delegate);
- controller->setupTouchChatPreview(scroll);
- return result;
- }
- object_ptr<Ui::SlideWrap<>> Suggestions::setupEmptyChannels() {
- const auto icon = SearchEmptyIcon::NoResults;
- return setupEmpty(_channelsContent, icon, tr::lng_channels_none_about());
- }
- object_ptr<Ui::SlideWrap<>> Suggestions::setupEmpty(
- not_null<QWidget*> parent,
- SearchEmptyIcon icon,
- rpl::producer<QString> text) {
- auto content = object_ptr<SearchEmpty>(
- parent,
- icon,
- std::move(text) | Ui::Text::ToWithEntities());
- const auto raw = content.data();
- rpl::combine(
- _chatsScroll->heightValue(),
- _topPeersWrap->heightValue()
- ) | rpl::start_with_next([=](int height, int top) {
- raw->setMinimalHeight(height - top);
- }, raw->lifetime());
- auto result = object_ptr<Ui::SlideWrap<>>(
- parent,
- std::move(content));
- result->toggle(false, anim::type::instant);
- result->toggledValue() | rpl::filter([=](bool shown) {
- return shown && _controller->session().data().chatsListLoaded();
- }) | rpl::start_with_next([=] {
- raw->animate();
- }, raw->lifetime());
- return result;
- }
- bool Suggestions::persist() const {
- return _persist;
- }
- void Suggestions::clearPersistance() {
- _persist = false;
- }
- rpl::producer<TopPeersList> TopPeersContent(
- not_null<Main::Session*> session) {
- return [=](auto consumer) {
- auto lifetime = rpl::lifetime();
- struct Entry {
- not_null<History*> history;
- int index = 0;
- };
- struct State {
- TopPeersList data;
- base::flat_map<not_null<PeerData*>, Entry> indices;
- base::has_weak_ptr guard;
- bool scheduled = true;
- };
- auto state = lifetime.make_state<State>();
- const auto top = session->topPeers().list();
- auto &entries = state->data.entries;
- auto &indices = state->indices;
- entries.reserve(top.size());
- indices.reserve(top.size());
- const auto now = base::unixtime::now();
- for (const auto &peer : top) {
- const auto user = peer->asUser();
- if (user->isInaccessible()) {
- continue;
- }
- const auto self = user && user->isSelf();
- const auto history = peer->owner().history(peer);
- const auto badges = history->chatListBadgesState();
- entries.push_back({
- .id = peer->id.value,
- .name = (self
- ? tr::lng_saved_messages(tr::now)
- : peer->shortName()),
- .userpic = (self
- ? Ui::MakeSavedMessagesThumbnail()
- : Ui::MakeUserpicThumbnail(peer)),
- .badge = uint32(badges.unreadCounter),
- .unread = badges.unread,
- .muted = !self && history->muted(),
- .online = user && !self && Data::IsUserOnline(user, now),
- });
- if (entries.back().online) {
- user->owner().watchForOffline(user, now);
- }
- indices.emplace(peer, Entry{
- .history = peer->owner().history(peer),
- .index = int(entries.size()) - 1,
- });
- }
- const auto push = [=] {
- if (!state->scheduled) {
- return;
- }
- state->scheduled = false;
- consumer.put_next_copy(state->data);
- };
- const auto schedule = [=] {
- if (state->scheduled) {
- return;
- }
- state->scheduled = true;
- crl::on_main(&state->guard, push);
- };
- using Flag = Data::PeerUpdate::Flag;
- session->changes().peerUpdates(
- Flag::Name
- | Flag::Photo
- | Flag::Notifications
- | Flag::OnlineStatus
- ) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
- const auto peer = update.peer;
- if (peer->isSelf()) {
- return;
- }
- const auto i = state->indices.find(peer);
- if (i == end(state->indices)) {
- return;
- }
- auto changed = false;
- auto &entry = state->data.entries[i->second.index];
- const auto flags = update.flags;
- if (flags & Flag::Name) {
- const auto now = peer->shortName();
- if (entry.name != now) {
- entry.name = now;
- changed = true;
- }
- }
- if (flags & Flag::Photo) {
- entry.userpic = Ui::MakeUserpicThumbnail(peer);
- changed = true;
- }
- if (flags & Flag::Notifications) {
- const auto now = i->second.history->muted();
- if (entry.muted != now) {
- entry.muted = now;
- changed = true;
- }
- }
- if (flags & Flag::OnlineStatus) {
- if (const auto user = peer->asUser()) {
- const auto now = base::unixtime::now();
- const auto value = Data::IsUserOnline(user, now);
- if (entry.online != value) {
- entry.online = value;
- changed = true;
- if (value) {
- user->owner().watchForOffline(user, now);
- }
- }
- }
- }
- if (changed) {
- schedule();
- }
- }, lifetime);
- session->data().unreadBadgeChanges(
- ) | rpl::start_with_next([=] {
- auto changed = false;
- auto &entries = state->data.entries;
- for (const auto &[peer, data] : state->indices) {
- const auto badges = data.history->chatListBadgesState();
- auto &entry = entries[data.index];
- if (entry.badge != badges.unreadCounter
- || entry.unread != badges.unread) {
- entry.badge = badges.unreadCounter;
- entry.unread = badges.unread;
- changed = true;
- }
- }
- if (changed) {
- schedule();
- }
- }, lifetime);
- push();
- return lifetime;
- };
- }
- RecentPeersList RecentPeersContent(not_null<Main::Session*> session) {
- return RecentPeersList{ session->recentPeers().list() };
- }
- object_ptr<Ui::BoxContent> StarsExamplesBox(
- not_null<Window::SessionController*> window) {
- auto controller = std::make_unique<PopularAppsController>(
- window,
- nullptr,
- nullptr);
- const auto raw = controller.get();
- auto initBox = [=](not_null<PeerListBox*> box) {
- box->setTitle(tr::lng_credits_box_history_entry_gift_examples());
- box->addButton(tr::lng_close(), [=] {
- box->closeBox();
- });
- raw->load();
- raw->chosen() | rpl::start_with_next([=](not_null<PeerData*> peer) {
- if (const auto user = peer->asUser()) {
- if (const auto info = user->botInfo.get()) {
- if (info->hasMainApp) {
- window->session().attachWebView().open({
- .bot = user,
- .context = {
- .controller = window,
- .maySkipConfirmation = true,
- },
- .source = InlineBots::WebViewSourceBotProfile(),
- });
- return;
- }
- }
- }
- window->showPeerInfo(peer);
- }, box->lifetime());
- };
- return Box<PeerListBox>(std::move(controller), std::move(initBox));
- }
- object_ptr<Ui::BoxContent> PopularAppsAboutBox(
- not_null<Window::SessionController*> window) {
- return Ui::MakeInformBox({
- .text = tr::lng_popular_apps_info_text(
- lt_bot,
- rpl::single(Ui::Text::Link(
- u"@botfather"_q,
- u"https://t.me/botfather"_q)),
- lt_link,
- tr::lng_popular_apps_info_here(
- ) | Ui::Text::ToLink(tr::lng_popular_apps_info_url(tr::now)),
- Ui::Text::RichLangValue),
- .confirmText = tr::lng_popular_apps_info_confirm(),
- .title = tr::lng_popular_apps_info_title(),
- });
- }
- } // namespace Dialogs
|