Show badges in new tabs.

This commit is contained in:
John Preston 2025-05-27 16:59:38 +04:00
parent 8512154b45
commit 5943052cd1
5 changed files with 197 additions and 30 deletions

View file

@ -93,6 +93,9 @@ struct BadgesState {
friend inline constexpr auto operator<=>(
BadgesState,
BadgesState) = default;
friend inline constexpr bool operator==(
BadgesState,
BadgesState) = default;
[[nodiscard]] bool empty() const {
return !unread && !mention && !reaction;

View file

@ -180,11 +180,11 @@ void SubsectionTabs::setupSlider(
slider->sectionActivated() | rpl::start_with_next([=](int active) {
if (active >= 0
&& active < _slice.size()
&& _active != _slice[active]) {
&& _active != _slice[active].thread) {
auto params = Window::SectionShow();
params.way = Window::SectionShow::Way::ClearStack;
params.animated = anim::type::instant;
_controller->showThread(_slice[active], {}, params);
_controller->showThread(_slice[active].thread, {}, params);
}
}, slider->lifetime());
@ -209,11 +209,11 @@ void SubsectionTabs::setupSlider(
if (availableFrom < full
&& _beforeSkipped.value_or(0) > 0
&& !_slice.empty()) {
_around = _slice.front();
_around = _slice.front().thread;
refreshSlice();
} else if (availableTill < full) {
if (_afterAvailable > 0) {
_around = _slice.back();
_around = _slice.back().thread;
refreshSlice();
} else if (!_afterSkipped.has_value()) {
_loading = true;
@ -232,9 +232,9 @@ void SubsectionTabs::setupSlider(
};
auto sections = std::vector<Ui::SubsectionTab>();
auto activeIndex = -1;
for (const auto &thread : _slice) {
for (const auto &item : _slice) {
const auto index = int(sections.size());
if (thread == _active) {
if (item.thread == _active) {
activeIndex = index;
}
const auto textFg = [=] {
@ -243,23 +243,24 @@ void SubsectionTabs::setupSlider(
st::windowActiveTextFg,
slider->buttonActive(slider->buttonAt(index)));
};
if (const auto topic = thread->asTopic()) {
if (const auto topic = item.thread->asTopic()) {
if (vertical) {
const auto general = topic->isGeneral();
sections.push_back({
.text = { topic->title() },
.userpic = (topic->iconId()
.text = { item.name },
.userpic = (item.iconId
? Ui::MakeEmojiThumbnail(
&topic->owner(),
Data::SerializeCustomEmojiId(topic->iconId()),
Data::SerializeCustomEmojiId(item.iconId),
paused,
textFg)
: Ui::MakeEmojiThumbnail(
&topic->owner(),
Data::TopicIconEmojiEntity({
.title = (topic->isGeneral()
.title = (general
? Data::ForumGeneralIconTitle()
: topic->title()),
.colorId = (topic->isGeneral()
: item.name),
.colorId = (general
? Data::ForumGeneralIconColor(
st::windowSubTextFg->c)
: topic->colorId()),
@ -272,7 +273,7 @@ void SubsectionTabs::setupSlider(
.text = topic->titleWithIcon(),
});
}
} else if (const auto sublist = thread->asSublist()) {
} else if (const auto sublist = item.thread->asSublist()) {
const auto peer = sublist->sublistPeer();
if (vertical) {
sections.push_back({
@ -294,6 +295,8 @@ void SubsectionTabs::setupSlider(
.userpic = Ui::MakeAllSubsectionsThumbnail(textFg),
});
}
auto &section = sections.back();
section.badges = item.badges;
}
auto scrollSavingThread = (Data::Thread*)nullptr;
@ -309,21 +312,24 @@ void SubsectionTabs::setupSlider(
? slider->lookupSectionPosition(index + 1)
: (indexPosition + scrollValue + 1);
if (indexPosition <= scrollValue && nextPosition > scrollValue) {
scrollSavingThread = _sectionsSlice[index];
scrollSavingThread = _sectionsSlice[index].thread;
scrollSavingShift = scrollValue - indexPosition;
break;
}
indexPosition = nextPosition;
}
scrollSavingIndex = scrollSavingThread
? int(ranges::find(_slice, not_null(scrollSavingThread))
- begin(_slice))
? int(ranges::find(
_slice,
not_null(scrollSavingThread),
&Item::thread
) - begin(_slice))
: -1;
if (scrollSavingIndex == _slice.size()) {
scrollSavingIndex = -1;
for (auto index = 0; index != count; ++index) {
const auto thread = _sectionsSlice[index];
if (ranges::contains(_slice, thread)) {
const auto thread = _sectionsSlice[index].thread;
if (ranges::contains(_slice, thread, &Item::thread)) {
scrollSavingThread = thread;
scrollSavingShift = scrollValue
- slider->lookupSectionPosition(index);
@ -483,6 +489,7 @@ void SubsectionTabs::setVisible(bool shown) {
}
void SubsectionTabs::track() {
using Event = Data::Session::ChatListEntryRefresh;
if (const auto forum = _history->peer->forum()) {
forum->topicDestroyed(
) | rpl::start_with_next([=](not_null<Data::ForumTopic*> topic) {
@ -491,6 +498,19 @@ void SubsectionTabs::track() {
refreshSlice();
}
}, _lifetime);
forum->topicsList()->unreadStateChanges(
) | rpl::start_with_next([=] {
scheduleRefresh();
}, _lifetime);
forum->owner().chatListEntryRefreshes(
) | rpl::filter([=](const Event &event) {
const auto topic = event.filterId ? nullptr : event.key.topic();
return (topic && topic->forum() == forum);
}) | rpl::start_with_next([=] {
scheduleRefresh();
}, _lifetime);
} else if (const auto monoforum = _history->peer->monoforum()) {
monoforum->sublistDestroyed(
) | rpl::start_with_next([=](not_null<Data::SavedSublist*> sublist) {
@ -499,12 +519,29 @@ void SubsectionTabs::track() {
refreshSlice();
}
}, _lifetime);
monoforum->chatsList()->unreadStateChanges(
) | rpl::start_with_next([=] {
scheduleRefresh();
}, _lifetime);
monoforum->owner().chatListEntryRefreshes(
) | rpl::filter([=](const Event &event) {
const auto sublist = event.filterId
? nullptr
: event.key.sublist();
return (sublist && sublist->parent() == monoforum);
}) | rpl::start_with_next([=] {
scheduleRefresh();
}, _lifetime);
} else {
Unexpected("Peer in SubsectionTabs::track.");
}
}
void SubsectionTabs::refreshSlice() {
_refreshScheduled = false;
const auto forum = _history->peer->forum();
const auto monoforum = _history->peer->monoforum();
Assert(forum || monoforum);
@ -512,15 +549,25 @@ void SubsectionTabs::refreshSlice() {
const auto list = forum
? forum->topicsList()
: monoforum->chatsList();
auto slice = std::vector<not_null<Data::Thread*>>();
auto slice = std::vector<Item>();
slice.reserve(_slice.size() + 10);
const auto guard = gsl::finally([&] {
if (_slice != slice) {
_slice = std::move(slice);
_refreshed.fire({});
}
});
const auto push = [&](not_null<Data::Thread*> thread) {
const auto topic = thread->asTopic();
slice.push_back({
.thread = thread,
.badges = thread->chatListBadgesState(),
.iconId = topic ? topic->iconId() : DocumentId(),
.name = thread->chatListName(),
});
};
if (!list) {
slice.push_back(_history);
push(_history);
_beforeSkipped = _afterSkipped = 0;
_afterAvailable = 0;
return;
@ -542,13 +589,25 @@ void SubsectionTabs::refreshSlice() {
_afterAvailable = std::max(0, int(chats.end() - till));
_afterSkipped = list->loaded() ? _afterAvailable : std::optional<int>();
if (from == chats.begin()) {
slice.push_back(_history);
push(_history);
}
for (auto i = from; i != till; ++i) {
slice.push_back((*i)->thread());
push((*i)->thread());
}
}
void SubsectionTabs::scheduleRefresh() {
if (_refreshScheduled) {
return;
}
_refreshScheduled = true;
InvokeQueued(_shadow, [=] {
if (_refreshScheduled) {
refreshSlice();
}
});
}
bool SubsectionTabs::switchTo(
not_null<Data::Thread*> thread,
not_null<Ui::RpWidget*> parent) {

View file

@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "dialogs/dialogs_common.h"
class History;
namespace Data {
@ -53,12 +55,27 @@ public:
void hide();
private:
struct Item {
not_null<Data::Thread*> thread;
Dialogs::BadgesState badges;
DocumentId iconId = 0;
QString name;
friend inline constexpr auto operator<=>(
const Item &,
const Item &) = default;
friend inline constexpr bool operator==(
const Item &,
const Item &) = default;
};
void track();
void setupHorizontal(not_null<QWidget*> parent);
void setupVertical(not_null<QWidget*> parent);
void toggleModes();
void setVisible(bool shown);
void refreshSlice();
void scheduleRefresh();
void loadMore();
[[nodiscard]] rpl::producer<> dataChanged() const;
@ -74,8 +91,8 @@ private:
Ui::RpWidget *_vertical = nullptr;
Ui::RpWidget *_shadow = nullptr;
std::vector<not_null<Data::Thread*>> _slice;
std::vector<not_null<Data::Thread*>> _sectionsSlice;
std::vector<Item> _slice;
std::vector<Item> _sectionsSlice;
not_null<Data::Thread*> _active;
not_null<Data::Thread*> _around;
@ -83,6 +100,7 @@ private:
int _afterLimit = 0;
int _afterAvailable = 0;
bool _loading = false;
bool _refreshScheduled = false;
std::optional<int> _beforeSkipped;
std::optional<int> _afterSkipped;

View file

@ -8,9 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/controls/subsection_tabs_slider.h"
#include "base/call_delayed.h"
#include "dialogs/dialogs_three_state_icon.h"
#include "ui/effects/ripple_animation.h"
#include "ui/dynamic_image.h"
#include "ui/unread_badge_paint.h"
#include "styles/style_chat.h"
#include "styles/style_dialogs.h"
#include "styles/style_filter_icons.h"
namespace Ui {
@ -105,6 +108,41 @@ void VerticalButton::paintEvent(QPaintEvent *e) {
.align = style::al_top,
.paused = _delegate->buttonPaused(),
});
const auto &state = _data.badges;
const auto top = _st.userpicTop / 2;
auto right = width() - textLeft;
UnreadBadgeStyle st;
if (state.unread) {
st.muted = state.unreadMuted;
const auto counter = (state.unreadCounter <= 0)
? QString()
: ((state.mention || state.reaction)
&& (state.unreadCounter > 999))
? (u"99+"_q)
: (state.unreadCounter > 999999)
? (u"99999+"_q)
: QString::number(state.unreadCounter);
const auto badge = PaintUnreadBadge(p, counter, right, top, st);
right -= badge.width() + st.padding;
}
if (state.mention || state.reaction) {
UnreadBadgeStyle st;
st.sizeId = state.mention
? UnreadBadgeSize::Dialogs
: UnreadBadgeSize::ReactionInDialogs;
st.muted = state.mention
? state.mentionMuted
: state.reactionMuted;
st.padding = 0;
st.textTop = 0;
const auto counter = QString();
const auto badge = PaintUnreadBadge(p, counter, right, top, st);
(state.mention
? st::dialogsUnreadMention.icon
: st::dialogsUnreadReaction.icon).paintInCenter(p, badge);
right -= badge.width() + st.padding + st::dialogsUnreadPadding;
}
}
HorizontalButton::HorizontalButton(
@ -118,7 +156,28 @@ HorizontalButton::HorizontalButton(
}
void HorizontalButton::updateSize() {
resize(_st.strictSkip + _text.maxWidth(), _st.height);
auto width = _st.strictSkip + _text.maxWidth();
const auto &state = _data.badges;
UnreadBadgeStyle st;
if (state.unread) {
const auto counter = (state.unreadCounter <= 0)
? QString()
: QString::number(state.unreadCounter);
const auto badge = CountUnreadBadgeSize(counter, st);
width += badge.width() + st.padding;
}
if (state.mention || state.reaction) {
st.sizeId = state.mention
? UnreadBadgeSize::Dialogs
: UnreadBadgeSize::ReactionInDialogs;
st.padding = 0;
st.textTop = 0;
const auto counter = QString();
const auto badge = CountUnreadBadgeSize(counter, st);
width += badge.width() + st.padding + st::dialogsUnreadPadding;
}
resize(width, _st.height);
}
void HorizontalButton::dataUpdatedHook() {
@ -149,6 +208,36 @@ void HorizontalButton::paintEvent(QPaintEvent *e) {
.availableWidth = _text.maxWidth(),
.paused = _delegate->buttonPaused(),
});
auto right = width() - _st.strictSkip + (_st.strictSkip / 2);
UnreadBadgeStyle st;
const auto &state = _data.badges;
const auto badgeTop = (height() - st.size) / 2;
if (state.unread) {
st.muted = state.unreadMuted;
const auto counter = (state.unreadCounter <= 0)
? QString()
: QString::number(state.unreadCounter);
const auto badge = PaintUnreadBadge(p, counter, right, badgeTop, st);
right -= badge.width() + st.padding;
}
if (state.mention || state.reaction) {
UnreadBadgeStyle st;
st.sizeId = state.mention
? UnreadBadgeSize::Dialogs
: UnreadBadgeSize::ReactionInDialogs;
st.muted = state.mention
? state.mentionMuted
: state.reactionMuted;
st.padding = 0;
st.textTop = 0;
const auto counter = QString();
const auto badge = PaintUnreadBadge(p, counter, right, badgeTop, st);
(state.mention
? st::dialogsUnreadMention.icon
: st::dialogsUnreadReaction.icon).paintInCenter(p, badge);
right -= badge.width() + st.padding + st::dialogsUnreadPadding;
}
}
} // namespace

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "dialogs/dialogs_common.h"
#include "ui/round_rect.h"
#include "ui/rp_widget.h"
#include "ui/widgets/buttons.h"
@ -25,10 +26,7 @@ class SubsectionButton;
struct SubsectionTab {
TextWithEntities text;
std::shared_ptr<DynamicImage> userpic;
int counter = 0;
bool muted = false;
bool mention = false;
bool reaciton = false;
Dialogs::BadgesState badges;
};
struct SubsectionTabs {