Show stories segments in contacts list.

This commit is contained in:
John Preston 2023-07-04 12:29:40 +04:00
parent 451c4e3101
commit 9a29807276
4 changed files with 197 additions and 35 deletions

View File

@ -261,15 +261,23 @@ void PeerListBox::peerListSetRowChecked(
not_null<PeerListRow*> row,
bool checked) {
if (checked) {
addSelectItem(row, anim::type::normal);
if (_controller->trackSelectedList()) {
addSelectItem(row, anim::type::normal);
}
PeerListContentDelegate::peerListSetRowChecked(row, checked);
peerListUpdateRow(row);
// This call deletes row from _searchRows.
_select->entity()->clearQuery();
if (_select) {
_select->entity()->clearQuery();
}
} else {
// The itemRemovedCallback will call changeCheckState() here.
_select->entity()->removeItem(row->id());
if (_select) {
_select->entity()->removeItem(row->id());
} else {
PeerListContentDelegate::peerListSetRowChecked(row, checked);
}
peerListUpdateRow(row);
}
}
@ -1131,6 +1139,24 @@ PeerListRow *PeerListContent::findRow(PeerListRowId id) {
return (it == _rowsById.cend()) ? nullptr : it->second.get();
}
std::optional<QPoint> PeerListContent::lastRowMousePosition() const {
if (!_lastMousePosition) {
return std::nullopt;
}
const auto point = mapFromGlobal(*_lastMousePosition);
auto in = parentWidget()->rect().contains(
parentWidget()->mapFromGlobal(*_lastMousePosition));
auto rowsPointY = point.y() - rowsTop();
const auto index = (in
&& rowsPointY >= 0
&& rowsPointY < shownRowsCount() * _rowHeight)
? (rowsPointY / _rowHeight)
: -1;
return (index >= 0 && index == _selected.index.value)
? QPoint(point.x(), rowsPointY)
: std::optional<QPoint>();
}
void PeerListContent::removeRow(not_null<PeerListRow*> row) {
auto index = row->absoluteIndex();
auto isSearchResult = row->isSearchResult();
@ -1998,10 +2024,12 @@ void PeerListContent::setSearchQuery(
bool PeerListContent::submitted() {
if (const auto row = getRow(_selected.index)) {
_lastMousePosition = std::nullopt;
_controller->rowClicked(row);
return true;
} else if (showingSearch()) {
if (const auto row = getRow(RowIndex(0))) {
_lastMousePosition = std::nullopt;
_controller->rowClicked(row);
return true;
}

View File

@ -327,6 +327,7 @@ public:
virtual void peerListScrollToTop() = 0;
virtual int peerListFullRowsCount() = 0;
virtual PeerListRow *peerListFindRow(PeerListRowId id) = 0;
virtual std::optional<QPoint> peerListLastRowMousePosition() = 0;
virtual void peerListSortRows(Fn<bool(const PeerListRow &a, const PeerListRow &b)> compare) = 0;
virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
virtual void peerListShowBox(
@ -503,6 +504,9 @@ public:
return delegate()->peerListIsRowChecked(row);
}
virtual bool trackSelectedList() {
return true;
}
virtual bool searchInLocal() {
return true;
}
@ -612,6 +616,7 @@ public:
void prependRow(std::unique_ptr<PeerListRow> row);
void prependRowFromSearchResult(not_null<PeerListRow*> row);
PeerListRow *findRow(PeerListRowId id);
std::optional<QPoint> lastRowMousePosition() const;
void updateRow(not_null<PeerListRow*> row) {
updateRow(row, RowIndex());
}
@ -866,6 +871,9 @@ public:
PeerListRow *peerListFindRow(PeerListRowId id) override {
return _content->findRow(id);
}
std::optional<QPoint> peerListLastRowMousePosition() override {
return _content->lastRowMousePosition();
}
void peerListUpdateRow(not_null<PeerListRow*> row) override {
_content->updateRow(row);
}

View File

@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/ui_utility.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_stories.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
@ -109,6 +110,46 @@ private:
};
[[nodiscard]] std::vector<Ui::RoundImageCheckboxSegment> PrepareSegments(
int count,
int unread,
const QBrush &unreadBrush) {
Expects(unread <= count);
Expects(count > 0);
auto result = std::vector<Ui::RoundImageCheckboxSegment>();
const auto add = [&](bool unread) {
result.push_back({
.brush = unread ? unreadBrush : st::dialogsUnreadBgMuted->b,
.width = (unread
? st::dialogsStoriesFull.lineTwice / 2.
: st::dialogsStoriesFull.lineReadTwice / 2.),
});
};
result.reserve(count);
for (auto i = 0, till = count - unread; i != till; ++i) {
add(false);
}
for (auto i = 0; i != unread; ++i) {
add(true);
}
return result;
}
[[nodiscard]] QBrush CreateStoriesGradient() {
const auto &st = st::contactsWithStories.item;
const auto left = st.photoPosition.x();
const auto top = st.photoPosition.y();
auto gradient = QLinearGradient(
QPoint(left + st.photoSize, top),
QPoint(left, top + st.photoSize));
gradient.setStops({
{ 0., st::groupCallLive1->c },
{ 1., st::groupCallMuted1->c },
});
return QBrush(gradient);
}
StoriesRow::StoriesRow(
not_null<Main::Session*> session,
const QBrush &unread,
@ -133,26 +174,8 @@ void StoriesRow::updateGradient(QBrush unread) {
}
void StoriesRow::refreshSegments() {
Expects(_unreadCount <= _count);
Expects(_count > 0);
auto segments = std::vector<Ui::RoundImageCheckboxSegment>();
const auto add = [&](bool unread) {
segments.push_back({
.brush = unread ? _unread : st::dialogsUnreadBgMuted->b,
.width = (unread
? st::dialogsStoriesFull.lineTwice / 2.
: st::dialogsStoriesFull.lineReadTwice / 2.),
});
};
segments.reserve(_count);
for (auto i = 0, count = _count - _unreadCount; i != count; ++i) {
add(false);
}
for (auto i = 0; i != _unreadCount; ++i) {
add(true);
}
setCustomizedCheckSegments(std::move(segments));
setCustomizedCheckSegments(
PrepareSegments(_count, _unreadCount, _unread));
}
StoriesController::StoriesController(
@ -164,20 +187,10 @@ StoriesController::StoriesController(
, _content(std::move(content))
, _open(std::move(open))
, _loadMore(std::move(loadMore)) {
const auto createGradient = [=] {
auto gradient = QLinearGradient(
QPoint(10, 0),
QPoint(0, 10));
gradient.setStops({
{ 0., st::groupCallLive1->c },
{ 1., st::groupCallMuted1->c },
});
_unread = QBrush(gradient);
};
createGradient();
_unread = CreateStoriesGradient();
style::PaletteChanged(
) | rpl::start_with_next([=] {
createGradient();
_unread = CreateStoriesGradient();
for (auto i = 0, count = int(delegate()->peerListFullRowsCount())
; i != count
; ++i) {
@ -248,6 +261,7 @@ void StoriesController::refresh(const Content &content) {
delegate()->peerListAppendRow(std::move(added));
delegate()->peerListSetRowChecked(raw, true);
raw->applySegments(element);
raw->finishCheckedAnimation();
}
++position;
}
@ -351,6 +365,7 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
auto controller = std::make_unique<ContactsBoxController>(
&sessionController->session());
controller->setStyleOverrides(&st::contactsWithStories);
controller->setStoriesShown(true);
const auto raw = controller.get();
auto init = [=](not_null<PeerListBox*> box) {
using namespace Dialogs::Stories;
@ -647,6 +662,33 @@ void ContactsBoxController::prepare() {
prepareViewHook();
if (_storiesShown) {
_storiesUnread = CreateStoriesGradient();
style::PaletteChanged() | rpl::start_with_next([=] {
_storiesUnread = CreateStoriesGradient();
for (auto &entry : _storiesCounts) {
entry.second.count = entry.second.unread = -1;
}
updateStories();
}, lifetime());
const auto stories = &session().data().stories();
rpl::merge(
rpl::single(rpl::empty),
stories->sourcesChanged(Data::StorySourcesList::NotHidden),
stories->sourcesChanged(Data::StorySourcesList::Hidden)
) | rpl::start_with_next([=] {
updateStories();
}, lifetime());
stories->sourceChanged() | rpl::start_with_next([=](PeerId id) {
const auto source = stories->source(id);
const auto info = source
? source->info()
: Data::StoriesSourceInfo();
updateStoriesFor(id.value, info.count, info.unreadCount);
}, lifetime());
}
session().data().contactsLoaded().value(
) | rpl::start_with_next([=] {
rebuildRows();
@ -692,6 +734,14 @@ std::unique_ptr<PeerListRow> ContactsBoxController::createSearchRow(
void ContactsBoxController::rowClicked(not_null<PeerListRow*> row) {
const auto peer = row->peer();
if (const auto window = peer->session().tryResolveWindow()) {
if (_storiesShown) {
const auto point = delegate()->peerListLastRowMousePosition();
const auto &st = st::contactsWithStories.item;
if (point && point->x() < st.photoPosition.x() + st.photoSize) {
window->openPeerStories(peer->id);
return;
}
}
window->showPeerHistory(row->peer());
}
}
@ -717,6 +767,55 @@ void ContactsBoxController::setSortMode(SortMode mode) {
}
}
void ContactsBoxController::setStoriesShown(bool shown) {
_storiesShown = shown;
}
void ContactsBoxController::updateStories() {
const auto stories = &_session->data().stories();
const auto &a = stories->sources(Data::StorySourcesList::NotHidden);
const auto &b = stories->sources(Data::StorySourcesList::Hidden);
auto checked = base::flat_set<PeerListRowId>();
for (const auto &info : ranges::views::concat(a, b)) {
const auto id = info.id.value;
checked.emplace(id);
updateStoriesFor(id, info.count, info.unreadCount);
}
for (auto i = begin(_storiesCounts); i != end(_storiesCounts); ++i) {
if (i->second.count && !checked.contains(i->first)) {
updateStoriesFor(i->first, 0, 0);
}
}
}
void ContactsBoxController::updateStoriesFor(
uint64 id,
int count,
int unread) {
if (const auto row = delegate()->peerListFindRow(id)) {
applyRowStories(row, count, unread);
delegate()->peerListUpdateRow(row);
}
}
void ContactsBoxController::applyRowStories(
not_null<PeerListRow*> row,
int count,
int unread,
bool force) {
auto &counts = _storiesCounts[row->id()];
if (!force && counts.count == count && counts.unread == unread) {
return;
}
counts.count = count;
counts.unread = unread;
delegate()->peerListSetRowChecked(row, count > 0);
if (count > 0) {
row->setCustomizedCheckSegments(
PrepareSegments(count, unread, _storiesUnread));
}
}
void ContactsBoxController::sort() {
switch (_sortMode) {
case SortMode::Alphabet: sortByName(); break;
@ -762,7 +861,15 @@ bool ContactsBoxController::appendRow(not_null<UserData*> user) {
return false;
}
if (auto row = createRow(user)) {
const auto raw = row.get();
delegate()->peerListAppendRow(std::move(row));
if (_storiesShown) {
const auto stories = &session().data().stories();
if (const auto source = stories->source(user->id)) {
const auto info = source->info();
applyRowStories(raw, info.count, info.unreadCount, true);
}
}
return true;
}
return false;

View File

@ -128,12 +128,16 @@ public:
[[nodiscard]] std::unique_ptr<PeerListRow> createSearchRow(
not_null<PeerData*> peer) override final;
void rowClicked(not_null<PeerListRow*> row) override;
bool trackSelectedList() override {
return !_storiesShown;
}
enum class SortMode {
Alphabet,
Online,
};
void setSortMode(SortMode mode);
void setStoriesShown(bool shown);
protected:
virtual std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user);
@ -143,18 +147,33 @@ protected:
}
private:
struct StoriesCount {
int count = 0;
int unread = 0;
};
void sort();
void sortByName();
void sortByOnline();
void rebuildRows();
void updateStories();
void checkForEmptyRows();
bool appendRow(not_null<UserData*> user);
void updateStoriesFor(uint64 id, int count, int unread);
void applyRowStories(
not_null<PeerListRow*> row,
int count,
int unread,
bool force = false);
const not_null<Main::Session*> _session;
SortMode _sortMode = SortMode::Alphabet;
base::Timer _sortByOnlineTimer;
rpl::lifetime _sortByOnlineLifetime;
QBrush _storiesUnread;
base::flat_map<uint64, StoriesCount> _storiesCounts;
bool _storiesShown = false;
};
class ChooseRecipientBoxController