mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Initial version of top peers.
This commit is contained in:
parent
11e4c45969
commit
18598f8dca
21 changed files with 908 additions and 27 deletions
|
@ -460,10 +460,14 @@ PRIVATE
|
||||||
data/business/data_business_info.h
|
data/business/data_business_info.h
|
||||||
data/business/data_shortcut_messages.cpp
|
data/business/data_shortcut_messages.cpp
|
||||||
data/business/data_shortcut_messages.h
|
data/business/data_shortcut_messages.h
|
||||||
|
data/components/recent_peers.cpp
|
||||||
|
data/components/recent_peers.h
|
||||||
data/components/scheduled_messages.cpp
|
data/components/scheduled_messages.cpp
|
||||||
data/components/scheduled_messages.h
|
data/components/scheduled_messages.h
|
||||||
data/components/sponsored_messages.cpp
|
data/components/sponsored_messages.cpp
|
||||||
data/components/sponsored_messages.h
|
data/components/sponsored_messages.h
|
||||||
|
data/components/top_peers.cpp
|
||||||
|
data/components/top_peers.h
|
||||||
data/notify/data_notify_settings.cpp
|
data/notify/data_notify_settings.cpp
|
||||||
data/notify/data_notify_settings.h
|
data/notify/data_notify_settings.h
|
||||||
data/notify/data_peer_notify_settings.cpp
|
data/notify/data_peer_notify_settings.cpp
|
||||||
|
@ -608,6 +612,18 @@ PRIVATE
|
||||||
data/data_wall_paper.h
|
data/data_wall_paper.h
|
||||||
data/data_web_page.cpp
|
data/data_web_page.cpp
|
||||||
data/data_web_page.h
|
data/data_web_page.h
|
||||||
|
dialogs/ui/dialogs_layout.cpp
|
||||||
|
dialogs/ui/dialogs_layout.h
|
||||||
|
dialogs/ui/dialogs_message_view.cpp
|
||||||
|
dialogs/ui/dialogs_message_view.h
|
||||||
|
dialogs/ui/dialogs_stories_content.cpp
|
||||||
|
dialogs/ui/dialogs_stories_content.h
|
||||||
|
dialogs/ui/dialogs_suggestions.cpp
|
||||||
|
dialogs/ui/dialogs_suggestions.h
|
||||||
|
dialogs/ui/dialogs_topics_view.cpp
|
||||||
|
dialogs/ui/dialogs_topics_view.h
|
||||||
|
dialogs/ui/dialogs_video_userpic.cpp
|
||||||
|
dialogs/ui/dialogs_video_userpic.h
|
||||||
dialogs/dialogs_entry.cpp
|
dialogs/dialogs_entry.cpp
|
||||||
dialogs/dialogs_entry.h
|
dialogs/dialogs_entry.h
|
||||||
dialogs/dialogs_indexed_list.cpp
|
dialogs/dialogs_indexed_list.cpp
|
||||||
|
@ -630,16 +646,6 @@ PRIVATE
|
||||||
dialogs/dialogs_search_tags.h
|
dialogs/dialogs_search_tags.h
|
||||||
dialogs/dialogs_widget.cpp
|
dialogs/dialogs_widget.cpp
|
||||||
dialogs/dialogs_widget.h
|
dialogs/dialogs_widget.h
|
||||||
dialogs/ui/dialogs_layout.cpp
|
|
||||||
dialogs/ui/dialogs_layout.h
|
|
||||||
dialogs/ui/dialogs_message_view.cpp
|
|
||||||
dialogs/ui/dialogs_message_view.h
|
|
||||||
dialogs/ui/dialogs_stories_content.cpp
|
|
||||||
dialogs/ui/dialogs_stories_content.h
|
|
||||||
dialogs/ui/dialogs_topics_view.cpp
|
|
||||||
dialogs/ui/dialogs_topics_view.h
|
|
||||||
dialogs/ui/dialogs_video_userpic.cpp
|
|
||||||
dialogs/ui/dialogs_video_userpic.h
|
|
||||||
editor/color_picker.cpp
|
editor/color_picker.cpp
|
||||||
editor/color_picker.h
|
editor/color_picker.h
|
||||||
editor/controllers/controllers.h
|
editor/controllers/controllers.h
|
||||||
|
|
|
@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "mtproto/mtproto_dc_options.h"
|
#include "mtproto/mtproto_dc_options.h"
|
||||||
#include "data/business/data_shortcut_messages.h"
|
#include "data/business/data_shortcut_messages.h"
|
||||||
#include "data/components/scheduled_messages.h"
|
#include "data/components/scheduled_messages.h"
|
||||||
|
#include "data/components/top_peers.h"
|
||||||
#include "data/notify/data_notify_settings.h"
|
#include "data/notify/data_notify_settings.h"
|
||||||
#include "data/stickers/data_stickers.h"
|
#include "data/stickers/data_stickers.h"
|
||||||
#include "data/data_saved_messages.h"
|
#include "data/data_saved_messages.h"
|
||||||
|
@ -1577,6 +1578,11 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||||
} else {
|
} else {
|
||||||
if (existing) {
|
if (existing) {
|
||||||
existing->destroy();
|
existing->destroy();
|
||||||
|
} else {
|
||||||
|
// Not the server-side date, but close enough.
|
||||||
|
session().topPeers().increment(
|
||||||
|
local->history()->peer,
|
||||||
|
local->date());
|
||||||
}
|
}
|
||||||
local->setRealId(d.vid().v);
|
local->setRealId(d.vid().v);
|
||||||
}
|
}
|
||||||
|
|
18
Telegram/SourceFiles/data/components/recent_peers.cpp
Normal file
18
Telegram/SourceFiles/data/components/recent_peers.cpp
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
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 "data/components/recent_peers.h"
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
|
||||||
|
RecentPeers::RecentPeers(not_null<Main::Session*> session)
|
||||||
|
: _session(session) {
|
||||||
|
}
|
||||||
|
|
||||||
|
RecentPeers::~RecentPeers() = default;
|
||||||
|
|
||||||
|
} // namespace Data
|
26
Telegram/SourceFiles/data/components/recent_peers.h
Normal file
26
Telegram/SourceFiles/data/components/recent_peers.h
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Main {
|
||||||
|
class Session;
|
||||||
|
} // namespace Main
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
|
||||||
|
class RecentPeers final {
|
||||||
|
public:
|
||||||
|
explicit RecentPeers(not_null<Main::Session*> session);
|
||||||
|
~RecentPeers();
|
||||||
|
|
||||||
|
private:
|
||||||
|
const not_null<Main::Session*> _session;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Data
|
157
Telegram/SourceFiles/data/components/top_peers.cpp
Normal file
157
Telegram/SourceFiles/data/components/top_peers.cpp
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
/*
|
||||||
|
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 "data/components/top_peers.h"
|
||||||
|
|
||||||
|
#include "api/api_hash.h"
|
||||||
|
#include "apiwrap.h"
|
||||||
|
#include "data/data_peer.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "mtproto/mtproto_config.h"
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kLimit = 32;
|
||||||
|
constexpr auto kRequestTimeLimit = 10 * crl::time(1000);
|
||||||
|
|
||||||
|
[[nodiscard]] float64 RatingDelta(TimeId now, TimeId was, int decay) {
|
||||||
|
return std::exp((now - was) * 1. / decay);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TopPeers::TopPeers(not_null<Main::Session*> session)
|
||||||
|
: _session(session) {
|
||||||
|
using namespace rpl::mappers;
|
||||||
|
crl::on_main(session, [=] {
|
||||||
|
_session->data().chatsListLoadedEvents(
|
||||||
|
) | rpl::filter(_1 == nullptr) | rpl::start_with_next([=] {
|
||||||
|
crl::on_main(_session, [=] {
|
||||||
|
request();
|
||||||
|
});
|
||||||
|
}, _session->lifetime());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TopPeers::~TopPeers() = default;
|
||||||
|
|
||||||
|
std::vector<not_null<PeerData*>> TopPeers::list() const {
|
||||||
|
return _list
|
||||||
|
| ranges::view::transform(&TopPeer::peer)
|
||||||
|
| ranges::to_vector;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TopPeers::disabled() const {
|
||||||
|
return _disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> TopPeers::updates() const {
|
||||||
|
return _updates.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TopPeers::increment(not_null<PeerData*> peer, TimeId date) {
|
||||||
|
if (date <= _lastReceivedDate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (const auto user = peer->asUser(); user && !user->isBot()) {
|
||||||
|
auto changed = false;
|
||||||
|
auto i = ranges::find(_list, peer, &TopPeer::peer);
|
||||||
|
if (i == end(_list)) {
|
||||||
|
_list.push_back({ .peer = peer });
|
||||||
|
i = end(_list) - 1;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
const auto &config = peer->session().mtp().config();
|
||||||
|
const auto decay = config.values().ratingDecay;
|
||||||
|
i->rating += RatingDelta(date, _lastReceivedDate, decay);
|
||||||
|
for (; i != begin(_list); --i) {
|
||||||
|
if (i->rating >= (i - 1)->rating) {
|
||||||
|
changed = true;
|
||||||
|
std::swap(*i, *(i - 1));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
_updates.fire({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TopPeers::reload() {
|
||||||
|
if (_requestId
|
||||||
|
|| (_lastReceived
|
||||||
|
&& _lastReceived + kRequestTimeLimit > crl::now())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
request();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TopPeers::request() {
|
||||||
|
if (_requestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_requestId = _session->api().request(MTPcontacts_GetTopPeers(
|
||||||
|
MTP_flags(MTPcontacts_GetTopPeers::Flag::f_correspondents),
|
||||||
|
MTP_int(0),
|
||||||
|
MTP_int(kLimit),
|
||||||
|
MTP_long(countHash())
|
||||||
|
)).done([=](const MTPcontacts_TopPeers &result, const MTP::Response &response) {
|
||||||
|
_lastReceivedDate = TimeId(response.outerMsgId >> 32);
|
||||||
|
_lastReceived = crl::now();
|
||||||
|
_requestId = 0;
|
||||||
|
|
||||||
|
result.match([&](const MTPDcontacts_topPeers &data) {
|
||||||
|
_disabled = false;
|
||||||
|
const auto owner = &_session->data();
|
||||||
|
owner->processUsers(data.vusers());
|
||||||
|
owner->processChats(data.vchats());
|
||||||
|
for (const auto &category : data.vcategories().v) {
|
||||||
|
const auto &data = category.data();
|
||||||
|
data.vcategory().match(
|
||||||
|
[&](const MTPDtopPeerCategoryCorrespondents &) {
|
||||||
|
_list = ranges::views::all(
|
||||||
|
data.vpeers().v
|
||||||
|
) | ranges::views::transform([&](const MTPTopPeer &top) {
|
||||||
|
return TopPeer{
|
||||||
|
owner->peer(peerFromMTP(top.data().vpeer())),
|
||||||
|
top.data().vrating().v,
|
||||||
|
};
|
||||||
|
}) | ranges::to_vector;
|
||||||
|
}, [](const auto &) {
|
||||||
|
LOG(("API Error: Unexpected top peer category."));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_updates.fire({});
|
||||||
|
}, [&](const MTPDcontacts_topPeersDisabled &) {
|
||||||
|
if (!_disabled) {
|
||||||
|
_list.clear();
|
||||||
|
_disabled = true;
|
||||||
|
_updates.fire({});
|
||||||
|
}
|
||||||
|
}, [](const MTPDcontacts_topPeersNotModified &) {
|
||||||
|
});
|
||||||
|
}).fail([=] {
|
||||||
|
_lastReceived = crl::now();
|
||||||
|
_requestId = 0;
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 TopPeers::countHash() const {
|
||||||
|
using namespace Api;
|
||||||
|
auto hash = HashInit();
|
||||||
|
for (const auto &top : _list) {
|
||||||
|
HashUpdate(hash, peerToUser(top.peer->id).bare);
|
||||||
|
}
|
||||||
|
return HashFinalize(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Data
|
51
Telegram/SourceFiles/data/components/top_peers.h
Normal file
51
Telegram/SourceFiles/data/components/top_peers.h
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Main {
|
||||||
|
class Session;
|
||||||
|
} // namespace Main
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
|
||||||
|
class TopPeers final {
|
||||||
|
public:
|
||||||
|
explicit TopPeers(not_null<Main::Session*> session);
|
||||||
|
~TopPeers();
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<not_null<PeerData*>> list() const;
|
||||||
|
[[nodiscard]] bool disabled() const;
|
||||||
|
[[nodiscard]] rpl::producer<> updates() const;
|
||||||
|
|
||||||
|
void increment(not_null<PeerData*> peer, TimeId date);
|
||||||
|
void reload();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct TopPeer {
|
||||||
|
not_null<PeerData*> peer;
|
||||||
|
float64 rating = 0.;
|
||||||
|
};
|
||||||
|
|
||||||
|
void request();
|
||||||
|
[[nodiscard]] uint64 countHash() const;
|
||||||
|
|
||||||
|
const not_null<Main::Session*> _session;
|
||||||
|
|
||||||
|
std::vector<TopPeer> _list;
|
||||||
|
rpl::event_stream<> _updates;
|
||||||
|
crl::time _lastReceived = 0;
|
||||||
|
TimeId _lastReceivedDate = 0;
|
||||||
|
|
||||||
|
mtpRequestId _requestId = 0;
|
||||||
|
|
||||||
|
bool _disabled = false;
|
||||||
|
bool _received = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Data
|
|
@ -591,6 +591,11 @@ dialogsStoriesFull: DialogsStories {
|
||||||
font: font(11px);
|
font: font(11px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
topPeers: DialogsStories(dialogsStoriesFull) {
|
||||||
|
photo: 46px;
|
||||||
|
photoLeft: 10px;
|
||||||
|
photoTop: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
dialogsStoriesList: DialogsStoriesList {
|
dialogsStoriesList: DialogsStoriesList {
|
||||||
small: dialogsStories;
|
small: dialogsStories;
|
||||||
|
@ -633,3 +638,4 @@ dialogsSearchTagArrowPadding: margins(-6px, 3px, 0px, 0px);
|
||||||
dialogsSearchTagPromoLeft: 6px;
|
dialogsSearchTagPromoLeft: 6px;
|
||||||
dialogsSearchTagPromoRight: 1px;
|
dialogsSearchTagPromoRight: 1px;
|
||||||
dialogsSearchTagPromoSkip: 6px;
|
dialogsSearchTagPromoSkip: 6px;
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/options.h"
|
#include "base/options.h"
|
||||||
#include "dialogs/ui/dialogs_stories_content.h"
|
#include "dialogs/ui/dialogs_stories_content.h"
|
||||||
#include "dialogs/ui/dialogs_stories_list.h"
|
#include "dialogs/ui/dialogs_stories_list.h"
|
||||||
|
#include "dialogs/ui/dialogs_suggestions.h"
|
||||||
#include "dialogs/dialogs_inner_widget.h"
|
#include "dialogs/dialogs_inner_widget.h"
|
||||||
#include "dialogs/dialogs_search_from_controllers.h"
|
#include "dialogs/dialogs_search_from_controllers.h"
|
||||||
#include "dialogs/dialogs_key.h"
|
#include "dialogs/dialogs_key.h"
|
||||||
|
@ -1021,7 +1022,10 @@ void Widget::fullSearchRefreshOn(rpl::producer<> events) {
|
||||||
|
|
||||||
void Widget::updateControlsVisibility(bool fast) {
|
void Widget::updateControlsVisibility(bool fast) {
|
||||||
updateLoadMoreChatsVisibility();
|
updateLoadMoreChatsVisibility();
|
||||||
_scroll->show();
|
_scroll->setVisible(!_suggestions);
|
||||||
|
if (_suggestions) {
|
||||||
|
_suggestions->show();
|
||||||
|
}
|
||||||
updateStoriesVisibility();
|
updateStoriesVisibility();
|
||||||
if ((_openedFolder || _openedForum) && _searchHasFocus.current()) {
|
if ((_openedFolder || _openedForum) && _searchHasFocus.current()) {
|
||||||
setInnerFocus();
|
setInnerFocus();
|
||||||
|
@ -1085,8 +1089,36 @@ void Widget::updateLockUnlockPosition() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Widget::updateHasFocus(not_null<QWidget*> focused) {
|
void Widget::updateHasFocus(not_null<QWidget*> focused) {
|
||||||
_searchHasFocus = (focused == _search.data());
|
const auto has = (focused == _search.data());
|
||||||
updateForceDisplayWide();
|
if (_searchHasFocus.current() != has) {
|
||||||
|
_searchHasFocus = (focused == _search.data());
|
||||||
|
updateStoriesVisibility();
|
||||||
|
updateForceDisplayWide();
|
||||||
|
updateSuggestions(anim::type::normal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::updateSuggestions(anim::type animated) {
|
||||||
|
const auto suggest = _searchHasFocus.current()
|
||||||
|
&& !_searchInChat
|
||||||
|
&& (_inner->state() == WidgetState::Default);
|
||||||
|
if (!suggest && _suggestions) {
|
||||||
|
_suggestions = nullptr;
|
||||||
|
_scroll->show();
|
||||||
|
} else if (suggest && !_suggestions) {
|
||||||
|
_suggestions = std::make_unique<Suggestions>(
|
||||||
|
this,
|
||||||
|
rpl::single(TopPeersContent(&session())));
|
||||||
|
|
||||||
|
_suggestions->topPeerChosen(
|
||||||
|
) | rpl::start_with_next([=](PeerId id) {
|
||||||
|
controller()->showPeerHistory(id);
|
||||||
|
}, _suggestions->lifetime());
|
||||||
|
|
||||||
|
_suggestions->show();
|
||||||
|
_scroll->hide();
|
||||||
|
updateControlsGeometry();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Widget::changeOpenedSubsection(
|
void Widget::changeOpenedSubsection(
|
||||||
|
@ -1513,7 +1545,10 @@ void Widget::startWidthAnimation() {
|
||||||
void Widget::stopWidthAnimation() {
|
void Widget::stopWidthAnimation() {
|
||||||
_widthAnimationCache = QPixmap();
|
_widthAnimationCache = QPixmap();
|
||||||
if (!_showAnimation) {
|
if (!_showAnimation) {
|
||||||
_scroll->show();
|
_scroll->setVisible(!_suggestions);
|
||||||
|
if (_suggestions) {
|
||||||
|
_suggestions->show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateStoriesVisibility();
|
updateStoriesVisibility();
|
||||||
update();
|
update();
|
||||||
|
@ -1528,6 +1563,7 @@ void Widget::updateStoriesVisibility() {
|
||||||
|| _openedForum
|
|| _openedForum
|
||||||
|| !_widthAnimationCache.isNull()
|
|| !_widthAnimationCache.isNull()
|
||||||
|| _childList
|
|| _childList
|
||||||
|
|| _searchHasFocus.current()
|
||||||
|| !_search->getLastText().isEmpty()
|
|| !_search->getLastText().isEmpty()
|
||||||
|| _searchInChat
|
|| _searchInChat
|
||||||
|| _stories->empty();
|
|| _stories->empty();
|
||||||
|
@ -2460,6 +2496,7 @@ void Widget::applySearchUpdate(bool force) {
|
||||||
clearSearchCache();
|
clearSearchCache();
|
||||||
}
|
}
|
||||||
_cancelSearch->toggle(!filterText.isEmpty(), anim::type::normal);
|
_cancelSearch->toggle(!filterText.isEmpty(), anim::type::normal);
|
||||||
|
updateSuggestions(anim::type::instant);
|
||||||
updateLoadMoreChatsVisibility();
|
updateLoadMoreChatsVisibility();
|
||||||
updateJumpToDateVisibility();
|
updateJumpToDateVisibility();
|
||||||
updateLockUnlockPosition();
|
updateLockUnlockPosition();
|
||||||
|
@ -2675,6 +2712,7 @@ bool Widget::setSearchInChat(
|
||||||
if (searchInPeerUpdated) {
|
if (searchInPeerUpdated) {
|
||||||
_searchInChat = chat;
|
_searchInChat = chat;
|
||||||
controller()->setSearchInChat(_searchInChat);
|
controller()->setSearchInChat(_searchInChat);
|
||||||
|
updateSuggestions(anim::type::instant);
|
||||||
updateJumpToDateVisibility();
|
updateJumpToDateVisibility();
|
||||||
updateStoriesVisibility();
|
updateStoriesVisibility();
|
||||||
}
|
}
|
||||||
|
@ -3041,6 +3079,14 @@ void Widget::updateControlsGeometry() {
|
||||||
};
|
};
|
||||||
_updateScrollGeometryCached();
|
_updateScrollGeometryCached();
|
||||||
|
|
||||||
|
if (_suggestions) {
|
||||||
|
_suggestions->setGeometry(
|
||||||
|
0,
|
||||||
|
expandedStoriesTop,
|
||||||
|
scrollWidth,
|
||||||
|
height() - expandedStoriesTop - bottomSkip);
|
||||||
|
}
|
||||||
|
|
||||||
_inner->resize(scrollWidth, _inner->height());
|
_inner->resize(scrollWidth, _inner->height());
|
||||||
_inner->setNarrowRatio(narrowRatio);
|
_inner->setNarrowRatio(narrowRatio);
|
||||||
if (newScrollTop != wasScrollTop) {
|
if (newScrollTop != wasScrollTop) {
|
||||||
|
@ -3097,6 +3143,7 @@ void Widget::keyPressEvent(QKeyEvent *e) {
|
||||||
&& !_openedFolder
|
&& !_openedFolder
|
||||||
&& !_openedForum
|
&& !_openedForum
|
||||||
&& _search->isVisible()
|
&& _search->isVisible()
|
||||||
|
&& !_search->hasFocus()
|
||||||
&& !e->text().isEmpty()) {
|
&& !e->text().isEmpty()) {
|
||||||
_search->setFocusFast();
|
_search->setFocusFast();
|
||||||
QCoreApplication::sendEvent(_search->rawTextEdit(), e);
|
QCoreApplication::sendEvent(_search->rawTextEdit(), e);
|
||||||
|
|
|
@ -75,6 +75,7 @@ class Key;
|
||||||
struct ChosenRow;
|
struct ChosenRow;
|
||||||
class InnerWidget;
|
class InnerWidget;
|
||||||
enum class SearchRequestType;
|
enum class SearchRequestType;
|
||||||
|
class Suggestions;
|
||||||
|
|
||||||
class Widget final : public Window::AbstractSectionWidget {
|
class Widget final : public Window::AbstractSectionWidget {
|
||||||
public:
|
public:
|
||||||
|
@ -242,6 +243,7 @@ private:
|
||||||
void startScrollUpButtonAnimation(bool shown);
|
void startScrollUpButtonAnimation(bool shown);
|
||||||
void updateScrollUpPosition();
|
void updateScrollUpPosition();
|
||||||
void updateLockUnlockPosition();
|
void updateLockUnlockPosition();
|
||||||
|
void updateSuggestions(anim::type animated);
|
||||||
|
|
||||||
MTP::Sender _api;
|
MTP::Sender _api;
|
||||||
|
|
||||||
|
@ -273,6 +275,7 @@ private:
|
||||||
|
|
||||||
object_ptr<Ui::ElasticScroll> _scroll;
|
object_ptr<Ui::ElasticScroll> _scroll;
|
||||||
QPointer<InnerWidget> _inner;
|
QPointer<InnerWidget> _inner;
|
||||||
|
std::unique_ptr<Suggestions> _suggestions;
|
||||||
class BottomButton;
|
class BottomButton;
|
||||||
object_ptr<BottomButton> _updateTelegram = { nullptr };
|
object_ptr<BottomButton> _updateTelegram = { nullptr };
|
||||||
object_ptr<BottomButton> _loadMoreChats = { nullptr };
|
object_ptr<BottomButton> _loadMoreChats = { nullptr };
|
||||||
|
@ -291,7 +294,7 @@ private:
|
||||||
|
|
||||||
Data::Folder *_openedFolder = nullptr;
|
Data::Folder *_openedFolder = nullptr;
|
||||||
Data::Forum *_openedForum = nullptr;
|
Data::Forum *_openedForum = nullptr;
|
||||||
Dialogs::Key _searchInChat;
|
Key _searchInChat;
|
||||||
History *_searchInMigrated = nullptr;
|
History *_searchInMigrated = nullptr;
|
||||||
PeerData *_searchFromAuthor = nullptr;
|
PeerData *_searchFromAuthor = nullptr;
|
||||||
std::vector<Data::ReactionId> _searchTags;
|
std::vector<Data::ReactionId> _searchTags;
|
||||||
|
|
|
@ -184,9 +184,9 @@ rpl::producer<bool> List::toggleExpandedRequests() const {
|
||||||
return _toggleExpandedRequests.events();
|
return _toggleExpandedRequests.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<> List::entered() const {
|
//rpl::producer<> List::entered() const {
|
||||||
return _entered.events();
|
// return _entered.events();
|
||||||
}
|
//}
|
||||||
|
|
||||||
rpl::producer<> List::loadMoreRequests() const {
|
rpl::producer<> List::loadMoreRequests() const {
|
||||||
return _loadMoreRequests.events();
|
return _loadMoreRequests.events();
|
||||||
|
@ -217,9 +217,9 @@ void List::requestExpanded(bool expanded) {
|
||||||
_toggleExpandedRequests.fire_copy(_expanded);
|
_toggleExpandedRequests.fire_copy(_expanded);
|
||||||
}
|
}
|
||||||
|
|
||||||
void List::enterEventHook(QEnterEvent *e) {
|
//void List::enterEventHook(QEnterEvent *e) {
|
||||||
_entered.fire({});
|
//_entered.fire({});
|
||||||
}
|
//}
|
||||||
|
|
||||||
void List::resizeEvent(QResizeEvent *e) {
|
void List::resizeEvent(QResizeEvent *e) {
|
||||||
updateScrollMax();
|
updateScrollMax();
|
||||||
|
|
|
@ -94,7 +94,7 @@ public:
|
||||||
[[nodiscard]] rpl::producer<uint64> clicks() const;
|
[[nodiscard]] rpl::producer<uint64> clicks() const;
|
||||||
[[nodiscard]] rpl::producer<ShowMenuRequest> showMenuRequests() const;
|
[[nodiscard]] rpl::producer<ShowMenuRequest> showMenuRequests() const;
|
||||||
[[nodiscard]] rpl::producer<bool> toggleExpandedRequests() const;
|
[[nodiscard]] rpl::producer<bool> toggleExpandedRequests() const;
|
||||||
[[nodiscard]] rpl::producer<> entered() const;
|
//[[nodiscard]] rpl::producer<> entered() const;
|
||||||
[[nodiscard]] rpl::producer<> loadMoreRequests() const;
|
[[nodiscard]] rpl::producer<> loadMoreRequests() const;
|
||||||
|
|
||||||
[[nodiscard]] auto verticalScrollEvents() const
|
[[nodiscard]] auto verticalScrollEvents() const
|
||||||
|
@ -123,7 +123,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
void showContent(Content &&content);
|
void showContent(Content &&content);
|
||||||
void enterEventHook(QEnterEvent *e) override;
|
//void enterEventHook(QEnterEvent *e) override;
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
void paintEvent(QPaintEvent *e) override;
|
void paintEvent(QPaintEvent *e) override;
|
||||||
void wheelEvent(QWheelEvent *e) override;
|
void wheelEvent(QWheelEvent *e) override;
|
||||||
|
@ -173,7 +173,7 @@ private:
|
||||||
rpl::event_stream<uint64> _clicks;
|
rpl::event_stream<uint64> _clicks;
|
||||||
rpl::event_stream<ShowMenuRequest> _showMenuRequests;
|
rpl::event_stream<ShowMenuRequest> _showMenuRequests;
|
||||||
rpl::event_stream<bool> _toggleExpandedRequests;
|
rpl::event_stream<bool> _toggleExpandedRequests;
|
||||||
rpl::event_stream<> _entered;
|
//rpl::event_stream<> _entered;
|
||||||
rpl::event_stream<> _loadMoreRequests;
|
rpl::event_stream<> _loadMoreRequests;
|
||||||
rpl::event_stream<> _collapsedGeometryChanged;
|
rpl::event_stream<> _collapsedGeometryChanged;
|
||||||
|
|
||||||
|
|
77
Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp
Normal file
77
Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
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 "data/components/top_peers.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "ui/widgets/elastic_scroll.h"
|
||||||
|
#include "ui/widgets/labels.h"
|
||||||
|
#include "ui/wrap/vertical_layout.h"
|
||||||
|
#include "ui/wrap/slide_wrap.h"
|
||||||
|
#include "ui/dynamic_thumbnails.h"
|
||||||
|
#include "styles/style_layers.h"
|
||||||
|
|
||||||
|
namespace Dialogs {
|
||||||
|
|
||||||
|
Suggestions::Suggestions(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
rpl::producer<TopPeersList> topPeers)
|
||||||
|
: RpWidget(parent)
|
||||||
|
, _scroll(std::make_unique<Ui::ElasticScroll>(this))
|
||||||
|
, _content(_scroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
|
||||||
|
, _topPeersWrap(_content->add(object_ptr<Ui::SlideWrap<TopPeersStrip>>(
|
||||||
|
this,
|
||||||
|
object_ptr<TopPeersStrip>(this, std::move(topPeers)))))
|
||||||
|
, _topPeers(_topPeersWrap->entity())
|
||||||
|
, _divider(_content->add(setupDivider())) {
|
||||||
|
_topPeers->emptyValue() | rpl::start_with_next([=](bool empty) {
|
||||||
|
_topPeersWrap->toggle(!empty, anim::type::instant);
|
||||||
|
}, _topPeers->lifetime());
|
||||||
|
|
||||||
|
_topPeers->clicks() | rpl::start_with_next([=](uint64 peerIdRaw) {
|
||||||
|
_topPeerChosen.fire(PeerId(peerIdRaw));
|
||||||
|
}, _topPeers->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
Suggestions::~Suggestions() = default;
|
||||||
|
|
||||||
|
void Suggestions::paintEvent(QPaintEvent *e) {
|
||||||
|
QPainter(this).fillRect(e->rect(), st::windowBg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Suggestions::resizeEvent(QResizeEvent *e) {
|
||||||
|
_scroll->setGeometry(rect());
|
||||||
|
_content->resizeToWidth(width());
|
||||||
|
}
|
||||||
|
|
||||||
|
object_ptr<Ui::RpWidget> Suggestions::setupDivider() {
|
||||||
|
auto result = object_ptr<Ui::DividerLabel>(
|
||||||
|
this,
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
this,
|
||||||
|
rpl::single(u"Recent"_q),
|
||||||
|
st::boxDividerLabel),
|
||||||
|
st::defaultBoxDividerLabelPadding);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
TopPeersList TopPeersContent(not_null<Main::Session*> session) {
|
||||||
|
auto result = TopPeersList();
|
||||||
|
for (const auto &peer : session->topPeers().list()) {
|
||||||
|
result.entries.push_back(TopPeersEntry{
|
||||||
|
.id = peer->id.value,
|
||||||
|
.name = peer->shortName(),
|
||||||
|
.userpic = Ui::MakeUserpicThumbnail(peer),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Dialogs
|
59
Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h
Normal file
59
Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/object_ptr.h"
|
||||||
|
#include "dialogs/ui/top_peers_strip.h"
|
||||||
|
#include "ui/rp_widget.h"
|
||||||
|
|
||||||
|
namespace Main {
|
||||||
|
class Session;
|
||||||
|
} // namespace Main
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class ElasticScroll;
|
||||||
|
class VerticalLayout;
|
||||||
|
template <typename Widget>
|
||||||
|
class SlideWrap;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Dialogs {
|
||||||
|
|
||||||
|
class Suggestions final : public Ui::RpWidget {
|
||||||
|
public:
|
||||||
|
Suggestions(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
rpl::producer<TopPeersList> topPeers);
|
||||||
|
~Suggestions();
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<PeerId> topPeerChosen() const {
|
||||||
|
return _topPeerChosen.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
|
||||||
|
[[nodiscard]] object_ptr<Ui::RpWidget> setupDivider();
|
||||||
|
|
||||||
|
void updateControlsGeometry();
|
||||||
|
|
||||||
|
const std::unique_ptr<Ui::ElasticScroll> _scroll;
|
||||||
|
const not_null<Ui::VerticalLayout*> _content;
|
||||||
|
const not_null<Ui::SlideWrap<TopPeersStrip>*> _topPeersWrap;
|
||||||
|
const not_null<TopPeersStrip*> _topPeers;
|
||||||
|
const not_null<Ui::RpWidget*> _divider;
|
||||||
|
|
||||||
|
rpl::event_stream<PeerId> _topPeerChosen;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] TopPeersList TopPeersContent(
|
||||||
|
not_null<Main::Session*> session);
|
||||||
|
|
||||||
|
} // namespace Dialogs
|
302
Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp
Normal file
302
Telegram/SourceFiles/dialogs/ui/top_peers_strip.cpp
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
/*
|
||||||
|
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/top_peers_strip.h"
|
||||||
|
|
||||||
|
#include "ui/text/text.h"
|
||||||
|
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||||
|
#include "ui/widgets/popup_menu.h"
|
||||||
|
#include "ui/dynamic_image.h"
|
||||||
|
#include "ui/painter.h"
|
||||||
|
#include "styles/style_dialogs.h"
|
||||||
|
#include "styles/style_widgets.h"
|
||||||
|
|
||||||
|
#include <QtWidgets/QApplication>
|
||||||
|
|
||||||
|
namespace Dialogs {
|
||||||
|
|
||||||
|
struct TopPeersStrip::Entry {
|
||||||
|
uint64 id = 0;
|
||||||
|
Ui::Text::String name;
|
||||||
|
std::shared_ptr<Ui::DynamicImage> userpic;
|
||||||
|
bool subscribed = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
TopPeersStrip::TopPeersStrip(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
rpl::producer<TopPeersList> content)
|
||||||
|
: RpWidget(parent) {
|
||||||
|
resize(0, st::topPeers.height);
|
||||||
|
|
||||||
|
std::move(content) | rpl::start_with_next([=](const TopPeersList &list) {
|
||||||
|
apply(list);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
setMouseTracking(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TopPeersStrip::~TopPeersStrip() = default;
|
||||||
|
|
||||||
|
void TopPeersStrip::resizeEvent(QResizeEvent *e) {
|
||||||
|
updateScrollMax();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TopPeersStrip::wheelEvent(QWheelEvent *e) {
|
||||||
|
const auto phase = e->phase();
|
||||||
|
const auto fullDelta = e->pixelDelta().isNull()
|
||||||
|
? e->angleDelta()
|
||||||
|
: e->pixelDelta();
|
||||||
|
if (phase == Qt::ScrollBegin || phase == Qt::ScrollEnd) {
|
||||||
|
_scrollingLock = Qt::Orientation();
|
||||||
|
if (fullDelta.isNull()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto vertical = qAbs(fullDelta.x()) < qAbs(fullDelta.y());
|
||||||
|
if (_scrollingLock == Qt::Orientation() && phase != Qt::NoScrollPhase) {
|
||||||
|
_scrollingLock = vertical ? Qt::Vertical : Qt::Horizontal;
|
||||||
|
}
|
||||||
|
if (_scrollingLock == Qt::Vertical || (vertical && !_scrollLeftMax)) {
|
||||||
|
_verticalScrollEvents.fire(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto delta = vertical
|
||||||
|
? fullDelta.y()
|
||||||
|
: ((style::RightToLeft() ? -1 : 1) * fullDelta.x());
|
||||||
|
|
||||||
|
const auto now = _scrollLeft;
|
||||||
|
const auto used = now - delta;
|
||||||
|
const auto next = std::clamp(used, 0, _scrollLeftMax);
|
||||||
|
if (next != now) {
|
||||||
|
_scrollLeft = next;
|
||||||
|
updateSelected();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
e->accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TopPeersStrip::mousePressEvent(QMouseEvent *e) {
|
||||||
|
if (e->button() != Qt::LeftButton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_lastMousePosition = e->globalPos();
|
||||||
|
updateSelected();
|
||||||
|
|
||||||
|
_mouseDownPosition = _lastMousePosition;
|
||||||
|
_pressed = _selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TopPeersStrip::mouseMoveEvent(QMouseEvent *e) {
|
||||||
|
_lastMousePosition = e->globalPos();
|
||||||
|
updateSelected();
|
||||||
|
|
||||||
|
if (!_dragging && _mouseDownPosition) {
|
||||||
|
if ((_lastMousePosition - *_mouseDownPosition).manhattanLength()
|
||||||
|
>= QApplication::startDragDistance()) {
|
||||||
|
_dragging = true;
|
||||||
|
_startDraggingLeft = _scrollLeft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkDragging();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TopPeersStrip::checkDragging() {
|
||||||
|
if (_dragging) {
|
||||||
|
const auto sign = (style::RightToLeft() ? -1 : 1);
|
||||||
|
const auto newLeft = std::clamp(
|
||||||
|
(sign * (_mouseDownPosition->x() - _lastMousePosition.x())
|
||||||
|
+ _startDraggingLeft),
|
||||||
|
0,
|
||||||
|
_scrollLeftMax);
|
||||||
|
if (newLeft != _scrollLeft) {
|
||||||
|
_scrollLeft = newLeft;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TopPeersStrip::mouseReleaseEvent(QMouseEvent *e) {
|
||||||
|
_lastMousePosition = e->globalPos();
|
||||||
|
const auto guard = gsl::finally([&] {
|
||||||
|
_mouseDownPosition = std::nullopt;
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto pressed = std::exchange(_pressed, -1);
|
||||||
|
if (finishDragging()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateSelected();
|
||||||
|
if (_selected == pressed) {
|
||||||
|
if (_selected < _entries.size()) {
|
||||||
|
_clicks.fire_copy(_entries[_selected].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TopPeersStrip::updateScrollMax() {
|
||||||
|
const auto &st = st::topPeers;
|
||||||
|
const auto single = st.photoLeft * 2 + st.photo;
|
||||||
|
const auto widthFull = int(_entries.size()) * single;
|
||||||
|
_scrollLeftMax = std::max(widthFull - width(), 0);
|
||||||
|
_scrollLeft = std::clamp(_scrollLeft, 0, _scrollLeftMax);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TopPeersStrip::empty() const {
|
||||||
|
return _empty.current();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<bool> TopPeersStrip::emptyValue() const {
|
||||||
|
return _empty.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<uint64> TopPeersStrip::clicks() const {
|
||||||
|
return _clicks.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto TopPeersStrip::showMenuRequests() const
|
||||||
|
-> rpl::producer<ShowTopPeerMenuRequest> {
|
||||||
|
return _showMenuRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TopPeersStrip::apply(const TopPeersList &list) {
|
||||||
|
auto now = std::vector<Entry>();
|
||||||
|
|
||||||
|
if (list.entries.empty()) {
|
||||||
|
_empty = true;
|
||||||
|
}
|
||||||
|
for (const auto &entry : list.entries) {
|
||||||
|
const auto i = ranges::find(_entries, entry.id, &Entry::id);
|
||||||
|
if (i != end(_entries)) {
|
||||||
|
now.push_back(base::take(*i));
|
||||||
|
} else {
|
||||||
|
now.push_back({ .id = entry.id });
|
||||||
|
}
|
||||||
|
apply(now.back(), entry);
|
||||||
|
}
|
||||||
|
for (auto &entry : _entries) {
|
||||||
|
if (entry.subscribed) {
|
||||||
|
entry.userpic->subscribeToUpdates(nullptr);
|
||||||
|
entry.subscribed = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_entries = std::move(now);
|
||||||
|
if (!_entries.empty()) {
|
||||||
|
_empty = false;
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TopPeersStrip::apply(Entry &entry, const TopPeersEntry &data) {
|
||||||
|
Expects(entry.id == data.id);
|
||||||
|
Expects(data.userpic != nullptr);
|
||||||
|
|
||||||
|
if (entry.name.toString() != data.name) {
|
||||||
|
entry.name.setText(st::topPeers.nameStyle, data.name);
|
||||||
|
}
|
||||||
|
if (entry.userpic.get() != data.userpic.get()) {
|
||||||
|
if (entry.subscribed) {
|
||||||
|
entry.userpic->subscribeToUpdates(nullptr);
|
||||||
|
entry.subscribed = 0;
|
||||||
|
}
|
||||||
|
entry.userpic = data.userpic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TopPeersStrip::paintEvent(QPaintEvent *e) {
|
||||||
|
auto p = Painter(this);
|
||||||
|
auto x = -_scrollLeft;
|
||||||
|
const auto &st = st::topPeers;
|
||||||
|
const auto line = st.lineTwice / 2;
|
||||||
|
const auto single = st.photoLeft * 2 + st.photo;
|
||||||
|
for (auto &entry : _entries) {
|
||||||
|
if (!entry.subscribed) {
|
||||||
|
entry.userpic->subscribeToUpdates([=] {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
entry.subscribed = 1;
|
||||||
|
}
|
||||||
|
const auto image = entry.userpic->image(st.photo);
|
||||||
|
p.drawImage(
|
||||||
|
QRect(x + st.photoLeft, st.photoTop, st.photo, st.photo),
|
||||||
|
image);
|
||||||
|
|
||||||
|
const auto nameLeft = x + st.nameLeft;
|
||||||
|
entry.name.drawElided(p, nameLeft, st.nameTop, single, 1, style::al_top);
|
||||||
|
|
||||||
|
x += single;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TopPeersStrip::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
|
_menu = nullptr;
|
||||||
|
|
||||||
|
if (e->reason() == QContextMenuEvent::Mouse) {
|
||||||
|
_lastMousePosition = e->globalPos();
|
||||||
|
updateSelected();
|
||||||
|
}
|
||||||
|
if (_selected < 0 || _entries.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||||
|
this,
|
||||||
|
st::popupMenuWithIcons);
|
||||||
|
_showMenuRequests.fire({
|
||||||
|
_entries[_selected].id,
|
||||||
|
Ui::Menu::CreateAddActionCallback(_menu),
|
||||||
|
});
|
||||||
|
if (_menu->empty()) {
|
||||||
|
_menu = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto updateAfterMenuDestroyed = [=] {
|
||||||
|
const auto globalPosition = QCursor::pos();
|
||||||
|
if (rect().contains(mapFromGlobal(globalPosition))) {
|
||||||
|
_lastMousePosition = globalPosition;
|
||||||
|
updateSelected();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
QObject::connect(
|
||||||
|
_menu.get(),
|
||||||
|
&QObject::destroyed,
|
||||||
|
crl::guard(&_menuGuard, updateAfterMenuDestroyed));
|
||||||
|
_menu->popup(e->globalPos());
|
||||||
|
e->accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TopPeersStrip::finishDragging() {
|
||||||
|
if (!_dragging) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
checkDragging();
|
||||||
|
_dragging = false;
|
||||||
|
updateSelected();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TopPeersStrip::updateSelected() {
|
||||||
|
if (_pressed >= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto &st = st::topPeers;
|
||||||
|
const auto p = mapFromGlobal(_lastMousePosition);
|
||||||
|
const auto x = p.x();
|
||||||
|
const auto single = st.photoLeft * 2 + st.photo;
|
||||||
|
const auto index = (x - _scrollLeft) / single;
|
||||||
|
const auto selected = (index < 0 || index >= _entries.size())
|
||||||
|
? -1
|
||||||
|
: index;
|
||||||
|
if (_selected != selected) {
|
||||||
|
const auto over = (selected >= 0);
|
||||||
|
if (over != (_selected >= 0)) {
|
||||||
|
setCursor(over ? style::cur_pointer : style::cur_default);
|
||||||
|
}
|
||||||
|
_selected = selected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Dialogs
|
93
Telegram/SourceFiles/dialogs/ui/top_peers_strip.h
Normal file
93
Telegram/SourceFiles/dialogs/ui/top_peers_strip.h
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/weak_ptr.h"
|
||||||
|
#include "ui/widgets/menu/menu_add_action_callback.h"
|
||||||
|
#include "ui/rp_widget.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class DynamicImage;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Dialogs {
|
||||||
|
|
||||||
|
struct TopPeersEntry {
|
||||||
|
uint64 id = 0;
|
||||||
|
QString name;
|
||||||
|
std::shared_ptr<Ui::DynamicImage> userpic;
|
||||||
|
uint32 badge : 30 = 0;
|
||||||
|
uint32 muted : 1 = 0;
|
||||||
|
uint32 online : 1 = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TopPeersList {
|
||||||
|
std::vector<TopPeersEntry> entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShowTopPeerMenuRequest {
|
||||||
|
uint64 id = 0;
|
||||||
|
Ui::Menu::MenuCallback callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TopPeersStrip final : public Ui::RpWidget {
|
||||||
|
public:
|
||||||
|
TopPeersStrip(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
rpl::producer<TopPeersList> content);
|
||||||
|
~TopPeersStrip();
|
||||||
|
|
||||||
|
[[nodiscard]] bool empty() const;
|
||||||
|
[[nodiscard]] rpl::producer<bool> emptyValue() const;
|
||||||
|
[[nodiscard]] rpl::producer<uint64> clicks() const;
|
||||||
|
[[nodiscard]] auto showMenuRequests() const
|
||||||
|
-> rpl::producer<ShowTopPeerMenuRequest>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Entry;
|
||||||
|
|
||||||
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
void wheelEvent(QWheelEvent *e) override;
|
||||||
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent *e) override;
|
||||||
|
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||||
|
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||||
|
|
||||||
|
void updateScrollMax();
|
||||||
|
void updateSelected();
|
||||||
|
void checkDragging();
|
||||||
|
bool finishDragging();
|
||||||
|
|
||||||
|
void apply(const TopPeersList &list);
|
||||||
|
void apply(Entry &entry, const TopPeersEntry &data);
|
||||||
|
|
||||||
|
std::vector<Entry> _entries;
|
||||||
|
rpl::variable<bool> _empty = true;
|
||||||
|
|
||||||
|
rpl::event_stream<uint64> _clicks;
|
||||||
|
rpl::event_stream<ShowTopPeerMenuRequest> _showMenuRequests;
|
||||||
|
rpl::event_stream<not_null<QWheelEvent*>> _verticalScrollEvents;
|
||||||
|
|
||||||
|
QPoint _lastMousePosition;
|
||||||
|
std::optional<QPoint> _mouseDownPosition;
|
||||||
|
int _startDraggingLeft = 0;
|
||||||
|
int _scrollLeft = 0;
|
||||||
|
int _scrollLeftMax = 0;
|
||||||
|
bool _dragging = false;
|
||||||
|
Qt::Orientation _scrollingLock = {};
|
||||||
|
|
||||||
|
int _selected = -1;
|
||||||
|
int _pressed = -1;
|
||||||
|
|
||||||
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||||
|
base::has_weak_ptr _menuGuard;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Dialogs
|
|
@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/business/data_shortcut_messages.h"
|
#include "data/business/data_shortcut_messages.h"
|
||||||
#include "data/components/scheduled_messages.h"
|
#include "data/components/scheduled_messages.h"
|
||||||
#include "data/components/sponsored_messages.h"
|
#include "data/components/sponsored_messages.h"
|
||||||
|
#include "data/components/top_peers.h"
|
||||||
#include "data/notify/data_notify_settings.h"
|
#include "data/notify/data_notify_settings.h"
|
||||||
#include "data/stickers/data_stickers.h"
|
#include "data/stickers/data_stickers.h"
|
||||||
#include "data/data_drafts.h"
|
#include "data/data_drafts.h"
|
||||||
|
@ -429,9 +430,13 @@ not_null<HistoryItem*> History::createItem(
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return message.match([&](const auto &data) {
|
const auto result = message.match([&](const auto &data) {
|
||||||
return makeMessage(id, data, localFlags);
|
return makeMessage(id, data, localFlags);
|
||||||
});
|
});
|
||||||
|
if (result->out() && result->isRegular()) {
|
||||||
|
session().topPeers().increment(peer, result->date());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<not_null<HistoryItem*>> History::createItems(
|
std::vector<not_null<HistoryItem*>> History::createItems(
|
||||||
|
|
|
@ -28,8 +28,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "storage/file_upload.h"
|
#include "storage/file_upload.h"
|
||||||
#include "storage/storage_account.h"
|
#include "storage/storage_account.h"
|
||||||
#include "storage/storage_facade.h"
|
#include "storage/storage_facade.h"
|
||||||
|
#include "data/components/recent_peers.h"
|
||||||
#include "data/components/scheduled_messages.h"
|
#include "data/components/scheduled_messages.h"
|
||||||
#include "data/components/sponsored_messages.h"
|
#include "data/components/sponsored_messages.h"
|
||||||
|
#include "data/components/top_peers.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
@ -100,6 +102,7 @@ Session::Session(
|
||||||
, _attachWebView(std::make_unique<InlineBots::AttachWebView>(this))
|
, _attachWebView(std::make_unique<InlineBots::AttachWebView>(this))
|
||||||
, _scheduledMessages(std::make_unique<Data::ScheduledMessages>(this))
|
, _scheduledMessages(std::make_unique<Data::ScheduledMessages>(this))
|
||||||
, _sponsoredMessages(std::make_unique<Data::SponsoredMessages>(this))
|
, _sponsoredMessages(std::make_unique<Data::SponsoredMessages>(this))
|
||||||
|
, _topPeers(std::make_unique<Data::TopPeers>(this))
|
||||||
, _supportHelper(Support::Helper::Create(this))
|
, _supportHelper(Support::Helper::Create(this))
|
||||||
, _saveSettingsTimer([=] { saveSettings(); }) {
|
, _saveSettingsTimer([=] { saveSettings(); }) {
|
||||||
Expects(_settings != nullptr);
|
Expects(_settings != nullptr);
|
||||||
|
|
|
@ -31,8 +31,10 @@ class Templates;
|
||||||
namespace Data {
|
namespace Data {
|
||||||
class Session;
|
class Session;
|
||||||
class Changes;
|
class Changes;
|
||||||
|
class RecentPeers;
|
||||||
class ScheduledMessages;
|
class ScheduledMessages;
|
||||||
class SponsoredMessages;
|
class SponsoredMessages;
|
||||||
|
class TopPeers;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
|
@ -106,12 +108,18 @@ public:
|
||||||
[[nodiscard]] Data::Changes &changes() const {
|
[[nodiscard]] Data::Changes &changes() const {
|
||||||
return *_changes;
|
return *_changes;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] Data::RecentPeers &recentPeers() const {
|
||||||
|
return *_recentPeers;
|
||||||
|
}
|
||||||
[[nodiscard]] Data::SponsoredMessages &sponsoredMessages() const {
|
[[nodiscard]] Data::SponsoredMessages &sponsoredMessages() const {
|
||||||
return *_sponsoredMessages;
|
return *_sponsoredMessages;
|
||||||
}
|
}
|
||||||
[[nodiscard]] Data::ScheduledMessages &scheduledMessages() const {
|
[[nodiscard]] Data::ScheduledMessages &scheduledMessages() const {
|
||||||
return *_scheduledMessages;
|
return *_scheduledMessages;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] Data::TopPeers &topPeers() const {
|
||||||
|
return *_topPeers;
|
||||||
|
}
|
||||||
[[nodiscard]] Api::Updates &updates() const {
|
[[nodiscard]] Api::Updates &updates() const {
|
||||||
return *_updates;
|
return *_updates;
|
||||||
}
|
}
|
||||||
|
@ -232,8 +240,10 @@ private:
|
||||||
const std::unique_ptr<Stickers::GiftBoxPack> _giftBoxStickersPacks;
|
const std::unique_ptr<Stickers::GiftBoxPack> _giftBoxStickersPacks;
|
||||||
const std::unique_ptr<SendAsPeers> _sendAsPeers;
|
const std::unique_ptr<SendAsPeers> _sendAsPeers;
|
||||||
const std::unique_ptr<InlineBots::AttachWebView> _attachWebView;
|
const std::unique_ptr<InlineBots::AttachWebView> _attachWebView;
|
||||||
|
const std::unique_ptr<Data::RecentPeers> _recentPeers;
|
||||||
const std::unique_ptr<Data::ScheduledMessages> _scheduledMessages;
|
const std::unique_ptr<Data::ScheduledMessages> _scheduledMessages;
|
||||||
const std::unique_ptr<Data::SponsoredMessages> _sponsoredMessages;
|
const std::unique_ptr<Data::SponsoredMessages> _sponsoredMessages;
|
||||||
|
const std::unique_ptr<Data::TopPeers> _topPeers;
|
||||||
|
|
||||||
const std::unique_ptr<Support::Helper> _supportHelper;
|
const std::unique_ptr<Support::Helper> _supportHelper;
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,8 @@ QByteArray Config::serialize() const {
|
||||||
+ Serialize::stringSize(_fields.txtDomainString)
|
+ Serialize::stringSize(_fields.txtDomainString)
|
||||||
+ 3 * sizeof(qint32)
|
+ 3 * sizeof(qint32)
|
||||||
+ Serialize::stringSize(_fields.reactionDefaultEmoji)
|
+ Serialize::stringSize(_fields.reactionDefaultEmoji)
|
||||||
+ sizeof(quint64);
|
+ sizeof(quint64)
|
||||||
|
+ sizeof(qint32);
|
||||||
|
|
||||||
auto result = QByteArray();
|
auto result = QByteArray();
|
||||||
result.reserve(size);
|
result.reserve(size);
|
||||||
|
@ -89,7 +90,8 @@ QByteArray Config::serialize() const {
|
||||||
<< qint32(_fields.blockedMode ? 1 : 0)
|
<< qint32(_fields.blockedMode ? 1 : 0)
|
||||||
<< qint32(_fields.captionLengthMax)
|
<< qint32(_fields.captionLengthMax)
|
||||||
<< _fields.reactionDefaultEmoji
|
<< _fields.reactionDefaultEmoji
|
||||||
<< quint64(_fields.reactionDefaultCustom);
|
<< quint64(_fields.reactionDefaultCustom)
|
||||||
|
<< qint32(_fields.ratingDecay);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -185,6 +187,9 @@ std::unique_ptr<Config> Config::FromSerialized(const QByteArray &serialized) {
|
||||||
read(raw->_fields.reactionDefaultEmoji);
|
read(raw->_fields.reactionDefaultEmoji);
|
||||||
read(raw->_fields.reactionDefaultCustom);
|
read(raw->_fields.reactionDefaultCustom);
|
||||||
}
|
}
|
||||||
|
if (!stream.atEnd()) {
|
||||||
|
read(raw->_fields.ratingDecay);
|
||||||
|
}
|
||||||
|
|
||||||
if (stream.status() != QDataStream::Ok
|
if (stream.status() != QDataStream::Ok
|
||||||
|| !raw->_dcOptions.constructFromSerialized(dcOptionsSerialized)) {
|
|| !raw->_dcOptions.constructFromSerialized(dcOptionsSerialized)) {
|
||||||
|
@ -249,6 +254,10 @@ void Config::apply(const MTPDconfig &data) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_fields.autologinToken = qs(data.vautologin_token().value_or_empty());
|
_fields.autologinToken = qs(data.vautologin_token().value_or_empty());
|
||||||
|
_fields.ratingDecay = data.vrating_e_decay().v;
|
||||||
|
if (_fields.ratingDecay <= 0) {
|
||||||
|
_fields.ratingDecay = ConfigFields().ratingDecay;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.vdc_options().v.empty()) {
|
if (data.vdc_options().v.empty()) {
|
||||||
LOG(("MTP Error: config with empty dc_options received!"));
|
LOG(("MTP Error: config with empty dc_options received!"));
|
||||||
|
|
|
@ -39,6 +39,7 @@ struct ConfigFields {
|
||||||
QString txtDomainString;
|
QString txtDomainString;
|
||||||
bool blockedMode = false;
|
bool blockedMode = false;
|
||||||
int captionLengthMax = 1024;
|
int captionLengthMax = 1024;
|
||||||
|
int ratingDecay = 2419200;
|
||||||
QString reactionDefaultEmoji = ConfigDefaultReactionEmoji();
|
QString reactionDefaultEmoji = ConfigDefaultReactionEmoji();
|
||||||
uint64 reactionDefaultCustom;
|
uint64 reactionDefaultCustom;
|
||||||
QString autologinToken;
|
QString autologinToken;
|
||||||
|
|
|
@ -87,6 +87,8 @@ PRIVATE
|
||||||
dialogs/dialogs_three_state_icon.h
|
dialogs/dialogs_three_state_icon.h
|
||||||
dialogs/ui/dialogs_stories_list.cpp
|
dialogs/ui/dialogs_stories_list.cpp
|
||||||
dialogs/ui/dialogs_stories_list.h
|
dialogs/ui/dialogs_stories_list.h
|
||||||
|
dialogs/ui/top_peers_strip.cpp
|
||||||
|
dialogs/ui/top_peers_strip.h
|
||||||
|
|
||||||
editor/controllers/undo_controller.cpp
|
editor/controllers/undo_controller.cpp
|
||||||
editor/controllers/undo_controller.h
|
editor/controllers/undo_controller.h
|
||||||
|
|
Loading…
Add table
Reference in a new issue