Show stories segments in contacts list.
This commit is contained in:
parent
451c4e3101
commit
9a29807276
|
@ -261,15 +261,23 @@ void PeerListBox::peerListSetRowChecked(
|
||||||
not_null<PeerListRow*> row,
|
not_null<PeerListRow*> row,
|
||||||
bool checked) {
|
bool checked) {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
addSelectItem(row, anim::type::normal);
|
if (_controller->trackSelectedList()) {
|
||||||
|
addSelectItem(row, anim::type::normal);
|
||||||
|
}
|
||||||
PeerListContentDelegate::peerListSetRowChecked(row, checked);
|
PeerListContentDelegate::peerListSetRowChecked(row, checked);
|
||||||
peerListUpdateRow(row);
|
peerListUpdateRow(row);
|
||||||
|
|
||||||
// This call deletes row from _searchRows.
|
// This call deletes row from _searchRows.
|
||||||
_select->entity()->clearQuery();
|
if (_select) {
|
||||||
|
_select->entity()->clearQuery();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// The itemRemovedCallback will call changeCheckState() here.
|
// 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);
|
peerListUpdateRow(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1131,6 +1139,24 @@ PeerListRow *PeerListContent::findRow(PeerListRowId id) {
|
||||||
return (it == _rowsById.cend()) ? nullptr : it->second.get();
|
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) {
|
void PeerListContent::removeRow(not_null<PeerListRow*> row) {
|
||||||
auto index = row->absoluteIndex();
|
auto index = row->absoluteIndex();
|
||||||
auto isSearchResult = row->isSearchResult();
|
auto isSearchResult = row->isSearchResult();
|
||||||
|
@ -1998,10 +2024,12 @@ void PeerListContent::setSearchQuery(
|
||||||
|
|
||||||
bool PeerListContent::submitted() {
|
bool PeerListContent::submitted() {
|
||||||
if (const auto row = getRow(_selected.index)) {
|
if (const auto row = getRow(_selected.index)) {
|
||||||
|
_lastMousePosition = std::nullopt;
|
||||||
_controller->rowClicked(row);
|
_controller->rowClicked(row);
|
||||||
return true;
|
return true;
|
||||||
} else if (showingSearch()) {
|
} else if (showingSearch()) {
|
||||||
if (const auto row = getRow(RowIndex(0))) {
|
if (const auto row = getRow(RowIndex(0))) {
|
||||||
|
_lastMousePosition = std::nullopt;
|
||||||
_controller->rowClicked(row);
|
_controller->rowClicked(row);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -327,6 +327,7 @@ public:
|
||||||
virtual void peerListScrollToTop() = 0;
|
virtual void peerListScrollToTop() = 0;
|
||||||
virtual int peerListFullRowsCount() = 0;
|
virtual int peerListFullRowsCount() = 0;
|
||||||
virtual PeerListRow *peerListFindRow(PeerListRowId id) = 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 void peerListSortRows(Fn<bool(const PeerListRow &a, const PeerListRow &b)> compare) = 0;
|
||||||
virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
|
virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
|
||||||
virtual void peerListShowBox(
|
virtual void peerListShowBox(
|
||||||
|
@ -503,6 +504,9 @@ public:
|
||||||
return delegate()->peerListIsRowChecked(row);
|
return delegate()->peerListIsRowChecked(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual bool trackSelectedList() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
virtual bool searchInLocal() {
|
virtual bool searchInLocal() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -612,6 +616,7 @@ public:
|
||||||
void prependRow(std::unique_ptr<PeerListRow> row);
|
void prependRow(std::unique_ptr<PeerListRow> row);
|
||||||
void prependRowFromSearchResult(not_null<PeerListRow*> row);
|
void prependRowFromSearchResult(not_null<PeerListRow*> row);
|
||||||
PeerListRow *findRow(PeerListRowId id);
|
PeerListRow *findRow(PeerListRowId id);
|
||||||
|
std::optional<QPoint> lastRowMousePosition() const;
|
||||||
void updateRow(not_null<PeerListRow*> row) {
|
void updateRow(not_null<PeerListRow*> row) {
|
||||||
updateRow(row, RowIndex());
|
updateRow(row, RowIndex());
|
||||||
}
|
}
|
||||||
|
@ -866,6 +871,9 @@ public:
|
||||||
PeerListRow *peerListFindRow(PeerListRowId id) override {
|
PeerListRow *peerListFindRow(PeerListRowId id) override {
|
||||||
return _content->findRow(id);
|
return _content->findRow(id);
|
||||||
}
|
}
|
||||||
|
std::optional<QPoint> peerListLastRowMousePosition() override {
|
||||||
|
return _content->lastRowMousePosition();
|
||||||
|
}
|
||||||
void peerListUpdateRow(not_null<PeerListRow*> row) override {
|
void peerListUpdateRow(not_null<PeerListRow*> row) override {
|
||||||
_content->updateRow(row);
|
_content->updateRow(row);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/ui_utility.h"
|
#include "ui/ui_utility.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_stories.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_user.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(
|
StoriesRow::StoriesRow(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
const QBrush &unread,
|
const QBrush &unread,
|
||||||
|
@ -133,26 +174,8 @@ void StoriesRow::updateGradient(QBrush unread) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void StoriesRow::refreshSegments() {
|
void StoriesRow::refreshSegments() {
|
||||||
Expects(_unreadCount <= _count);
|
setCustomizedCheckSegments(
|
||||||
Expects(_count > 0);
|
PrepareSegments(_count, _unreadCount, _unread));
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StoriesController::StoriesController(
|
StoriesController::StoriesController(
|
||||||
|
@ -164,20 +187,10 @@ StoriesController::StoriesController(
|
||||||
, _content(std::move(content))
|
, _content(std::move(content))
|
||||||
, _open(std::move(open))
|
, _open(std::move(open))
|
||||||
, _loadMore(std::move(loadMore)) {
|
, _loadMore(std::move(loadMore)) {
|
||||||
const auto createGradient = [=] {
|
_unread = CreateStoriesGradient();
|
||||||
auto gradient = QLinearGradient(
|
|
||||||
QPoint(10, 0),
|
|
||||||
QPoint(0, 10));
|
|
||||||
gradient.setStops({
|
|
||||||
{ 0., st::groupCallLive1->c },
|
|
||||||
{ 1., st::groupCallMuted1->c },
|
|
||||||
});
|
|
||||||
_unread = QBrush(gradient);
|
|
||||||
};
|
|
||||||
createGradient();
|
|
||||||
style::PaletteChanged(
|
style::PaletteChanged(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
createGradient();
|
_unread = CreateStoriesGradient();
|
||||||
for (auto i = 0, count = int(delegate()->peerListFullRowsCount())
|
for (auto i = 0, count = int(delegate()->peerListFullRowsCount())
|
||||||
; i != count
|
; i != count
|
||||||
; ++i) {
|
; ++i) {
|
||||||
|
@ -248,6 +261,7 @@ void StoriesController::refresh(const Content &content) {
|
||||||
delegate()->peerListAppendRow(std::move(added));
|
delegate()->peerListAppendRow(std::move(added));
|
||||||
delegate()->peerListSetRowChecked(raw, true);
|
delegate()->peerListSetRowChecked(raw, true);
|
||||||
raw->applySegments(element);
|
raw->applySegments(element);
|
||||||
|
raw->finishCheckedAnimation();
|
||||||
}
|
}
|
||||||
++position;
|
++position;
|
||||||
}
|
}
|
||||||
|
@ -351,6 +365,7 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
|
||||||
auto controller = std::make_unique<ContactsBoxController>(
|
auto controller = std::make_unique<ContactsBoxController>(
|
||||||
&sessionController->session());
|
&sessionController->session());
|
||||||
controller->setStyleOverrides(&st::contactsWithStories);
|
controller->setStyleOverrides(&st::contactsWithStories);
|
||||||
|
controller->setStoriesShown(true);
|
||||||
const auto raw = controller.get();
|
const auto raw = controller.get();
|
||||||
auto init = [=](not_null<PeerListBox*> box) {
|
auto init = [=](not_null<PeerListBox*> box) {
|
||||||
using namespace Dialogs::Stories;
|
using namespace Dialogs::Stories;
|
||||||
|
@ -647,6 +662,33 @@ void ContactsBoxController::prepare() {
|
||||||
|
|
||||||
prepareViewHook();
|
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(
|
session().data().contactsLoaded().value(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
rebuildRows();
|
rebuildRows();
|
||||||
|
@ -692,6 +734,14 @@ std::unique_ptr<PeerListRow> ContactsBoxController::createSearchRow(
|
||||||
void ContactsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
void ContactsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||||
const auto peer = row->peer();
|
const auto peer = row->peer();
|
||||||
if (const auto window = peer->session().tryResolveWindow()) {
|
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());
|
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() {
|
void ContactsBoxController::sort() {
|
||||||
switch (_sortMode) {
|
switch (_sortMode) {
|
||||||
case SortMode::Alphabet: sortByName(); break;
|
case SortMode::Alphabet: sortByName(); break;
|
||||||
|
@ -762,7 +861,15 @@ bool ContactsBoxController::appendRow(not_null<UserData*> user) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (auto row = createRow(user)) {
|
if (auto row = createRow(user)) {
|
||||||
|
const auto raw = row.get();
|
||||||
delegate()->peerListAppendRow(std::move(row));
|
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 true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -128,12 +128,16 @@ public:
|
||||||
[[nodiscard]] std::unique_ptr<PeerListRow> createSearchRow(
|
[[nodiscard]] std::unique_ptr<PeerListRow> createSearchRow(
|
||||||
not_null<PeerData*> peer) override final;
|
not_null<PeerData*> peer) override final;
|
||||||
void rowClicked(not_null<PeerListRow*> row) override;
|
void rowClicked(not_null<PeerListRow*> row) override;
|
||||||
|
bool trackSelectedList() override {
|
||||||
|
return !_storiesShown;
|
||||||
|
}
|
||||||
|
|
||||||
enum class SortMode {
|
enum class SortMode {
|
||||||
Alphabet,
|
Alphabet,
|
||||||
Online,
|
Online,
|
||||||
};
|
};
|
||||||
void setSortMode(SortMode mode);
|
void setSortMode(SortMode mode);
|
||||||
|
void setStoriesShown(bool shown);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user);
|
virtual std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user);
|
||||||
|
@ -143,18 +147,33 @@ protected:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct StoriesCount {
|
||||||
|
int count = 0;
|
||||||
|
int unread = 0;
|
||||||
|
};
|
||||||
void sort();
|
void sort();
|
||||||
void sortByName();
|
void sortByName();
|
||||||
void sortByOnline();
|
void sortByOnline();
|
||||||
void rebuildRows();
|
void rebuildRows();
|
||||||
|
void updateStories();
|
||||||
void checkForEmptyRows();
|
void checkForEmptyRows();
|
||||||
bool appendRow(not_null<UserData*> user);
|
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;
|
const not_null<Main::Session*> _session;
|
||||||
SortMode _sortMode = SortMode::Alphabet;
|
SortMode _sortMode = SortMode::Alphabet;
|
||||||
base::Timer _sortByOnlineTimer;
|
base::Timer _sortByOnlineTimer;
|
||||||
rpl::lifetime _sortByOnlineLifetime;
|
rpl::lifetime _sortByOnlineLifetime;
|
||||||
|
|
||||||
|
QBrush _storiesUnread;
|
||||||
|
base::flat_map<uint64, StoriesCount> _storiesCounts;
|
||||||
|
bool _storiesShown = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class ChooseRecipientBoxController
|
class ChooseRecipientBoxController
|
||||||
|
|
Loading…
Reference in New Issue
Block a user