mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-18 23:27:09 +02:00
Show stories summary status in chats list.
This commit is contained in:
parent
1fc37178b7
commit
d57ada8a64
4 changed files with 240 additions and 18 deletions
|
@ -3778,6 +3778,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_userpic_builder_color_subtitle" = "Choose background";
|
||||
"lng_userpic_builder_emoji_subtitle" = "Choose sticker or emoji";
|
||||
|
||||
"lng_stories_row_count#one" = "{count} Story";
|
||||
"lng_stories_row_count#other" = "{count} Stories";
|
||||
"lng_stories_row_unread_and_one" = "{accumulated}, {user}";
|
||||
"lng_stories_row_unread_and_last" = "{accumulated} and {user}";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
"lng_wnd_choose_program_menu" = "Choose Default Program...";
|
||||
|
|
|
@ -495,6 +495,8 @@ DialogsStories {
|
|||
shift: pixels;
|
||||
lineTwice: pixels;
|
||||
lineReadTwice: pixels;
|
||||
nameLeft: pixels;
|
||||
nameRight: pixels;
|
||||
nameTop: pixels;
|
||||
nameStyle: TextStyle;
|
||||
}
|
||||
|
@ -507,7 +509,9 @@ dialogsStories: DialogsStories {
|
|||
shift: 16px;
|
||||
lineTwice: 3px;
|
||||
lineReadTwice: 0px;
|
||||
nameTop: 9px;
|
||||
nameLeft: 11px;
|
||||
nameRight: 10px;
|
||||
nameTop: 3px;
|
||||
nameStyle: semiboldTextStyle;
|
||||
}
|
||||
|
||||
|
@ -519,6 +523,8 @@ dialogsStoriesFull: DialogsStories {
|
|||
photoTop: 9px;
|
||||
lineTwice: 4px;
|
||||
lineReadTwice: 2px;
|
||||
nameLeft: 0px;
|
||||
nameRight: 0px;
|
||||
nameTop: 56px;
|
||||
nameStyle: TextStyle(defaultTextStyle) {
|
||||
font: font(11px);
|
||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "dialogs/ui/dialogs_stories_list.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/painter.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
|
||||
|
@ -17,6 +18,7 @@ namespace {
|
|||
|
||||
constexpr auto kSmallUserpicsShown = 3;
|
||||
constexpr auto kSmallReadOpacity = 0.6;
|
||||
constexpr auto kSummaryExpandLeft = 1.5;
|
||||
|
||||
[[nodiscard]] int AvailableNameWidth() {
|
||||
const auto &full = st::dialogsStoriesFull;
|
||||
|
@ -40,7 +42,7 @@ List::List(
|
|||
}, lifetime());
|
||||
|
||||
_shownAnimation.stop();
|
||||
resize(0, _items.empty() ? 0 : st::dialogsStoriesFull.height);
|
||||
resize(0, _data.empty() ? 0 : st::dialogsStoriesFull.height);
|
||||
}
|
||||
|
||||
void List::showContent(Content &&content) {
|
||||
|
@ -48,24 +50,25 @@ void List::showContent(Content &&content) {
|
|||
return;
|
||||
}
|
||||
if (content.users.empty()) {
|
||||
_hidingItems = base::take(_items);
|
||||
if (!_hidingItems.empty()) {
|
||||
_hidingData = base::take(_data);
|
||||
if (!_hidingData.empty()) {
|
||||
toggleAnimated(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto hidden = _content.users.empty();
|
||||
_content = std::move(content);
|
||||
auto items = base::take(_items.empty() ? _hidingItems : _items);
|
||||
_hidingItems.clear();
|
||||
_items.reserve(_content.users.size());
|
||||
auto items = base::take(
|
||||
_data.items.empty() ? _hidingData.items : _data.items);
|
||||
_hidingData = {};
|
||||
_data.items.reserve(_content.users.size());
|
||||
for (const auto &user : _content.users) {
|
||||
const auto i = ranges::find(items, user.id, [](const Item &item) {
|
||||
return item.user.id;
|
||||
});
|
||||
if (i != end(items)) {
|
||||
_items.push_back(std::move(*i));
|
||||
auto &item = _items.back();
|
||||
_data.items.push_back(std::move(*i));
|
||||
auto &item = _data.items.back();
|
||||
if (item.user.userpic != user.userpic) {
|
||||
item.user.userpic = user.userpic;
|
||||
item.subscribed = false;
|
||||
|
@ -76,16 +79,94 @@ void List::showContent(Content &&content) {
|
|||
}
|
||||
item.user.unread = user.unread;
|
||||
} else {
|
||||
_items.emplace_back(Item{ .user = user });
|
||||
_data.items.emplace_back(Item{ .user = user });
|
||||
}
|
||||
}
|
||||
updateScrollMax();
|
||||
updateSummary(_data);
|
||||
update();
|
||||
if (hidden) {
|
||||
toggleAnimated(true);
|
||||
}
|
||||
}
|
||||
|
||||
List::Summaries List::ComposeSummaries(Data &data) {
|
||||
const auto total = int(data.items.size());
|
||||
auto unreadInFirst = 0;
|
||||
auto unreadTotal = 0;
|
||||
for (auto i = 0; i != total; ++i) {
|
||||
if (data.items[i].user.unread) {
|
||||
++unreadTotal;
|
||||
if (i < kSmallUserpicsShown) {
|
||||
++unreadInFirst;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto result = Summaries();
|
||||
result.total.string
|
||||
= tr::lng_stories_row_count(tr::now, lt_count, total);
|
||||
const auto append = [&](QString &to, int index, bool last) {
|
||||
if (to.isEmpty()) {
|
||||
to = data.items[index].user.name;
|
||||
} else {
|
||||
to = (last
|
||||
? tr::lng_stories_row_unread_and_last
|
||||
: tr::lng_stories_row_unread_and_one)(
|
||||
tr::now,
|
||||
lt_accumulated,
|
||||
to,
|
||||
lt_user,
|
||||
data.items[index].user.name);
|
||||
}
|
||||
};
|
||||
if (!total) {
|
||||
return result;
|
||||
} else if (total <= kSmallUserpicsShown) {
|
||||
for (auto i = 0; i != total; ++i) {
|
||||
append(result.allNames.string, i, i == total - 1);
|
||||
}
|
||||
}
|
||||
if (unreadInFirst > 0 && unreadInFirst == unreadTotal) {
|
||||
for (auto i = 0; i != total; ++i) {
|
||||
if (data.items[i].user.unread) {
|
||||
append(result.unreadNames.string, i, !--unreadTotal);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool List::StringsEqual(const Summaries &a, const Summaries &b) {
|
||||
return (a.total.string == b.total.string)
|
||||
&& (a.allNames.string == b.allNames.string)
|
||||
&& (a.unreadNames.string == b.unreadNames.string);
|
||||
}
|
||||
|
||||
void List::Populate(Summary &summary) {
|
||||
if (summary.empty()) {
|
||||
return;
|
||||
}
|
||||
summary.cache = QImage();
|
||||
summary.text = Ui::Text::String(
|
||||
st::dialogsStories.nameStyle,
|
||||
summary.string);
|
||||
}
|
||||
|
||||
void List::Populate(Summaries &summaries) {
|
||||
Populate(summaries.total);
|
||||
Populate(summaries.allNames);
|
||||
Populate(summaries.unreadNames);
|
||||
}
|
||||
|
||||
void List::updateSummary(Data &data) {
|
||||
auto summaries = ComposeSummaries(data);
|
||||
if (StringsEqual(summaries, data.summaries)) {
|
||||
return;
|
||||
}
|
||||
data.summaries = std::move(summaries);
|
||||
Populate(data.summaries);
|
||||
}
|
||||
|
||||
void List::toggleAnimated(bool shown) {
|
||||
_shownAnimation.start(
|
||||
[=] { updateHeight(); },
|
||||
|
@ -95,16 +176,19 @@ void List::toggleAnimated(bool shown) {
|
|||
}
|
||||
|
||||
void List::updateHeight() {
|
||||
const auto shown = _shownAnimation.value(_items.empty() ? 0. : 1.);
|
||||
const auto shown = _shownAnimation.value(_data.empty() ? 0. : 1.);
|
||||
resize(
|
||||
width(),
|
||||
anim::interpolate(0, st::dialogsStoriesFull.height, shown));
|
||||
if (_data.empty() && shown == 0.) {
|
||||
_hidingData = {};
|
||||
}
|
||||
}
|
||||
|
||||
void List::updateScrollMax() {
|
||||
const auto &full = st::dialogsStoriesFull;
|
||||
const auto singleFull = full.photoLeft * 2 + full.photo;
|
||||
const auto widthFull = full.left + int(_items.size()) * singleFull;
|
||||
const auto widthFull = full.left + int(_data.items.size()) * singleFull;
|
||||
_scrollLeftMax = std::max(widthFull - width(), 0);
|
||||
_scrollLeft = std::clamp(_scrollLeft, 0, _scrollLeftMax);
|
||||
update();
|
||||
|
@ -139,18 +223,19 @@ void List::paintEvent(QPaintEvent *e) {
|
|||
const auto lerp = [=](float64 a, float64 b) {
|
||||
return a + (b - a) * ratio;
|
||||
};
|
||||
auto &rendering = _data.empty() ? _hidingData : _data;
|
||||
const auto photo = lerp(st.photo, full.photo);
|
||||
const auto photoTopSmall = (st.height - st.photo) / 2.;
|
||||
const auto photoTop = lerp(photoTopSmall, full.photoTop);
|
||||
const auto line = lerp(st.lineTwice, full.lineTwice) / 2.;
|
||||
const auto lineRead = lerp(st.lineReadTwice, full.lineReadTwice) / 2.;
|
||||
const auto infoTop = st.nameTop
|
||||
const auto summaryTop = st.nameTop
|
||||
- (st.photoTop + (st.photo / 2.))
|
||||
+ (photoTop + (photo / 2.));
|
||||
const auto singleSmall = st.shift;
|
||||
const auto singleFull = full.photoLeft * 2 + full.photo;
|
||||
const auto single = lerp(singleSmall, singleFull);
|
||||
const auto itemsCount = int(_items.size());
|
||||
const auto itemsCount = int(rendering.items.size());
|
||||
const auto leftSmall = st.left;
|
||||
const auto leftFull = full.left - _scrollLeft;
|
||||
const auto startIndexFull = std::max(-leftFull, 0) / singleFull;
|
||||
|
@ -182,6 +267,8 @@ void List::paintEvent(QPaintEvent *e) {
|
|||
const auto drawFull = (ratio > 0.);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
|
||||
paintSummary(p, rendering, summaryTop, ratio);
|
||||
|
||||
const auto count = std::max(
|
||||
endIndexFull - startIndexFull,
|
||||
endIndexSmall - startIndexSmall);
|
||||
|
@ -201,10 +288,10 @@ void List::paintEvent(QPaintEvent *e) {
|
|||
const auto indexSmall = startIndexSmall + index;
|
||||
const auto indexFull = startIndexFull + index;
|
||||
const auto small = (drawSmall && indexSmall < endIndexSmall)
|
||||
? &_items[indexSmall]
|
||||
? &rendering.items[indexSmall]
|
||||
: nullptr;
|
||||
const auto full = (drawFull && indexFull < endIndexFull)
|
||||
? &_items[indexFull]
|
||||
? &rendering.items[indexFull]
|
||||
: nullptr;
|
||||
const auto x = left + single * index;
|
||||
return Single{ x, indexSmall, small, indexFull, full };
|
||||
|
@ -361,6 +448,87 @@ void List::validateName(not_null<Item*> item) {
|
|||
text.drawElided(p, 0, 0, available, 1, style::al_top);
|
||||
}
|
||||
|
||||
List::Summary &List::ChooseSummary(
|
||||
Summaries &summaries,
|
||||
int totalItems,
|
||||
int fullWidth) {
|
||||
const auto &st = st::dialogsStories;
|
||||
const auto used = std::min(totalItems, kSmallUserpicsShown);
|
||||
const auto taken = st.left
|
||||
+ st.photoLeft
|
||||
+ st.photo
|
||||
+ (used - 1) * st.shift
|
||||
+ st.nameLeft
|
||||
+ st.nameRight;
|
||||
const auto available = fullWidth - taken;
|
||||
const auto prepare = [&](Summary &summary) {
|
||||
if (!summary.empty() && (summary.text.maxWidth() <= available)) {
|
||||
summary.available = available;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (prepare(summaries.unreadNames)) {
|
||||
return summaries.unreadNames;
|
||||
} else if (prepare(summaries.allNames)) {
|
||||
return summaries.allNames;
|
||||
}
|
||||
prepare(summaries.total);
|
||||
return summaries.total;
|
||||
}
|
||||
|
||||
void List::PrerenderSummary(Summary &summary) {
|
||||
if (!summary.cache.isNull()
|
||||
&& summary.cacheForWidth == summary.available
|
||||
&& summary.cacheColor == st::dialogsNameFg->c) {
|
||||
return;
|
||||
}
|
||||
const auto &st = st::dialogsStories;
|
||||
const auto use = std::min(summary.text.maxWidth(), summary.available);
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
summary.cache = QImage(
|
||||
QSize(use, st.nameStyle.font->height) * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
summary.cache.setDevicePixelRatio(ratio);
|
||||
summary.cache.fill(Qt::transparent);
|
||||
auto p = Painter(&summary.cache);
|
||||
p.setPen(st::dialogsNameFg);
|
||||
summary.text.drawElided(p, 0, 0, summary.available);
|
||||
}
|
||||
|
||||
void List::paintSummary(
|
||||
QPainter &p,
|
||||
Data &data,
|
||||
float64 summaryTop,
|
||||
float64 hidden) {
|
||||
const auto total = int(data.items.size());
|
||||
auto &summary = ChooseSummary(data.summaries, total, width());
|
||||
PrerenderSummary(summary);
|
||||
const auto lerp = [&](float64 from, float64 to) {
|
||||
return from + (to - from) * hidden;
|
||||
};
|
||||
const auto &st = st::dialogsStories;
|
||||
const auto &full = st::dialogsStoriesFull;
|
||||
const auto used = std::min(total, kSmallUserpicsShown);
|
||||
const auto fullLeft = st.left
|
||||
+ st.photoLeft
|
||||
+ st.photo
|
||||
+ (used - 1) * st.shift
|
||||
+ st.nameLeft;
|
||||
const auto leftFinal = std::min(
|
||||
full.left + (full.photoLeft * 2 + full.photo) * total,
|
||||
width()) * kSummaryExpandLeft;
|
||||
const auto left = lerp(fullLeft, leftFinal);
|
||||
const auto ratio = summary.cache.devicePixelRatio();
|
||||
const auto summaryWidth = lerp(summary.cache.width() / ratio, 0.);
|
||||
const auto summaryHeight = lerp(summary.cache.height() / ratio, 0.);
|
||||
summaryTop += ((summary.cache.height() / ratio) - summaryHeight) / 2.;
|
||||
p.setOpacity(1. - hidden);
|
||||
p.drawImage(
|
||||
QRectF(left, summaryTop, summaryWidth, summaryHeight),
|
||||
summary.cache);
|
||||
}
|
||||
|
||||
void List::wheelEvent(QWheelEvent *e) {
|
||||
const auto horizontal = (e->angleDelta().x() != 0);
|
||||
if (!horizontal) {
|
||||
|
|
|
@ -55,6 +55,43 @@ private:
|
|||
QColor nameCacheColor;
|
||||
bool subscribed = false;
|
||||
};
|
||||
struct Summary {
|
||||
QString string;
|
||||
Ui::Text::String text;
|
||||
int available = 0;
|
||||
QImage cache;
|
||||
QColor cacheColor;
|
||||
int cacheForWidth = 0;
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return string.isEmpty();
|
||||
}
|
||||
};
|
||||
struct Summaries {
|
||||
Summary total;
|
||||
Summary allNames;
|
||||
Summary unreadNames;
|
||||
};
|
||||
struct Data {
|
||||
std::vector<Item> items;
|
||||
Summaries summaries;
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return items.empty();
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] static Summaries ComposeSummaries(Data &data);
|
||||
[[nodiscard]] static bool StringsEqual(
|
||||
const Summaries &a,
|
||||
const Summaries &b);
|
||||
static void Populate(Summary &summary);
|
||||
static void Populate(Summaries &summaries);
|
||||
[[nodiscard]] static Summary &ChooseSummary(
|
||||
Summaries &summaries,
|
||||
int totalItems,
|
||||
int fullWidth);
|
||||
static void PrerenderSummary(Summary &summary);
|
||||
|
||||
void showContent(Content &&content);
|
||||
void enterEventHook(QEnterEvent *e) override;
|
||||
|
@ -68,15 +105,21 @@ private:
|
|||
void validateUserpic(not_null<Item*> item);
|
||||
void validateName(not_null<Item*> item);
|
||||
void updateScrollMax();
|
||||
void updateSummary(Data &data);
|
||||
void checkDragging();
|
||||
bool finishDragging();
|
||||
|
||||
void updateHeight();
|
||||
void toggleAnimated(bool shown);
|
||||
void paintSummary(
|
||||
QPainter &p,
|
||||
Data &data,
|
||||
float64 summaryTop,
|
||||
float64 hidden);
|
||||
|
||||
Content _content;
|
||||
std::vector<Item> _items;
|
||||
std::vector<Item> _hidingItems;
|
||||
Data _data;
|
||||
Data _hidingData;
|
||||
Fn<int()> _shownHeight = 0;
|
||||
rpl::event_stream<uint64> _clicks;
|
||||
rpl::event_stream<> _expandRequests;
|
||||
|
|
Loading…
Add table
Reference in a new issue