mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-14 13:17:08 +02:00
Implement basic recent search results.
This commit is contained in:
parent
2e0529bd9a
commit
2a6ff9203b
14 changed files with 456 additions and 57 deletions
BIN
Telegram/Resources/animations/search.tgs
Normal file
BIN
Telegram/Resources/animations/search.tgs
Normal file
Binary file not shown.
|
@ -5094,9 +5094,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_recent_clear" = "Clear";
|
||||
"lng_recent_clear_sure" = "Do you want to clear your search history?";
|
||||
"lng_recent_remove" = "Remove from Recent";
|
||||
"lng_recent_clear_all" = "Clear all";
|
||||
"lng_recent_hide_top" = "Remove all & Disable";
|
||||
"lng_recent_hide_sure" = "Are you sure you want to clear and disable frequent contacts list?\n\nYou can always turn this feature back on in Settings > Privacy > Suggest Frequent Contacts.";
|
||||
"lng_recent_hide_button" = "Hide";
|
||||
"lng_recent_none" = "Recent search results\nwill appear here.";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
|
|
|
@ -24,5 +24,6 @@
|
|||
<file alias="chat_link.tgs">../../animations/chat_link.tgs</file>
|
||||
<file alias="collectible_username.tgs">../../animations/collectible_username.tgs</file>
|
||||
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
|
||||
<file alias="search.tgs">../../animations/search.tgs</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -734,6 +734,10 @@ auto PeerListRow::generateNameWords() const
|
|||
return peer()->nameWords();
|
||||
}
|
||||
|
||||
QPoint PeerListRow::computeNamePosition(
|
||||
const style::PeerListItem &st) const {
|
||||
return st.namePosition;
|
||||
}
|
||||
|
||||
void PeerListRow::invalidatePixmapsCache() {
|
||||
if (_checkbox) {
|
||||
|
@ -1745,8 +1749,9 @@ crl::time PeerListContent::paintRow(
|
|||
? QMargins()
|
||||
: row->rightActionMargins();
|
||||
const auto &name = row->name();
|
||||
const auto namex = _st.item.namePosition.x();
|
||||
const auto namey = _st.item.namePosition.y();
|
||||
const auto namePosition = row->computeNamePosition(_st.item);
|
||||
const auto namex = namePosition.x();
|
||||
const auto namey = namePosition.y();
|
||||
auto namew = outerWidth - namex - skipRight;
|
||||
if (!rightActionSize.isEmpty()
|
||||
&& (namey < rightActionMargins.top() + rightActionSize.height())
|
||||
|
|
|
@ -100,6 +100,8 @@ public:
|
|||
-> const base::flat_set<QChar> &;
|
||||
[[nodiscard]] virtual auto generateNameWords() const
|
||||
-> const base::flat_set<QString> &;
|
||||
[[nodiscard]] virtual QPoint computeNamePosition(
|
||||
const style::PeerListItem &st) const;
|
||||
|
||||
virtual void preloadUserpic();
|
||||
|
||||
|
|
|
@ -80,15 +80,13 @@ void TopPeers::remove(not_null<PeerData*> peer) {
|
|||
const auto i = ranges::find(_list, peer, &TopPeer::peer);
|
||||
if (i != end(_list)) {
|
||||
_list.erase(i);
|
||||
_updates.fire({});
|
||||
updated();
|
||||
}
|
||||
|
||||
_requestId = _session->api().request(MTPcontacts_ResetTopPeerRating(
|
||||
MTP_topPeerCategoryCorrespondents(),
|
||||
peer->input
|
||||
)).send();
|
||||
|
||||
_session->local().writeSearchSuggestionsDelayed();
|
||||
}
|
||||
|
||||
void TopPeers::increment(not_null<PeerData*> peer, TimeId date) {
|
||||
|
@ -117,10 +115,10 @@ void TopPeers::increment(not_null<PeerData*> peer, TimeId date) {
|
|||
}
|
||||
}
|
||||
if (changed) {
|
||||
_updates.fire({});
|
||||
updated();
|
||||
} else {
|
||||
_session->local().writeSearchSuggestionsDelayed();
|
||||
}
|
||||
|
||||
_session->local().writeSearchSuggestionsDelayed();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,11 +138,11 @@ void TopPeers::toggleDisabled(bool disabled) {
|
|||
if (!_disabled || !_list.empty()) {
|
||||
_disabled = true;
|
||||
_list.clear();
|
||||
_updates.fire({});
|
||||
updated();
|
||||
}
|
||||
} else if (_disabled) {
|
||||
_disabled = false;
|
||||
_updates.fire({});
|
||||
updated();
|
||||
}
|
||||
|
||||
_session->api().request(MTPcontacts_ToggleTopPeers(
|
||||
|
@ -154,8 +152,6 @@ void TopPeers::toggleDisabled(bool disabled) {
|
|||
request();
|
||||
}
|
||||
}).send();
|
||||
|
||||
_session->local().writeSearchSuggestionsDelayed();
|
||||
}
|
||||
|
||||
void TopPeers::request() {
|
||||
|
@ -194,12 +190,12 @@ void TopPeers::request() {
|
|||
LOG(("API Error: Unexpected top peer category."));
|
||||
});
|
||||
}
|
||||
_updates.fire({});
|
||||
updated();
|
||||
}, [&](const MTPDcontacts_topPeersDisabled &) {
|
||||
if (!_disabled) {
|
||||
_list.clear();
|
||||
_disabled = true;
|
||||
_updates.fire({});
|
||||
updated();
|
||||
}
|
||||
}, [](const MTPDcontacts_topPeersNotModified &) {
|
||||
});
|
||||
|
@ -218,6 +214,11 @@ uint64 TopPeers::countHash() const {
|
|||
return HashFinalize(hash);
|
||||
}
|
||||
|
||||
void TopPeers::updated() {
|
||||
_updates.fire({});
|
||||
_session->local().writeSearchSuggestionsDelayed();
|
||||
}
|
||||
|
||||
QByteArray TopPeers::serialize() const {
|
||||
_session->local().readSearchSuggestions();
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ private:
|
|||
|
||||
void request();
|
||||
[[nodiscard]] uint64 countHash() const;
|
||||
void updated();
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
|
|
|
@ -600,6 +600,30 @@ topPeers: DialogsStories(dialogsStoriesFull) {
|
|||
topPeersRadius: 4px;
|
||||
topPeersMargin: margins(3px, 3px, 3px, 4px);
|
||||
|
||||
recentPeersEmptySize: 100px;
|
||||
recentPeersEmptyMargin: margins(10px, 10px, 10px, 10px);
|
||||
recentPeersEmptySkip: 10px;
|
||||
recentPeersItem: PeerListItem(defaultPeerListItem) {
|
||||
height: 56px;
|
||||
photoSize: 42px;
|
||||
photoPosition: point(10px, 7px);
|
||||
namePosition: point(64px, 9px);
|
||||
statusPosition: point(64px, 30px);
|
||||
button: OutlineButton(defaultPeerListButton) {
|
||||
textBg: contactsBg;
|
||||
textBgOver: contactsBgOver;
|
||||
ripple: defaultRippleAnimation;
|
||||
}
|
||||
statusFg: contactsStatusFg;
|
||||
statusFgOver: contactsStatusFgOver;
|
||||
statusFgActive: contactsStatusFgOnline;
|
||||
}
|
||||
recentPeersList: PeerList(defaultPeerList) {
|
||||
padding: margins(0px, 4px, 0px, 4px);
|
||||
item: recentPeersItem;
|
||||
}
|
||||
recentPeersSpecialNamePosition: point(64px, 19px);
|
||||
|
||||
dialogsStoriesList: DialogsStoriesList {
|
||||
small: dialogsStories;
|
||||
full: dialogsStoriesFull;
|
||||
|
|
|
@ -58,6 +58,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "storage/storage_media_prepare.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "storage/storage_domain.h"
|
||||
#include "data/components/recent_peers.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
|
@ -514,6 +515,12 @@ Widget::Widget(
|
|||
void Widget::chosenRow(const ChosenRow &row) {
|
||||
storiesToggleExplicitExpand(false);
|
||||
|
||||
if (!_search->getLastText().isEmpty()) {
|
||||
if (const auto history = row.key.history()) {
|
||||
session().recentPeers().bump(history->peer);
|
||||
}
|
||||
}
|
||||
|
||||
const auto history = row.key.history();
|
||||
const auto topicJump = history
|
||||
? history->peer->forumTopicFor(row.message.fullId.msg)
|
||||
|
@ -1120,11 +1127,13 @@ void Widget::updateSuggestions(anim::type animated) {
|
|||
_suggestions = std::make_unique<Suggestions>(
|
||||
this,
|
||||
controller(),
|
||||
TopPeersContent(&session()));
|
||||
TopPeersContent(&session()),
|
||||
RecentPeersContent(&session()));
|
||||
|
||||
_suggestions->topPeerChosen(
|
||||
) | rpl::start_with_next([=](PeerId id) {
|
||||
const auto peer = session().data().peer(id);
|
||||
rpl::merge(
|
||||
_suggestions->topPeerChosen(),
|
||||
_suggestions->recentPeerChosen()
|
||||
) | rpl::start_with_next([=](not_null<PeerData*> peer) {
|
||||
if (base::IsCtrlPressed()) {
|
||||
controller()->showInNewWindow(peer);
|
||||
} else {
|
||||
|
|
|
@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "dialogs/ui/dialogs_suggestions.h"
|
||||
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "data/components/recent_peers.h"
|
||||
#include "data/components/top_peers.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_peer_values.h"
|
||||
|
@ -15,7 +17,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/elastic_scroll.h"
|
||||
|
@ -24,16 +28,83 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/delayed_activation.h"
|
||||
#include "ui/dynamic_thumbnails.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/unread_badge_paint.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 {
|
||||
|
||||
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;
|
||||
|
||||
QPoint computeNamePosition(const style::PeerListItem &st) const override;
|
||||
|
||||
private:
|
||||
const not_null<History*> _history;
|
||||
QString _badgeString;
|
||||
QSize _badgeSize;
|
||||
uint32 _counter : 30 = 0;
|
||||
uint32 _unread : 1 = 0;
|
||||
uint32 _muted : 1 = 0;
|
||||
|
||||
};
|
||||
|
||||
class RecentsController final : public PeerListController {
|
||||
public:
|
||||
RecentsController(
|
||||
not_null<Main::Session*> session,
|
||||
RecentPeersList list);
|
||||
|
||||
[[nodiscard]] rpl::producer<int> count() const {
|
||||
return _count.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<not_null<PeerData*>> chosen() const {
|
||||
return _chosen.events();
|
||||
}
|
||||
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) override;
|
||||
Main::Session &session() const override;
|
||||
|
||||
QString savedMessagesChatStatus() const override;
|
||||
|
||||
private:
|
||||
void setupDivider();
|
||||
void subscribeToEvents();
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
RecentPeersList _recent;
|
||||
rpl::variable<int> _count;
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
rpl::event_stream<not_null<PeerData*>> _chosen;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
void FillTopPeerMenu(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const ShowTopPeerMenuRequest &request,
|
||||
|
@ -92,12 +163,210 @@ void FillTopPeerMenu(
|
|||
});
|
||||
}
|
||||
|
||||
RecentRow::RecentRow(not_null<PeerData*> peer)
|
||||
: PeerListRow(peer)
|
||||
, _history(peer->owner().history(peer)) {
|
||||
if (peer->isSelf() || peer->isRepliesChat()) {
|
||||
setCustomStatus(u" "_q);
|
||||
}
|
||||
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 {
|
||||
return _badgeSize;
|
||||
}
|
||||
|
||||
QMargins RecentRow::rightActionMargins() const {
|
||||
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 (!_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 true;
|
||||
}
|
||||
|
||||
QPoint RecentRow::computeNamePosition(const style::PeerListItem &st) const {
|
||||
return (peer()->isSelf() || peer()->isRepliesChat())
|
||||
? st::recentPeersSpecialNamePosition
|
||||
: st.namePosition;
|
||||
}
|
||||
|
||||
RecentsController::RecentsController(
|
||||
not_null<Main::Session*> session,
|
||||
RecentPeersList list)
|
||||
: _session(session)
|
||||
, _recent(std::move(list)) {
|
||||
}
|
||||
|
||||
void RecentsController::prepare() {
|
||||
setupDivider();
|
||||
|
||||
for (const auto &peer : _recent.list) {
|
||||
delegate()->peerListAppendRow(std::make_unique<RecentRow>(peer));
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
_count = _recent.list.size();
|
||||
|
||||
subscribeToEvents();
|
||||
}
|
||||
|
||||
void RecentsController::rowClicked(not_null<PeerListRow*> row) {
|
||||
_chosen.fire(row->peer());
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> RecentsController::rowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Main::Session &RecentsController::session() const {
|
||||
return *_session;
|
||||
}
|
||||
|
||||
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);
|
||||
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() && (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 != _count.current(); ++i) {
|
||||
const auto row = delegate()->peerListRowAt(i);
|
||||
if (static_cast<RecentRow*>(row.get())->refreshBadge()) {
|
||||
delegate()->peerListUpdateRow(row);
|
||||
}
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Suggestions::Suggestions(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
rpl::producer<TopPeersList> topPeers)
|
||||
rpl::producer<TopPeersList> topPeers,
|
||||
RecentPeersList recentPeers)
|
||||
: RpWidget(parent)
|
||||
, _scroll(std::make_unique<Ui::ElasticScroll>(this))
|
||||
, _content(_scroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
|
||||
|
@ -105,13 +374,22 @@ Suggestions::Suggestions(
|
|||
this,
|
||||
object_ptr<TopPeersStrip>(this, std::move(topPeers)))))
|
||||
, _topPeers(_topPeersWrap->entity())
|
||||
, _divider(_content->add(setupDivider())) {
|
||||
, _recentPeers(
|
||||
_content->add(
|
||||
setupRecentPeers(controller, std::move(recentPeers))))
|
||||
, _emptyRecent(_content->add(setupEmptyRecent(controller))) {
|
||||
_recentCount.value() | rpl::start_with_next([=](int count) {
|
||||
_recentPeers->toggle(count > 0, anim::type::instant);
|
||||
_emptyRecent->toggle(count == 0, anim::type::instant);
|
||||
}, _recentPeers->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) {
|
||||
_topPeerChosen.fire(PeerId(peerIdRaw));
|
||||
const auto peerId = PeerId(peerIdRaw);
|
||||
_topPeerChosen.fire(controller->session().data().peer(peerId));
|
||||
}, _topPeers->lifetime());
|
||||
|
||||
_topPeers->showMenuRequests(
|
||||
|
@ -178,36 +456,79 @@ void Suggestions::paintEvent(QPaintEvent *e) {
|
|||
}
|
||||
|
||||
void Suggestions::resizeEvent(QResizeEvent *e) {
|
||||
_scroll->setGeometry(rect());
|
||||
_content->resizeToWidth(width());
|
||||
const auto w = std::max(width(), st::columnMinimalWidthLeft);
|
||||
_scroll->setGeometry(0, 0, w, height());
|
||||
_content->resizeToWidth(w);
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> Suggestions::setupDivider() {
|
||||
auto result = object_ptr<Ui::FixedHeightWidget>(
|
||||
this,
|
||||
st::searchedBarHeight);
|
||||
const auto raw = result.data();
|
||||
object_ptr<Ui::SlideWrap<>> Suggestions::setupRecentPeers(
|
||||
not_null<Window::SessionController*> window,
|
||||
RecentPeersList recentPeers) {
|
||||
auto &lifetime = _content->lifetime();
|
||||
const auto delegate = lifetime.make_state<
|
||||
PeerListContentDelegateSimple
|
||||
>();
|
||||
const auto controller = lifetime.make_state<RecentsController>(
|
||||
&window->session(),
|
||||
std::move(recentPeers));
|
||||
controller->setStyleOverrides(&st::recentPeersList);
|
||||
|
||||
_recentCount = controller->count();
|
||||
|
||||
controller->chosen(
|
||||
) | rpl::start_with_next([=](not_null<PeerData*> peer) {
|
||||
window->session().recentPeers().bump(peer);
|
||||
_recentPeerChosen.fire_copy(peer);
|
||||
}, lifetime);
|
||||
|
||||
auto content = object_ptr<PeerListContent>(_content, controller);
|
||||
delegate->setContent(content);
|
||||
controller->setDelegate(delegate);
|
||||
|
||||
return object_ptr<Ui::SlideWrap<>>(this, std::move(content));
|
||||
}
|
||||
|
||||
object_ptr<Ui::SlideWrap<>> Suggestions::setupEmptyRecent(
|
||||
not_null<Window::SessionController*> window) {
|
||||
auto content = object_ptr<Ui::RpWidget>(_content);
|
||||
const auto raw = content.data();
|
||||
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
tr::lng_recent_title(),
|
||||
st::searchedBarLabel);
|
||||
const auto clear = Ui::CreateChild<Ui::LinkButton>(
|
||||
tr::lng_recent_none(),
|
||||
st::defaultPeerListAbout);
|
||||
const auto size = st::recentPeersEmptySize;
|
||||
const auto [widget, animate] = Settings::CreateLottieIcon(
|
||||
raw,
|
||||
tr::lng_recent_clear(tr::now),
|
||||
st::searchedBarLink);
|
||||
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());
|
||||
{
|
||||
.name = u"search"_q,
|
||||
.sizeOverride = { size, size },
|
||||
},
|
||||
st::recentPeersEmptyMargin);
|
||||
const auto icon = widget.data();
|
||||
|
||||
_scroll->heightValue() | rpl::start_with_next([=](int height) {
|
||||
raw->resize(raw->width(), height - st::topPeers.height);
|
||||
}, raw->lifetime());
|
||||
raw->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
||||
QPainter(raw).fillRect(clip, st::searchedBarBg);
|
||||
|
||||
raw->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
const auto x = (size.width() - icon->width()) / 2;
|
||||
const auto y = (size.height() - icon->height()) / 3;
|
||||
icon->move(x, y);
|
||||
label->move(
|
||||
(size.width() - label->width()) / 2,
|
||||
y + icon->height() + st::recentPeersEmptySkip);
|
||||
}, raw->lifetime());
|
||||
|
||||
auto result = object_ptr<Ui::SlideWrap<>>(_content, std::move(content));
|
||||
result->toggle(false, anim::type::instant);
|
||||
|
||||
result->toggledValue() | rpl::filter([=](bool shown) {
|
||||
return shown && window->session().data().chatsListLoaded();
|
||||
}) | rpl::start_with_next([=] {
|
||||
animate(anim::repeat::once);
|
||||
}, raw->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -356,4 +677,8 @@ rpl::producer<TopPeersList> TopPeersContent(
|
|||
};
|
||||
}
|
||||
|
||||
RecentPeersList RecentPeersContent(not_null<Main::Session*> session) {
|
||||
return RecentPeersList{ session->recentPeers().list() };
|
||||
}
|
||||
|
||||
} // namespace Dialogs
|
||||
|
|
|
@ -28,12 +28,17 @@ class SessionController;
|
|||
|
||||
namespace Dialogs {
|
||||
|
||||
struct RecentPeersList {
|
||||
std::vector<not_null<PeerData*>> list;
|
||||
};
|
||||
|
||||
class Suggestions final : public Ui::RpWidget {
|
||||
public:
|
||||
Suggestions(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
rpl::producer<TopPeersList> topPeers);
|
||||
rpl::producer<TopPeersList> topPeers,
|
||||
RecentPeersList recentPeers);
|
||||
~Suggestions();
|
||||
|
||||
void selectSkip(int delta);
|
||||
|
@ -42,27 +47,41 @@ public:
|
|||
void selectRight();
|
||||
void chooseRow();
|
||||
|
||||
[[nodiscard]] rpl::producer<PeerId> topPeerChosen() const {
|
||||
[[nodiscard]] rpl::producer<not_null<PeerData*>> topPeerChosen() const {
|
||||
return _topPeerChosen.events();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<not_null<PeerData*>> recentPeerChosen() const {
|
||||
return _recentPeerChosen.events();
|
||||
}
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> setupDivider();
|
||||
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupRecentPeers(
|
||||
not_null<Window::SessionController*> window,
|
||||
RecentPeersList recentPeers);
|
||||
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupEmptyRecent(
|
||||
not_null<Window::SessionController*> window);
|
||||
|
||||
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;
|
||||
rpl::variable<int> _recentCount;
|
||||
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _recentPeers;
|
||||
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _emptyRecent;
|
||||
|
||||
rpl::event_stream<not_null<PeerData*>> _topPeerChosen;
|
||||
rpl::event_stream<not_null<PeerData*>> _recentPeerChosen;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] rpl::producer<TopPeersList> TopPeersContent(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
[[nodiscard]] RecentPeersList RecentPeersContent(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
} // namespace Dialogs
|
||||
|
|
|
@ -30,9 +30,10 @@ struct TopPeersStrip::Entry {
|
|||
QImage userpicFrame;
|
||||
float64 userpicFrameOnline = 0.;
|
||||
QString badgeString;
|
||||
uint32 badge : 28 = 0;
|
||||
uint32 badge : 27 = 0;
|
||||
uint32 userpicFrameDirty : 1 = 0;
|
||||
uint32 subscribed : 1 = 0;
|
||||
uint32 unread : 1 = 0;
|
||||
uint32 online : 1 = 0;
|
||||
uint32 muted : 1 = 0;
|
||||
};
|
||||
|
@ -412,9 +413,15 @@ void TopPeersStrip::apply(Entry &entry, const TopPeersEntry &data) {
|
|||
entry.badgeString = QString();
|
||||
entry.userpicFrameDirty = 1;
|
||||
}
|
||||
if (entry.unread != data.unread) {
|
||||
entry.unread = data.unread;
|
||||
if (!entry.badge) {
|
||||
entry.userpicFrameDirty = 1;
|
||||
}
|
||||
}
|
||||
if (entry.muted != data.muted) {
|
||||
entry.muted = data.muted;
|
||||
if (entry.badge) {
|
||||
if (entry.badge || entry.unread) {
|
||||
entry.userpicFrameDirty = 1;
|
||||
}
|
||||
}
|
||||
|
@ -500,7 +507,7 @@ void TopPeersStrip::paintUserpic(
|
|||
}
|
||||
const auto simple = entry.userpic->image(size);
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto renderFrame = (online > 0) || entry.badge;
|
||||
const auto renderFrame = (online > 0) || entry.badge || entry.unread;
|
||||
if (!renderFrame) {
|
||||
entry.userpicFrame = QImage();
|
||||
p.drawImage(rect, simple);
|
||||
|
@ -541,9 +548,11 @@ void TopPeersStrip::paintUserpic(
|
|||
q.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
}
|
||||
|
||||
if (entry.badge) {
|
||||
if (entry.badge || entry.unread) {
|
||||
if (entry.badgeString.isEmpty()) {
|
||||
entry.badgeString = (entry.badge < 1000)
|
||||
entry.badgeString = !entry.badge
|
||||
? u" "_q
|
||||
: (entry.badge < 1000)
|
||||
? QString::number(entry.badge)
|
||||
: (QString::number(entry.badge / 1000) + 'K');
|
||||
}
|
||||
|
@ -551,7 +560,7 @@ void TopPeersStrip::paintUserpic(
|
|||
st.selected = selected;
|
||||
st.muted = entry.muted;
|
||||
const auto &counter = entry.badgeString;
|
||||
const auto badge = PaintUnreadBadge(q, counter, size, 0, st);
|
||||
PaintUnreadBadge(q, counter, size, 0, st);
|
||||
}
|
||||
|
||||
q.end();
|
||||
|
|
|
@ -100,6 +100,7 @@ Session::Session(
|
|||
, _giftBoxStickersPacks(std::make_unique<Stickers::GiftBoxPack>(this))
|
||||
, _sendAsPeers(std::make_unique<SendAsPeers>(this))
|
||||
, _attachWebView(std::make_unique<InlineBots::AttachWebView>(this))
|
||||
, _recentPeers(std::make_unique<Data::RecentPeers>(this))
|
||||
, _scheduledMessages(std::make_unique<Data::ScheduledMessages>(this))
|
||||
, _sponsoredMessages(std::make_unique<Data::SponsoredMessages>(this))
|
||||
, _topPeers(std::make_unique<Data::TopPeers>(this))
|
||||
|
|
|
@ -79,8 +79,8 @@ void CreateCircleMask(UnreadBadgeSizeData *data, int size) {
|
|||
}
|
||||
|
||||
[[nodiscard]] QString ComputeUnreadBadgeText(
|
||||
const QString &unreadCount,
|
||||
int allowDigits) {
|
||||
const QString &unreadCount,
|
||||
int allowDigits) {
|
||||
return (allowDigits > 0) && (unreadCount.size() > allowDigits + 1)
|
||||
? u".."_q + unreadCount.mid(unreadCount.size() - allowDigits)
|
||||
: unreadCount;
|
||||
|
|
Loading…
Add table
Reference in a new issue