Show stories summary status in chats list.

This commit is contained in:
John Preston 2023-05-23 22:54:13 +04:00
parent 1fc37178b7
commit d57ada8a64
4 changed files with 240 additions and 18 deletions

View File

@ -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...";

View File

@ -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);

View File

@ -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) {

View File

@ -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;