Track mentions / reactions together with unread.

This commit is contained in:
John Preston 2022-10-20 12:57:12 +04:00
parent 5356f6cd2c
commit 4910a60499
22 changed files with 407 additions and 478 deletions

View file

@ -1711,11 +1711,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
if (const auto history = session().data().historyLoaded(id)) {
history->setUnreadMark(data.is_unread());
}
}, [&](const MTPDdialogPeerFolder &dialog) {
//const auto id = dialog.vfolder_id().v; // #TODO archive
//if (const auto folder = session().data().folderLoaded(id)) {
// folder->setUnreadMark(data.is_unread());
//}
}, [](const MTPDdialogPeerFolder &dialog) {
});
} break;

View file

@ -204,17 +204,19 @@ bool ChatFilter::contains(not_null<History*> history) const {
if (_never.contains(history)) {
return false;
}
const auto state = (_flags & (Flag::NoMuted | Flag::NoRead))
? history->chatListBadgesState()
: Dialogs::BadgesState();
return false
|| ((_flags & flag)
&& (!(_flags & Flag::NoMuted)
|| !history->muted()
|| (history->unreadMentions().has()
|| (state.mention
&& history->folderKnown()
&& !history->folder()))
&& (!(_flags & Flag::NoRead)
|| history->chatListUnreadCount()
|| history->unreadMark()
|| history->unreadMentions().has()
|| state.unread
|| state.mention
|| history->fakeUnreadWhileOpened())
&& (!(_flags & Flag::NoArchived)
|| (history->folderKnown() && !history->folder())))

View file

@ -62,7 +62,6 @@ Folder::Folder(not_null<Data::Session*> owner, FolderId id)
}) | rpl::start_with_next([=](const Dialogs::UnreadState &old) {
++_chatListViewVersion;
notifyUnreadStateChange(old);
updateChatListEntryPostponed();
}, _lifetime);
_chatsList.fullSize().changes(
@ -328,24 +327,20 @@ bool Folder::shouldBeInChatList() const {
return !_chatsList.empty();
}
int Folder::chatListUnreadCount() const {
const auto state = chatListUnreadState();
return state.marks
+ (Core::App().settings().countUnreadMessages()
? state.messages
: state.chats);
}
Dialogs::UnreadState Folder::chatListUnreadState() const {
return _chatsList.unreadState();
}
bool Folder::chatListUnreadMark() const {
return false;
}
bool Folder::chatListMutedBadge() const {
return true;
Dialogs::BadgesState Folder::chatListBadgesState() const {
auto result = Dialogs::BadgesForUnread(
chatListUnreadState(),
Dialogs::CountInBadge::Chats,
Dialogs::IncludeInBadge::All);
result.unreadMuted = result.mentionMuted = result.reactionMuted = true;
if (result.unread && !result.unreadCounter) {
result.unreadCounter = 1;
}
return result;
}
HistoryItem *Folder::chatListMessage() const {

View file

@ -44,10 +44,8 @@ public:
int fixedOnTopIndex() const override;
bool shouldBeInChatList() const override;
int chatListUnreadCount() const override;
bool chatListUnreadMark() const override;
bool chatListMutedBadge() const override;
Dialogs::UnreadState chatListUnreadState() const override;
Dialogs::BadgesState chatListBadgesState() const override;
HistoryItem *chatListMessage() const override;
bool chatListMessageKnown() const override;
void requestChatListMessage() override;

View file

@ -155,7 +155,6 @@ ForumTopic::ForumTopic(not_null<Forum*> forum, MsgId rootId)
_replies->unreadCountValue(
) | rpl::combine_previous(
) | rpl::filter([=] {
session().changes().topicUpdated(this, UpdateFlag::UnreadView);
return inChatList();
}) | rpl::start_with_next([=](
std::optional<int> previous,
@ -602,87 +601,52 @@ bool ForumTopic::isServerSideUnread(
return _replies->isServerSideUnread(item);
}
int ForumTopic::unreadCount() const {
return _replies->unreadCountCurrent();
}
int ForumTopic::unreadCountForBadge() const {
const auto result = unreadCount();
return (!result && unreadMark()) ? 1 : result;
}
void ForumTopic::setMuted(bool muted) {
if (this->muted() == muted) {
return;
}
const auto refresher = gsl::finally([&] {
if (inChatList()) {
updateChatListEntry();
}
session().changes().topicUpdated(
this,
Data::TopicUpdate::Flag::Notifications);
});
const auto notify = (unreadCountForBadge() > 0);
const auto state = chatListBadgesState();
const auto notify = state.unread || state.reaction;
const auto notifier = unreadStateChangeNotifier(notify);
Thread::setMuted(muted);
}
bool ForumTopic::unreadCountKnown() const {
return _replies->unreadCountKnown();
}
void ForumTopic::setUnreadMark(bool unread) {
if (unreadMark() == unread) {
return;
}
const auto noUnreadMessages = !unreadCount();
const auto refresher = gsl::finally([&] {
if (inChatList() && noUnreadMessages) {
updateChatListEntry();
}
session().changes().topicUpdated(this, UpdateFlag::UnreadView);
});
const auto notifier = unreadStateChangeNotifier(noUnreadMessages);
Thread::setUnreadMark(unread);
session().changes().topicUpdated(
this,
Data::TopicUpdate::Flag::Notifications);
}
not_null<HistoryView::SendActionPainter*> ForumTopic::sendActionPainter() {
return _sendActionPainter.get();
}
int ForumTopic::chatListUnreadCount() const {
return unreadCount();
Dialogs::UnreadState ForumTopic::chatListUnreadState() const {
return unreadStateFor(
_replies->unreadCountCurrent(),
_replies->unreadCountKnown());
}
Dialogs::UnreadState ForumTopic::chatListUnreadState() const {
return unreadStateFor(unreadCount(), unreadCountKnown());
Dialogs::BadgesState ForumTopic::chatListBadgesState() const {
return Dialogs::BadgesForUnread(
chatListUnreadState(),
Dialogs::CountInBadge::Messages,
Dialogs::IncludeInBadge::All);
}
Dialogs::UnreadState ForumTopic::unreadStateFor(
int count,
bool known) const {
auto result = Dialogs::UnreadState();
const auto mark = !count && unreadMark();
const auto muted = this->muted();
result.messages = count;
result.messagesMuted = muted ? count : 0;
result.chats = count ? 1 : 0;
result.chatsMuted = (count && muted) ? 1 : 0;
result.marks = mark ? 1 : 0;
result.marksMuted = (mark && muted) ? 1 : 0;
result.mentions = unreadMentions().has() ? 1 : 0;
result.reactions = unreadReactions().has() ? 1 : 0;
result.messagesMuted = muted ? result.messages : 0;
result.chatsMuted = muted ? result.chats : 0;
result.reactionsMuted = muted ? result.reactions : 0;
result.known = known;
return result;
}
bool ForumTopic::chatListUnreadMark() const {
return false;
}
bool ForumTopic::chatListMutedBadge() const {
return muted();
}
HistoryItem *ForumTopic::chatListMessage() const {
return _lastMessage.value_or(nullptr);
}
@ -703,6 +667,27 @@ const base::flat_set<QChar> &ForumTopic::chatListFirstLetters() const {
return _titleFirstLetters;
}
void ForumTopic::hasUnreadMentionChanged(bool has) {
auto was = chatListUnreadState();
if (has) {
was.mentions = 0;
} else {
was.mentions = 1;
}
notifyUnreadStateChange(was);
}
void ForumTopic::hasUnreadReactionChanged(bool has) {
auto was = chatListUnreadState();
if (has) {
was.reactions = was.reactionsMuted = 0;
} else {
was.reactions = 1;
was.reactionsMuted = muted() ? was.reactions : 0;
}
notifyUnreadStateChange(was);
}
const QString &ForumTopic::chatListNameSortKey() const {
static const auto empty = QString();
return empty;

View file

@ -81,10 +81,8 @@ public:
int fixedOnTopIndex() const override;
bool shouldBeInChatList() const override;
int chatListUnreadCount() const override;
bool chatListUnreadMark() const override;
bool chatListMutedBadge() const override;
Dialogs::UnreadState chatListUnreadState() const override;
Dialogs::BadgesState chatListBadgesState() const override;
HistoryItem *chatListMessage() const override;
bool chatListMessageKnown() const override;
void requestChatListMessage() override;
@ -93,6 +91,9 @@ public:
const base::flat_set<QString> &chatListNameWords() const override;
const base::flat_set<QChar> &chatListFirstLetters() const override;
void hasUnreadMentionChanged(bool has) override;
void hasUnreadReactionChanged(bool has) override;
[[nodiscard]] HistoryItem *lastMessage() const;
[[nodiscard]] HistoryItem *lastServerMessage() const;
[[nodiscard]] bool lastMessageKnown() const;
@ -125,13 +126,7 @@ public:
[[nodiscard]] bool isServerSideUnread(
not_null<const HistoryItem*> item) const override;
[[nodiscard]] int unreadCount() const;
[[nodiscard]] bool unreadCountKnown() const;
[[nodiscard]] int unreadCountForBadge() const; // unreadCount || unreadMark ? 1 : 0.
void setMuted(bool muted) override;
void setUnreadMark(bool unread) override;
[[nodiscard]] auto sendActionPainter()
->not_null<HistoryView::SendActionPainter*> override;

View file

@ -144,7 +144,7 @@ void Thread::setMuted(bool muted) {
}
}
void Thread::setUnreadMark(bool unread) {
void Thread::setUnreadMarkFlag(bool unread) {
if (unread) {
_flags |= Flag::UnreadMark;
} else {

View file

@ -71,6 +71,8 @@ public:
[[nodiscard]] HistoryUnreadThings::ConstProxy unreadMentions() const;
[[nodiscard]] HistoryUnreadThings::Proxy unreadReactions();
[[nodiscard]] HistoryUnreadThings::ConstProxy unreadReactions() const;
virtual void hasUnreadMentionChanged(bool has) = 0;
virtual void hasUnreadReactionChanged(bool has) = 0;
void removeNotification(not_null<HistoryItem*> item);
void clearNotifications();
@ -90,7 +92,6 @@ public:
[[nodiscard]] bool unreadMark() const {
return (_flags & Flag::UnreadMark);
}
virtual void setUnreadMark(bool unread);
[[nodiscard]] virtual bool isServerSideUnread(
not_null<const HistoryItem*> item) const = 0;
@ -108,6 +109,9 @@ public:
[[nodiscard]] virtual auto sendActionPainter()
-> not_null<HistoryView::SendActionPainter*> = 0;
protected:
void setUnreadMarkFlag(bool unread);
private:
enum class Flag : uchar {
UnreadMark = (1 << 0),

View file

@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h"
#include "data/data_forum_topic.h"
#include "data/data_chat_filters.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "mainwidget.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
@ -44,6 +46,37 @@ uint64 PinnedDialogPos(int pinnedIndex) {
} // namespace
BadgesState BadgesForUnread(
const UnreadState &state,
CountInBadge count,
IncludeInBadge include) {
const auto countMessages = (count == CountInBadge::Messages)
|| ((count == CountInBadge::Default)
&& Core::App().settings().countUnreadMessages());
const auto counterFull = state.marks
+ (countMessages ? state.messages : state.chats);
const auto counterMuted = state.marksMuted
+ (countMessages ? state.messagesMuted : state.chatsMuted);
const auto unreadMuted = (counterFull <= counterMuted);
const auto includeMuted = (include == IncludeInBadge::All)
|| (include == IncludeInBadge::UnmutedOrAll && unreadMuted)
|| ((include == IncludeInBadge::Default)
&& Core::App().settings().includeMutedCounter());
const auto marks = state.marks - (includeMuted ? 0 : state.marksMuted);
const auto counter = counterFull - (includeMuted ? 0 : counterMuted);
const auto mark = (counter == 1) && (marks == 1);
return {
.unreadCounter = mark ? 0 : counter,
.unread = (counter > 0),
.unreadMuted = includeMuted && (counter <= counterMuted),
.mention = (state.mentions > 0),
.reaction = (state.reactions > 0),
.reactionMuted = (state.reactions <= state.reactionsMuted),
};
}
Entry::Entry(not_null<Data::Session*> owner, Type type)
: _owner(owner)
, _flags((type == Type::History)
@ -181,8 +214,24 @@ void Entry::notifyUnreadStateChange(const UnreadState &wasState) {
owner().chatsListFor(this)->unreadStateChanged(wasState, nowState);
auto &filters = owner().chatsFilters();
for (const auto &[filterId, links] : _chatListLinks) {
filters.chatsList(filterId)->unreadStateChanged(wasState, nowState);
if (filterId) {
filters.chatsList(filterId)->unreadStateChanged(
wasState,
nowState);
}
}
if (const auto history = asHistory()) {
session().changes().historyUpdated(
history,
Data::HistoryUpdate::Flag::UnreadView);
const auto isForFilters = [](UnreadState state) {
return state.messages || state.marks || state.mentions;
};
if (isForFilters(wasState) != isForFilters(nowState)) {
owner().chatsFilters().refreshHistory(history);
}
}
updateChatListEntryPostponed();
}
const Ui::Text::String &Entry::chatListNameText() const {

View file

@ -64,6 +64,9 @@ struct UnreadState {
int chatsMuted = 0;
int marks = 0;
int marksMuted = 0;
int reactions = 0;
int reactionsMuted = 0;
int mentions = 0;
bool known = false;
UnreadState &operator+=(const UnreadState &other) {
@ -73,6 +76,9 @@ struct UnreadState {
chatsMuted += other.chatsMuted;
marks += other.marks;
marksMuted += other.marksMuted;
reactions += other.reactions;
reactionsMuted += other.reactionsMuted;
mentions += other.mentions;
return *this;
}
UnreadState &operator-=(const UnreadState &other) {
@ -82,12 +88,11 @@ struct UnreadState {
chatsMuted -= other.chatsMuted;
marks -= other.marks;
marksMuted -= other.marksMuted;
reactions -= other.reactions;
reactionsMuted -= other.reactionsMuted;
mentions -= other.mentions;
return *this;
}
bool empty() const {
return !messages && !chats && !marks;
}
};
inline UnreadState operator+(const UnreadState &a, const UnreadState &b) {
@ -102,6 +107,42 @@ inline UnreadState operator-(const UnreadState &a, const UnreadState &b) {
return result;
}
struct BadgesState {
int unreadCounter = 0;
bool unread : 1 = false;
bool unreadMuted : 1 = false;
bool mention : 1 = false;
bool mentionMuted : 1 = false;
bool reaction : 1 = false;
bool reactionMuted : 1 = false;
friend inline constexpr auto operator<=>(
BadgesState,
BadgesState) = default;
[[nodiscard]] bool empty() const {
return !unread && !mention && !reaction;
}
};
enum class CountInBadge : uchar {
Default,
Chats,
Messages,
};
enum class IncludeInBadge : uchar {
Default,
Unmuted,
All,
UnmutedOrAll,
};
[[nodiscard]] BadgesState BadgesForUnread(
const UnreadState &state,
CountInBadge count = CountInBadge::Default,
IncludeInBadge include = IncludeInBadge::Default);
class Entry : public base::has_weak_ptr {
public:
enum class Type : uchar {
@ -167,10 +208,8 @@ public:
static constexpr auto kTopPromotionFixOnTopIndex = 2;
virtual bool shouldBeInChatList() const = 0;
virtual int chatListUnreadCount() const = 0;
virtual bool chatListUnreadMark() const = 0;
virtual bool chatListMutedBadge() const = 0;
virtual UnreadState chatListUnreadState() const = 0;
virtual BadgesState chatListBadgesState() const = 0;
virtual HistoryItem *chatListMessage() const = 0;
virtual bool chatListMessageKnown() const = 0;
virtual void requestChatListMessage() = 0;
@ -197,21 +236,13 @@ public:
}
[[nodiscard]] const Ui::Text::String &chatListNameText() const;
[[nodiscard]] Ui::PeerBadge &chatListBadge() const {
return _chatListBadge;
[[nodiscard]] Ui::PeerBadge &chatListPeerBadge() const {
return _chatListPeerBadge;
}
protected:
void notifyUnreadStateChange(const UnreadState &wasState);
auto unreadStateChangeNotifier(bool required) {
const auto notify = required && inChatList();
const auto wasState = notify ? chatListUnreadState() : UnreadState();
return gsl::finally([=] {
if (notify) {
notifyUnreadStateChange(wasState);
}
});
}
auto unreadStateChangeNotifier(bool required);
[[nodiscard]] int lookupPinnedIndex(FilterId filterId) const;
@ -220,6 +251,7 @@ private:
IsThread = (1 << 0),
IsHistory = (1 << 1),
UpdatePostponed = (1 << 2),
InUnreadChangeBlock = (1 << 3),
};
friend inline constexpr bool is_flag_type(Flag) { return true; }
using Flags = base::flags<Flag>;
@ -239,7 +271,7 @@ private:
uint64 _sortKeyInChatList = 0;
uint64 _sortKeyByDate = 0;
base::flat_map<FilterId, int> _pinnedIndex;
mutable Ui::PeerBadge _chatListBadge;
mutable Ui::PeerBadge _chatListPeerBadge;
mutable Ui::Text::String _chatListNameText;
mutable int _chatListNameVersion = 0;
TimeId _timeId = 0;
@ -247,4 +279,19 @@ private:
};
auto Entry::unreadStateChangeNotifier(bool required) {
Expects(!(_flags & Flag::InUnreadChangeBlock));
_flags |= Flag::InUnreadChangeBlock;
const auto notify = required && inChatList();
const auto wasState = notify ? chatListUnreadState() : UnreadState();
return gsl::finally([=] {
_flags &= ~Flag::InUnreadChangeBlock;
if (notify) {
Assert(inChatList());
notifyUnreadStateChange(wasState);
}
});
}
} // namespace Dialogs

View file

@ -799,7 +799,7 @@ void InnerWidget::paintCollapsedRow(
Expects(row->folder != nullptr);
const auto text = row->folder->chatListName();
const auto unread = row->folder->chatListUnreadCount();
const auto unread = row->folder->chatListBadgesState().unreadCounter;
Ui::PaintCollapsedRow(p, row->row, row->folder, text, unread, {
.st = _st,
.width = width(),
@ -3435,8 +3435,7 @@ void InnerWidget::setupShortcuts() {
request->check(Command::ReadChat) && request->handle([=] {
const auto history = _selected ? _selected->history() : nullptr;
if (history) {
if ((history->chatListUnreadCount() > 0)
|| history->chatListUnreadMark()) {
if (history->chatListBadgesState().unread) {
session().data().histories().readInbox(history);
}
return true;
@ -3470,8 +3469,7 @@ RowDescriptor InnerWidget::computeJump(
const auto needSkip = [&] {
return (result.key.folder() != nullptr)
|| (session().supportMode()
&& !result.key.entry()->chatListUnreadCount()
&& !result.key.entry()->chatListUnreadMark());
&& !result.key.entry()->chatListBadgesState().unread);
};
while (needSkip()) {
const auto next = down

View file

@ -8,7 +8,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_main_list.h"
#include "data/data_changes.h"
#include "data/data_session.h"
#include "data/data_chat_filters.h"
#include "main/main_session.h"
#include "history/history_unread_things.h"
#include "history/history.h"
namespace Dialogs {
@ -126,7 +129,11 @@ void MainList::unreadStateChanged(
void MainList::unreadEntryChanged(
const Dialogs::UnreadState &state,
bool added) {
if (state.empty()) {
if (!state.messages
&& !state.chats
&& !state.marks
&& !state.mentions
&& !state.reactions) {
return;
}
const auto updateCloudUnread = _cloudUnreadState.known && state.known;

View file

@ -14,6 +14,10 @@ namespace Main {
class Session;
} // namespace Main
namespace Data {
class Thread;
} // namespace Data
namespace Dialogs {
class MainList final {
@ -35,9 +39,7 @@ public:
void unreadStateChanged(
const UnreadState &wasState,
const UnreadState &nowState);
void unreadEntryChanged(
const Dialogs::UnreadState &state,
bool added);
void unreadEntryChanged(const UnreadState &state, bool added);
void updateCloudUnread(const MTPDdialogFolder &data);
[[nodiscard]] bool cloudUnreadKnown() const;
[[nodiscard]] UnreadState unreadState() const;

View file

@ -47,12 +47,12 @@ namespace {
const auto name = history->peer->name();
return TextWithEntities{
.text = name,
.entities = (history->chatListUnreadCount() > 0)
.entities = (history->chatListBadgesState().unread
? EntitiesInText{
{ EntityType::Semibold, 0, int(name.size()), QString() },
{ EntityType::PlainLink, 0, int(name.size()), QString() },
}
: EntitiesInText{}
: EntitiesInText{}),
};
};
const auto shown = int(peers.size());

View file

@ -99,156 +99,49 @@ void PaintRowDate(
PaintRowTopRight(p, dt, rectForName, context);
}
void PaintNarrowCounter(
int PaintBadges(
QPainter &p,
const PaintContext &context,
bool displayUnreadCounter,
bool displayUnreadMark,
bool displayMentionBadge,
bool displayReactionBadge,
int unreadCount,
bool unreadMuted,
bool mentionOrReactionMuted) {
auto skipBeforeMention = 0;
if (displayUnreadCounter || displayUnreadMark) {
const auto counter = (unreadCount > 0)
? QString::number(unreadCount)
BadgesState badgesState,
int right,
int top,
bool displayPinnedIcon = false,
int pinnedIconTop = 0) {
auto initial = right;
if (badgesState.unread) {
UnreadBadgeStyle st;
st.active = context.active;
st.selected = context.selected;
st.muted = badgesState.unreadMuted;
const auto counter = (badgesState.unreadCounter > 0)
? QString::number(badgesState.unreadCounter)
: QString();
const auto allowDigits = (displayMentionBadge
|| displayReactionBadge)
? 1
: 3;
const auto unreadRight = context.st->padding.left()
+ context.st->photoSize;
const auto unreadTop = context.st->padding.top()
+ context.st->photoSize
- st::dialogsUnreadHeight;
UnreadBadgeStyle st;
st.active = context.active;
st.selected = context.selected;
st.muted = unreadMuted;
const auto badge = PaintUnreadBadge(
p,
counter,
unreadRight,
unreadTop,
st,
allowDigits);
skipBeforeMention += badge.width() + st.padding;
}
if (displayMentionBadge || displayReactionBadge) {
const auto counter = QString();
const auto unreadRight = context.st->padding.left()
+ context.st->photoSize
- skipBeforeMention;
const auto unreadTop = context.st->padding.top()
+ context.st->photoSize
- st::dialogsUnreadHeight;
UnreadBadgeStyle st;
st.sizeId = displayMentionBadge
? UnreadBadgeSize::Dialogs
: UnreadBadgeSize::ReactionInDialogs;
st.active = context.active;
st.selected = context.selected;
st.muted = mentionOrReactionMuted;
st.padding = 0;
st.textTop = 0;
const auto badge = PaintUnreadBadge(
p,
counter,
unreadRight,
unreadTop,
st);
(displayMentionBadge
? (st.active
? st::dialogsUnreadMentionActive
: st.selected
? st::dialogsUnreadMentionOver
: st::dialogsUnreadMention)
: (st.active
? st::dialogsUnreadReactionActive
: st.selected
? st::dialogsUnreadReactionOver
: st::dialogsUnreadReaction)).paintInCenter(p, badge);
}
}
int PaintWideCounter(
QPainter &p,
const PaintContext &context,
int texttop,
int availableWidth,
bool displayUnreadCounter,
bool displayUnreadMark,
bool displayMentionBadge,
bool displayReactionBadge,
bool displayPinnedIcon,
int unreadCount,
bool unreadMuted,
bool mentionOrReactionMuted) {
const auto initial = availableWidth;
if (displayUnreadCounter || displayUnreadMark) {
const auto counter = (unreadCount > 0)
? QString::number(unreadCount)
: QString();
const auto unreadRight = context.width - context.st->padding.right();
const auto unreadTop = texttop
+ st::dialogsTextFont->ascent
- st::dialogsUnreadFont->ascent
- (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2;
UnreadBadgeStyle st;
st.active = context.active;
st.selected = context.selected;
st.muted = unreadMuted;
const auto badge = PaintUnreadBadge(
p,
counter,
unreadRight,
unreadTop,
st);
availableWidth -= badge.width() + st.padding;
const auto badge = PaintUnreadBadge(p, counter, right, top, st);
right -= badge.width() + st.padding;
} else if (displayPinnedIcon) {
const auto &icon = context.active
? st::dialogsPinnedIconActive
: context.selected
? st::dialogsPinnedIconOver
: st::dialogsPinnedIcon;
icon.paint(
p,
context.width - context.st->padding.right() - icon.width(),
texttop,
context.width);
availableWidth -= icon.width() + st::dialogsUnreadPadding;
icon.paint(p, right - icon.width(), pinnedIconTop, context.width);
right -= icon.width() + st::dialogsUnreadPadding;
}
if (displayMentionBadge || displayReactionBadge) {
const auto counter = QString();
const auto unreadRight = context.width
- context.st->padding.right()
- (initial - availableWidth);
const auto unreadTop = texttop
+ st::dialogsTextFont->ascent
- st::dialogsUnreadFont->ascent
- (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2;
if (badgesState.mention || badgesState.reaction) {
UnreadBadgeStyle st;
st.sizeId = displayMentionBadge
st.sizeId = badgesState.mention
? UnreadBadgeSize::Dialogs
: UnreadBadgeSize::ReactionInDialogs;
st.active = context.active;
st.selected = context.selected;
st.muted = mentionOrReactionMuted;
st.muted = badgesState.mention
? badgesState.mentionMuted
: badgesState.reactionMuted;
st.padding = 0;
st.textTop = 0;
const auto badge = PaintUnreadBadge(
p,
counter,
unreadRight,
unreadTop,
st);
(displayMentionBadge
const auto counter = QString();
const auto badge = PaintUnreadBadge(p, counter, right, top, st);
(badgesState.mention
? (st.active
? st::dialogsUnreadMentionActive
: st.selected
@ -259,11 +152,46 @@ int PaintWideCounter(
: st.selected
? st::dialogsUnreadReactionOver
: st::dialogsUnreadReaction)).paintInCenter(p, badge);
availableWidth -= badge.width()
+ st.padding
+ st::dialogsUnreadPadding;
right -= badge.width() + st.padding + st::dialogsUnreadPadding;
}
return availableWidth;
return (initial - right);
}
void PaintNarrowCounter(
QPainter &p,
const PaintContext &context,
BadgesState badgesState) {
const auto top = context.st->padding.top()
+ context.st->photoSize
- st::dialogsUnreadHeight;
PaintBadges(
p,
context,
badgesState,
context.st->padding.left() + context.st->photoSize,
top);
}
int PaintWideCounter(
QPainter &p,
const PaintContext &context,
BadgesState badgesState,
int texttop,
int availableWidth,
bool displayPinnedIcon) {
const auto top = texttop
+ st::dialogsTextFont->ascent
- st::dialogsUnreadFont->ascent
- (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2;
const auto used = PaintBadges(
p,
context,
badgesState,
context.width - context.st->padding.right(),
top,
displayPinnedIcon,
texttop);
return availableWidth - used;
}
void PaintListEntryText(
@ -309,7 +237,7 @@ enum class Flag {
};
inline constexpr bool is_flag_type(Flag) { return true; }
template <typename PaintItemCallback, typename PaintCounterCallback>
template <typename PaintItemCallback>
void PaintRow(
Painter &p,
not_null<const BasicRow*> row,
@ -325,9 +253,9 @@ void PaintRow(
const Data::Draft *draft,
QDateTime date,
const PaintContext &context,
BadgesState badgesState,
base::flags<Flag> flags,
PaintItemCallback &&paintItemCallback,
PaintCounterCallback &&paintCounterCallback) {
PaintItemCallback &&paintItemCallback) {
const auto supportMode = entry->session().supportMode();
if (supportMode) {
draft = nullptr;
@ -383,7 +311,7 @@ void PaintRow(
auto nameleft = context.st->nameLeft;
if (context.width <= nameleft) {
if (!draft && item && !item->isEmpty()) {
paintCounterCallback();
PaintNarrowCounter(p, context, badgesState);
}
return;
}
@ -891,12 +819,10 @@ void RowPainter::Paint(
const auto history = row->history();
const auto thread = row->thread();
const auto peer = history ? history->peer.get() : nullptr;
const auto unreadCount = entry->chatListUnreadCount();
const auto unreadMark = entry->chatListUnreadMark();
const auto unreadMuted = entry->chatListMutedBadge();
const auto badgesState = entry->chatListBadgesState();
const auto item = entry->chatListMessage();
const auto cloudDraft = [&]() -> const Data::Draft*{
if (thread && (!item || (!unreadCount && !unreadMark))) {
if (thread && (!item || !badgesState.unread)) {
// Draw item, if there are unread messages.
const auto draft = thread->owningHistory()->cloudDraft(
thread->topicRootId());
@ -919,30 +845,7 @@ void RowPainter::Paint(
? base::unixtime::parse(cloudDraft->date)
: QDateTime();
}();
const auto displayMentionBadge = thread
&& thread->unreadMentions().has();
const auto displayReactionBadge = !displayMentionBadge
&& thread
&& thread->unreadReactions().has();
const auto mentionOrReactionMuted = (entry->folder() != nullptr)
|| (!displayMentionBadge && unreadMuted);
const auto displayUnreadCounter = [&] {
if (displayMentionBadge
&& unreadCount == 1
&& item
&& item->isUnreadMention()) {
return false;
}
return (unreadCount > 0);
}();
const auto displayUnreadMark = !displayUnreadCounter
&& !displayMentionBadge
&& history
&& unreadMark;
const auto displayPinnedIcon = !displayUnreadCounter
&& !displayMentionBadge
&& !displayReactionBadge
&& !displayUnreadMark
const auto displayPinnedIcon = badgesState.empty()
&& entry->isPinnedDialog(context.filter)
&& (context.filter || !entry->fixedOnTopIndex());
@ -951,8 +854,7 @@ void RowPainter::Paint(
? history->peer->migrateTo()
: history->peer.get())
: nullptr;
const auto allowUserOnline = !context.narrow
|| (!displayUnreadCounter && !displayUnreadMark);
const auto allowUserOnline = !context.narrow || badgesState.empty();
const auto flags = (allowUserOnline ? Flag::AllowUserOnline : Flag(0))
| (peer && peer->isSelf() ? Flag::SavedMessages : Flag(0))
| (peer && peer->isRepliesChat() ? Flag::RepliesMessages : Flag(0));
@ -961,16 +863,10 @@ void RowPainter::Paint(
const auto availableWidth = PaintWideCounter(
p,
context,
badgesState,
texttop,
namewidth,
displayUnreadCounter,
displayUnreadMark,
displayMentionBadge,
displayReactionBadge,
displayPinnedIcon,
unreadCount,
unreadMuted,
mentionOrReactionMuted);
displayPinnedIcon);
const auto &color = context.active
? st::dialogsTextFgServiceActive
: context.selected
@ -1008,18 +904,6 @@ void RowPainter::Paint(
view->paint(p, rect, context);
}
};
const auto paintCounterCallback = [&] {
PaintNarrowCounter(
p,
context,
displayUnreadCounter,
displayUnreadMark,
displayMentionBadge,
displayReactionBadge,
unreadCount,
unreadMuted,
mentionOrReactionMuted);
};
PaintRow(
p,
row,
@ -1027,7 +911,7 @@ void RowPainter::Paint(
row->key(),
videoUserpic,
from,
entry->chatListBadge(),
entry->chatListPeerBadge(),
[=] { history->updateChatListEntry(); },
entry->chatListNameText(),
nullptr,
@ -1035,9 +919,9 @@ void RowPainter::Paint(
cloudDraft,
displayDate,
context,
badgesState,
flags,
paintItemCallback,
paintCounterCallback);
paintItemCallback);
}
void RowPainter::Paint(
@ -1078,22 +962,9 @@ void RowPainter::Paint(
return {};
}();
const auto unreadCount = context.displayUnreadInfo
? history->chatListUnreadCount()
: 0;
const auto unreadMark = context.displayUnreadInfo
&& history->chatListUnreadMark();
const auto unreadMuted = history->chatListMutedBadge();
const auto mentionOrReactionMuted = (history->folder() != nullptr);
const auto displayMentionBadge = context.displayUnreadInfo
&& history->unreadMentions().has();
const auto displayReactionBadge = context.displayUnreadInfo
&& !displayMentionBadge
&& history->unreadReactions().has();
const auto displayUnreadCounter = (unreadCount > 0);
const auto displayUnreadMark = !displayUnreadCounter
&& !displayMentionBadge
&& unreadMark;
const auto badgesState = context.displayUnreadInfo
? history->chatListBadgesState()
: BadgesState();
const auto displayPinnedIcon = false;
const auto paintItemCallback = [&](int nameleft, int namewidth) {
@ -1101,16 +972,10 @@ void RowPainter::Paint(
const auto availableWidth = PaintWideCounter(
p,
context,
badgesState,
texttop,
namewidth,
displayUnreadCounter,
displayUnreadMark,
displayMentionBadge,
displayReactionBadge,
displayPinnedIcon,
unreadCount,
unreadMuted,
mentionOrReactionMuted);
displayPinnedIcon);
const auto itemRect = QRect(
nameleft,
@ -1123,18 +988,6 @@ void RowPainter::Paint(
}
row->itemView().paint(p, itemRect, context);
};
const auto paintCounterCallback = [&] {
PaintNarrowCounter(
p,
context,
displayUnreadCounter,
displayUnreadMark,
displayMentionBadge,
displayReactionBadge,
unreadCount,
unreadMuted,
mentionOrReactionMuted);
};
const auto showSavedMessages = history->peer->isSelf()
&& !row->searchInChat();
const auto showRepliesMessages = history->peer->isRepliesChat()
@ -1156,9 +1009,9 @@ void RowPainter::Paint(
cloudDraft,
ItemDateTime(item),
context,
badgesState,
flags,
paintItemCallback,
paintCounterCallback);
paintItemCallback);
}
QRect RowPainter::SendActionAnimationRect(

View file

@ -1693,11 +1693,6 @@ int History::unreadCount() const {
return _unreadCount ? *_unreadCount : 0;
}
int History::unreadCountForBadge() const {
const auto result = unreadCount();
return (!result && unreadMark()) ? 1 : result;
}
bool History::unreadCountKnown() const {
return _unreadCount.has_value();
}
@ -1708,14 +1703,7 @@ void History::setUnreadCount(int newUnreadCount) {
if (_unreadCount == newUnreadCount) {
return;
}
const auto wasForBadge = (unreadCountForBadge() > 0);
const auto refresher = gsl::finally([&] {
if (wasForBadge != (unreadCountForBadge() > 0)) {
owner().chatsFilters().refreshHistory(this);
}
session().changes().historyUpdated(this, UpdateFlag::UnreadView);
});
const auto notifier = unreadStateChangeNotifier(true);
const auto notifier = unreadStateChangeNotifier(!peer->isForum());
_unreadCount = newUnreadCount;
const auto lastOutgoing = [&] {
@ -1753,26 +1741,22 @@ void History::setUnreadMark(bool unread) {
if (unreadMark() == unread) {
return;
}
const auto noUnreadMessages = !unreadCount();
const auto refresher = gsl::finally([&] {
if (inChatList() && noUnreadMessages) {
owner().chatsFilters().refreshHistory(this);
updateChatListEntry();
}
session().changes().historyUpdated(this, UpdateFlag::UnreadView);
});
const auto notifier = unreadStateChangeNotifier(noUnreadMessages);
Thread::setUnreadMark(unread);
const auto notifier = unreadStateChangeNotifier(
!unreadCount() && !peer->isForum());
Thread::setUnreadMarkFlag(unread);
}
void History::setFakeUnreadWhileOpened(bool enabled) {
if (_fakeUnreadWhileOpened == enabled
|| (enabled
&& (!inChatList()
|| (!unreadCount()
&& !unreadMark()
&& !unreadMentions().has())))) {
if (_fakeUnreadWhileOpened == enabled) {
return;
} else if (enabled) {
if (!inChatList()) {
return;
}
const auto state = chatListBadgesState();
if (!state.unread && !state.mention) {
return;
}
}
_fakeUnreadWhileOpened = enabled;
owner().chatsFilters().refreshHistory(this);
@ -1785,19 +1769,18 @@ void History::setFakeUnreadWhileOpened(bool enabled) {
void History::setMuted(bool muted) {
if (this->muted() == muted) {
return;
} else {
const auto state = peer->isForum()
? Dialogs::BadgesState()
: computeBadgesState();
const auto notify = (state.unread || state.reaction);
const auto notifier = unreadStateChangeNotifier(notify);
Thread::setMuted(muted);
}
const auto refresher = gsl::finally([&] {
if (inChatList()) {
owner().chatsFilters().refreshHistory(this);
updateChatListEntry();
}
session().changes().peerUpdated(
peer,
Data::PeerUpdate::Flag::Notifications);
});
const auto notify = (unreadCountForBadge() > 0);
const auto notifier = unreadStateChangeNotifier(notify);
Thread::setMuted(muted);
session().changes().peerUpdated(
peer,
Data::PeerUpdate::Flag::Notifications);
owner().chatsFilters().refreshHistory(this);
if (const auto forum = peer->forum()) {
owner().notifySettings().forumParentMuteUpdated(forum);
}
@ -1899,6 +1882,33 @@ int History::chatListNameVersion() const {
return peer->nameVersion();
}
void History::hasUnreadMentionChanged(bool has) {
if (peer->isForum()) {
return;
}
auto was = chatListUnreadState();
if (has) {
was.mentions = 0;
} else {
was.mentions = 1;
}
notifyUnreadStateChange(was);
}
void History::hasUnreadReactionChanged(bool has) {
if (peer->isForum()) {
return;
}
auto was = chatListUnreadState();
if (has) {
was.reactions = was.reactionsMuted = 0;
} else {
was.reactions = 1;
was.reactionsMuted = muted() ? was.reactions : 0;
}
notifyUnreadStateChange(was);
}
void History::applyPinnedUpdate(const MTPDupdateDialogPinned &data) {
const auto folderId = data.vfolder_id().value_or_empty();
if (!folderKnown()) {
@ -1998,10 +2008,7 @@ void History::getNextScrollTopItem(HistoryBlock *block, int32 i) {
}
void History::addUnreadBar() {
if (_unreadBarView || !_firstUnreadView || !unreadCount()) {
return;
}
if (const auto count = chatListUnreadCount()) {
if (!_unreadBarView && _firstUnreadView && unreadCount()) {
_unreadBarView = _firstUnreadView;
_unreadBarView->createUnreadBar(tr::lng_unread_bar_some());
}
@ -2066,41 +2073,6 @@ History *History::migrateSibling() const {
return owner().historyLoaded(addFromId);
}
int History::chatListUnreadCount() const {
if (peer->isForum()) {
const auto state = chatListUnreadState();
return state.marks
+ (Core::App().settings().countUnreadMessages()
? state.messages
: state.chats);
}
const auto result = unreadCount();
if (const auto migrated = migrateSibling()) {
return result + migrated->unreadCount();
}
return result;
}
bool History::chatListUnreadMark() const {
if (unreadMark()) {
return true;
} else if (const auto migrated = migrateSibling()) {
return migrated->unreadMark();
}
return false;
}
bool History::chatListMutedBadge() const {
if (const auto forum = peer->forum()) {
const auto state = forum->topicsList()->unreadState();
return (state.marksMuted >= state.marks)
&& (Core::App().settings().countUnreadMessages()
? (state.messagesMuted >= state.messages)
: (state.chatsMuted >= state.chats));
}
return muted();
}
Dialogs::UnreadState History::chatListUnreadState() const {
if (const auto forum = peer->forum()) {
return forum->topicsList()->unreadState();
@ -2108,17 +2080,47 @@ Dialogs::UnreadState History::chatListUnreadState() const {
return computeUnreadState();
}
Dialogs::BadgesState History::chatListBadgesState() const {
if (const auto forum = peer->forum()) {
return adjustBadgesStateByFolder(
Dialogs::BadgesForUnread(
forum->topicsList()->unreadState(),
Dialogs::CountInBadge::Chats,
Dialogs::IncludeInBadge::UnmutedOrAll));
}
return computeBadgesState();
}
Dialogs::BadgesState History::computeBadgesState() const {
return adjustBadgesStateByFolder(
Dialogs::BadgesForUnread(
computeUnreadState(),
Dialogs::CountInBadge::Messages,
Dialogs::IncludeInBadge::All));
}
Dialogs::BadgesState History::adjustBadgesStateByFolder(
Dialogs::BadgesState state) const {
if (folder()) {
state.mentionMuted = state.reactionMuted = state.unreadMuted = true;
}
return state;
}
Dialogs::UnreadState History::computeUnreadState() const {
auto result = Dialogs::UnreadState();
const auto count = _unreadCount.value_or(0);
const auto mark = !count && unreadMark();
const auto muted = this->muted();
result.messages = count;
result.messagesMuted = muted ? count : 0;
result.chats = count ? 1 : 0;
result.chatsMuted = (count && muted) ? 1 : 0;
result.marks = mark ? 1 : 0;
result.marksMuted = (mark && muted) ? 1 : 0;
result.mentions = unreadMentions().has() ? 1 : 0;
result.reactions = unreadReactions().has() ? 1 : 0;
result.messagesMuted = muted ? result.messages : 0;
result.chatsMuted = muted ? result.chats : 0;
result.marksMuted = muted ? result.marks : 0;
result.reactionsMuted = muted ? result.reactions : 0;
result.known = _unreadCount.has_value();
return result;
}
@ -2908,7 +2910,6 @@ void History::forumChanged(Data::Forum *old) {
return (_flags & Flag::IsForum) && inChatList();
}) | rpl::start_with_next([=](const Dialogs::UnreadState &old) {
notifyUnreadStateChange(old);
updateChatListEntryPostponed();
}, forum->lifetime());
} else {
_flags &= ~Flag::IsForum;

View file

@ -248,10 +248,9 @@ public:
[[nodiscard]] bool unreadCountRefreshNeeded(MsgId readTillId) const;
void setUnreadCount(int newUnreadCount);
void setUnreadMark(bool unread) override;
void setUnreadMark(bool unread);
void setFakeUnreadWhileOpened(bool enabled);
[[nodiscard]] bool fakeUnreadWhileOpened() const;
[[nodiscard]] int unreadCountForBadge() const; // unreadCount || unreadMark ? 1 : 0.
void setMuted(bool muted) override;
void addUnreadBar();
void destroyUnreadBar();
@ -380,10 +379,8 @@ public:
int fixedOnTopIndex() const override;
void updateChatListExistence() override;
bool shouldBeInChatList() const override;
int chatListUnreadCount() const override;
bool chatListUnreadMark() const override;
bool chatListMutedBadge() const override;
Dialogs::UnreadState chatListUnreadState() const override;
Dialogs::BadgesState chatListBadgesState() const override;
HistoryItem *chatListMessage() const override;
bool chatListMessageKnown() const override;
void requestChatListMessage() override;
@ -574,11 +571,17 @@ private:
HistoryService *insertJoinedMessage();
void insertMessageToBlocks(not_null<HistoryItem*> item);
[[nodiscard]] Dialogs::BadgesState computeBadgesState() const;
[[nodiscard]] Dialogs::BadgesState adjustBadgesStateByFolder(
Dialogs::BadgesState state) const;
[[nodiscard]] Dialogs::UnreadState computeUnreadState() const;
void setFolderPointer(Data::Folder *folder);
int chatListNameVersion() const override;
void hasUnreadMentionChanged(bool has) override;
void hasUnreadReactionChanged(bool has) override;
const std::unique_ptr<HistoryMainElementDelegateMixin> _delegateMixin;
Flags _flags = 0;

View file

@ -67,13 +67,12 @@ void Proxy::setCount(int count) {
list.setCount(count);
}
const auto has = (count > 0);
if (has != had) {
if (has != had && _thread->inChatList()) {
if (_type == Type::Mentions) {
if (const auto history = _thread->asHistory()) {
_thread->owner().chatsFilters().refreshHistory(history);
}
_thread->hasUnreadMentionChanged(has);
} else if (_type == Type::Reactions) {
_thread->hasUnreadReactionChanged(has);
}
_thread->updateChatListEntry();
}
}

View file

@ -2257,21 +2257,17 @@ void HistoryWidget::showHistory(
if (!_history->folderKnown()) {
session().data().histories().requestDialogEntry(_history);
}
if (_history->chatListUnreadMark()) {
_history->owner().histories().changeDialogUnreadMark(
// Must be done before unreadCountUpdated(), or we auto-close.
if (_history->unreadMark()) {
session().data().histories().changeDialogUnreadMark(
_history,
false);
if (_migrated) {
_migrated->owner().histories().changeDialogUnreadMark(
_migrated,
false);
}
// Must be done before unreadCountUpdated(), or we auto-close.
_history->setUnreadMark(false);
if (_migrated) {
_migrated->setUnreadMark(false);
}
}
if (_migrated && _migrated->unreadMark()) {
session().data().histories().changeDialogUnreadMark(
_migrated,
false);
}
unreadCountUpdated(); // set _historyDown badge.
showAboutTopPromotion();
@ -2913,7 +2909,7 @@ void HistoryWidget::maybeMarkReactionsRead(not_null<HistoryItem*> item) {
}
void HistoryWidget::unreadCountUpdated() {
if (_history->chatListUnreadMark()) {
if (_history->unreadMark() || (_migrated && _migrated->unreadMark())) {
crl::on_main(this, [=, history = _history] {
if (history == _history) {
closeCurrent();
@ -2922,7 +2918,7 @@ void HistoryWidget::unreadCountUpdated() {
});
} else {
_cornerButtons.updateJumpDownVisibility(
_history->chatListUnreadCount());
_history->chatListBadgesState().unreadCounter);
}
}

View file

@ -91,16 +91,16 @@ QImage ArchiveUserpic(not_null<Data::Folder*> folder) {
QImage UnreadBadge(not_null<PeerData*> peer) {
const auto history = peer->owner().history(peer->id);
const auto count = history->unreadCountForBadge();
if (!count) {
const auto state = history->chatListBadgesState();
if (!state.unread) {
return QImage();
}
const auto unread = history->unreadMark()
? QString()
: QString::number(count);
const auto counter = (state.unreadCounter > 0)
? QString::number(state.unreadCounter)
: QString();
Dialogs::Ui::UnreadBadgeStyle unreadSt;
unreadSt.sizeId = Dialogs::Ui::UnreadBadgeSize::TouchBar;
unreadSt.muted = history->muted();
unreadSt.muted = state.unreadMuted;
// Use constant values to draw badge regardless of cConfigScale().
unreadSt.size = kUnreadBadgeSize * cRetinaFactor();
unreadSt.padding = 4 * cRetinaFactor();

View file

@ -579,10 +579,10 @@ void MainMenu::setupArchive() {
return folder->owner().chatsList(folder)->unreadStateChanges();
}) | rpl::flatten_latest() | rpl::to_empty) | rpl::map([=] {
const auto loaded = folder();
return Badge::UnreadBadge{
loaded ? loaded->chatListUnreadCount() : 0,
true,
};
const auto state = loaded
? loaded->chatListBadgesState()
: Dialogs::BadgesState();
return Badge::UnreadBadge{ state.unreadCounter, true };
}));
controller->session().data().chatsListChanges(

View file

@ -112,8 +112,7 @@ void SetActionText(not_null<QAction*> action, rpl::producer<QString> &&text) {
}
[[nodiscard]] bool IsUnreadThread(not_null<Data::Thread*> thread) {
return (thread->chatListUnreadCount() > 0)
|| (thread->chatListUnreadMark());
return thread->chatListBadgesState().unread;
}
void MarkAsReadThread(not_null<Data::Thread*> thread) {
@ -1739,7 +1738,7 @@ void MenuAddMarkAsReadChatListAction(
const PeerMenuCallback &addAction) {
// There is no async to make weak from controller.
const auto unreadState = list()->unreadState();
if (unreadState.empty()) {
if (!unreadState.messages && !unreadState.marks && !unreadState.chats) {
return;
}