Improve archive chat list entry layout.

This commit is contained in:
John Preston 2019-04-23 16:29:23 +04:00
parent 2d1dcb36cb
commit 9ff02707bf
13 changed files with 291 additions and 50 deletions

View file

@ -304,7 +304,7 @@ historyPeer8NameFgSelected: historyPeer8NameFg; // orange group member name in a
historyPeer8UserpicBg: #faa774; // orange userpic background
historyPeerUserpicFg: windowFgActive; // default userpic initials
historyPeerSavedMessagesBg: historyPeer4UserpicBg; // saved messages userpic background
historyPeerArchiveUserpicBg: historyPeer2UserpicBg; // archive folder userpic background
historyPeerArchiveUserpicBg: dialogsUnreadBgMuted; // archive folder userpic background
// Some values are marked as (adjusted), it means they're adjusted by
// hue and saturation of the average background color if user chooses

View file

@ -1195,11 +1195,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_saved_short" = "Save";
"lng_saved_forward_here" = "Forward messages here for quick access";
"lng_archived_chats" = "Archived chats";
"lng_archived_name" = "Archived chats";
"lng_archived_add" = "Archive";
"lng_archived_remove" = "Unarchive";
"lng_chat_archived" = "Chat archived.\nMuted chats will stay archived after new messages arrive.";
"lng_chat_unarchived" = "Chat restored from your archive.";
"lng_archived_added" = "Chat archived.\nMuted chats will stay archived after new messages arrive.";
"lng_archived_removed" = "Chat restored from your archive.";
"lng_archived_chats#one" = "{count} chat";
"lng_archived_chats#other" = "{count} chats";
"lng_archived_unread_two" = "{chat}, {second_chat}";
"lng_archived_unread#one" = "{chat}, {second_chat} and {count} more unread chat";
"lng_archived_unread#other" = "{chat}, {second_chat} and {count} more unread chats";
"lng_dialogs_text_with_from" = "{from_part} {message}";
"lng_dialogs_text_from_wrapped" = "{from}:";

View file

@ -744,26 +744,33 @@ void ApiWrap::requestMoreDialogs(Data::Folder *folder) {
MTP_int(hash)
)).done([=](const MTPmessages_Dialogs &result) {
const auto state = dialogsLoadState(folder);
const auto count = result.match([](
const MTPDmessages_dialogsNotModified &) {
LOG(("API Error: not-modified received for requested dialogs."));
return 0;
}, [&](const MTPDmessages_dialogs &data) {
if (state) {
state->listReceived = true;
dialogsLoadFinish(folder); // may kill 'state'.
}
return int(data.vdialogs.v.size());
}, [&](const MTPDmessages_dialogsSlice &data) {
updateDialogsOffset(
folder,
data.vdialogs.v,
data.vmessages.v);
return data.vcount.v;
});
result.match([](const MTPDmessages_dialogsNotModified & data) {
LOG(("API Error: not-modified received for requested dialogs."));
}, [&](const auto &data) {
if constexpr (data.Is<MTPDmessages_dialogs>()) {
if (state) {
state->listReceived = true;
dialogsLoadFinish(folder); // may kill 'state'.
}
} else {
updateDialogsOffset(
folder,
data.vdialogs.v,
data.vmessages.v);
}
_session->data().processUsers(data.vusers);
_session->data().processChats(data.vchats);
_session->data().applyDialogs(
folder,
data.vmessages.v,
data.vdialogs.v);
data.vdialogs.v,
count);
});
if (!folder) {

View file

@ -25,6 +25,7 @@ namespace Data {
namespace {
constexpr auto kLoadedChatsMinCount = 20;
constexpr auto kShowChatNamesCount = 2;
rpl::producer<int> PinnedDialogsInFolderMaxValue() {
return rpl::single(
@ -52,7 +53,7 @@ Folder::Folder(not_null<Data::Session*> owner, FolderId id)
: Entry(owner, this)
, _id(id)
, _chatsList(PinnedDialogsInFolderMaxValue())
, _name(lang(lng_archived_chats)) {
, _name(lang(lng_archived_name)) {
indexNameParts();
}
@ -89,6 +90,9 @@ void Folder::indexNameParts() {
void Folder::registerOne(not_null<History*> history) {
if (_chatsList.indexed()->size() == 1) {
updateChatListSortPosition();
} else {
++_chatListViewVersion;
updateChatListEntry();
}
applyChatListMessage(history->chatListMessage());
}
@ -96,6 +100,9 @@ void Folder::registerOne(not_null<History*> history) {
void Folder::unregisterOne(not_null<History*> history) {
if (_chatsList.empty()) {
updateChatListExistence();
} else {
++_chatListViewVersion;
updateChatListEntry();
}
if (_chatListMessage && _chatListMessage->history() == history) {
computeChatListMessage();
@ -106,6 +113,12 @@ void Folder::oneListMessageChanged(HistoryItem *from, HistoryItem *to) {
if (!applyChatListMessage(to) && _chatListMessage == from) {
computeChatListMessage();
}
if (from || to) {
const auto history = from ? from->history() : to->history();
if (!history->chatListUnreadState().empty()) {
reorderUnreadHistories();
}
}
}
bool Folder::applyChatListMessage(HistoryItem *item) {
@ -141,6 +154,55 @@ void Folder::computeChatListMessage() {
updateChatListEntry();
}
void Folder::addUnreadHistory(not_null<History*> history) {
const auto i = ranges::find(_unreadHistories, history);
if (i == end(_unreadHistories)) {
_unreadHistories.push_back(history);
reorderUnreadHistories();
}
}
void Folder::removeUnreadHistory(not_null<History*> history) {
const auto i = ranges::find(_unreadHistories, history);
if (i != end(_unreadHistories)) {
_unreadHistories.erase(i);
reorderUnreadHistories();
}
}
void Folder::reorderUnreadHistories() {
// We want first kShowChatNamesCount histories, by last message date.
const auto predicate = [](not_null<History*> a, not_null<History*> b) {
const auto aItem = a->chatListMessage();
const auto bItem = b->chatListMessage();
const auto aDate = aItem ? aItem->date() : TimeId(0);
const auto bDate = bItem ? bItem->date() : TimeId(0);
return aDate > bDate;
};
if (size(_unreadHistories) <= kShowChatNamesCount) {
ranges::sort(_unreadHistories, predicate);
if (!ranges::equal(_unreadHistories, _unreadHistoriesLast)) {
_unreadHistoriesLast = _unreadHistories;
}
} else {
const auto till = begin(_unreadHistories) + kShowChatNamesCount - 1;
ranges::nth_element(_unreadHistories, till, predicate);
if constexpr (kShowChatNamesCount > 2) {
ranges::sort(begin(_unreadHistories), till, predicate);
}
auto &&head = ranges::view::all(
_unreadHistories
) | ranges::view::take_exactly(
kShowChatNamesCount
);
if (!ranges::equal(head, _unreadHistoriesLast)) {
_unreadHistoriesLast = head | ranges::to_vector;
}
}
++_chatListViewVersion;
updateChatListEntry();
}
not_null<Dialogs::MainList*> Folder::chatsList() {
return &_chatsList;
}
@ -194,6 +256,29 @@ void Folder::setChatsListLoaded(bool loaded) {
_chatsList.setLoaded(loaded);
}
void Folder::setCloudChatsListSize(int size) {
_cloudChatsListSize = size;
updateChatListEntry();
}
int Folder::chatsListSize() const {
return std::max(
_chatsList.indexed()->size(),
_chatsList.loaded() ? 0 : _cloudChatsListSize);
}
int Folder::unreadHistoriesCount() const {
return _unreadHistories.size();
}
const std::vector<not_null<History*>> &Folder::lastUnreadHistories() const {
return _unreadHistoriesLast;
}
uint32 Folder::chatListViewVersion() const {
return _chatListViewVersion;
}
void Folder::requestChatListMessage() {
if (!chatListMessageKnown()) {
session().api().requestDialogEntry(this);
@ -252,8 +337,17 @@ void Folder::applyPinnedUpdate(const MTPDupdateDialogPinned &data) {
}
void Folder::unreadStateChanged(
const Dialogs::Key &key,
const Dialogs::UnreadState &wasState,
const Dialogs::UnreadState &nowState) {
if (const auto history = key.history()) {
if (!wasState.empty() && nowState.empty()) {
removeUnreadHistory(history);
} else if (wasState.empty() && !nowState.empty()) {
addUnreadHistory(history);
}
}
const auto updateCloudUnread = _cloudUnread.messagesCount.has_value()
&& wasState.messagesCount.has_value();
const auto notify = _chatsList.loaded() || updateCloudUnread;
@ -276,8 +370,19 @@ void Folder::unreadStateChanged(
}
void Folder::unreadEntryChanged(
const Dialogs::Key &key,
const Dialogs::UnreadState &state,
bool added) {
if (const auto history = key.history()) {
if (!state.empty()) {
if (added) {
addUnreadHistory(history);
} else {
removeUnreadHistory(history);
}
}
}
const auto updateCloudUnread = _cloudUnread.messagesCount.has_value()
&& state.messagesCount.has_value();
const auto notify = _chatsList.loaded() || updateCloudUnread;

View file

@ -44,9 +44,13 @@ public:
void updateCloudUnread(const MTPDdialogFolder &data);
void unreadStateChanged(
const Dialogs::Key &key,
const Dialogs::UnreadState &wasState,
const Dialogs::UnreadState &nowState);
void unreadEntryChanged(const Dialogs::UnreadState &state, bool added);
void unreadEntryChanged(
const Dialogs::Key &key,
const Dialogs::UnreadState &state,
bool added);
TimeId adjustedChatListTimeId() const override;
@ -73,12 +77,22 @@ public:
bool chatsListLoaded() const;
void setChatsListLoaded(bool loaded = true);
void setCloudChatsListSize(int size);
int chatsListSize() const;
int unreadHistoriesCount() const;
const std::vector<not_null<History*>> &lastUnreadHistories() const;
uint32 chatListViewVersion() const;
private:
void indexNameParts();
bool applyChatListMessage(HistoryItem *item);
void computeChatListMessage();
void addUnreadHistory(not_null<History*> history);
void removeUnreadHistory(not_null<History*> history);
void reorderUnreadHistories();
FolderId _id = 0;
Dialogs::MainList _chatsList;
@ -87,7 +101,11 @@ private:
base::flat_set<QChar> _nameFirstLetters;
Dialogs::UnreadState _cloudUnread;
int _cloudChatsListSize = 0;
std::vector<not_null<History*>> _unreadHistories;
std::vector<not_null<History*>> _unreadHistoriesLast;
HistoryItem *_chatListMessage = nullptr;
uint32 _chatListViewVersion = 0;
//rpl::variable<MessagePosition> _unreadPosition;
};

View file

@ -1375,13 +1375,17 @@ void Session::applyPinnedChats(
void Session::applyDialogs(
Data::Folder *requestFolder,
const QVector<MTPMessage> &messages,
const QVector<MTPDialog> &dialogs) {
const QVector<MTPDialog> &dialogs,
std::optional<int> count) {
App::feedMsgs(messages, NewMessageLast);
for (const auto &dialog : dialogs) {
dialog.match([&](const auto &data) {
applyDialog(requestFolder, data);
});
}
if (requestFolder && count) {
requestFolder->setCloudChatsListSize(*count);
}
}
void Session::applyDialog(
@ -1663,7 +1667,7 @@ void Session::unreadStateChanged(
const auto nowState = key.entry()->chatListUnreadState();
if (const auto folder = key.entry()->folder()) {
folder->unreadStateChanged(wasState, nowState);
folder->unreadStateChanged(key, wasState, nowState);
} else {
_chatsList.unreadStateChanged(wasState, nowState);
}
@ -1674,10 +1678,12 @@ void Session::unreadEntryChanged(const Dialogs::Key &key, bool added) {
Expects(key.entry()->folderKnown());
const auto state = key.entry()->chatListUnreadState();
if (const auto folder = key.entry()->folder()) {
folder->unreadEntryChanged(state, added);
} else {
_chatsList.unreadEntryChanged(state, added);
if (!state.empty()) {
if (const auto folder = key.entry()->folder()) {
folder->unreadEntryChanged(key, state, added);
} else {
_chatsList.unreadEntryChanged(state, added);
}
}
}

View file

@ -296,7 +296,8 @@ public:
void applyDialogs(
Data::Folder *requestFolder,
const QVector<MTPMessage> &messages,
const QVector<MTPDialog> &dialogs);
const QVector<MTPDialog> &dialogs,
std::optional<int> count = std::nullopt);
void addSavedPeersAfter(const QDateTime &date);
void addAllSavedPeers();

View file

@ -47,6 +47,10 @@ struct UnreadState {
int chatsCountMuted = 0;
bool mark = false;
bool markMuted = false;
bool empty() const {
return !messagesCount.value_or(0) && !chatsCount && !mark;
}
};
class Entry {

View file

@ -34,7 +34,7 @@ bool ShowUserBotIcon(not_null<UserData*> user) {
return user->isBot() && !user->isSupport();
}
void paintRowTopRight(Painter &p, const QString &text, QRect &rectForName, bool active, bool selected) {
void PaintRowTopRight(Painter &p, const QString &text, QRect &rectForName, bool active, bool selected) {
const auto width = st::dialogsDateFont->width(text);
rectForName.setWidth(rectForName.width() - width - st::dialogsDateSkip);
p.setFont(st::dialogsDateFont);
@ -42,7 +42,7 @@ void paintRowTopRight(Painter &p, const QString &text, QRect &rectForName, bool
p.drawText(rectForName.left() + rectForName.width() + st::dialogsDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, text);
}
void paintRowDate(Painter &p, QDateTime date, QRect &rectForName, bool active, bool selected) {
void PaintRowDate(Painter &p, QDateTime date, QRect &rectForName, bool active, bool selected) {
const auto now = QDateTime::currentDateTime();
const auto &lastTime = date;
const auto nowDate = now.date();
@ -60,7 +60,7 @@ void paintRowDate(Painter &p, QDateTime date, QRect &rectForName, bool active, b
return lastDate.toString(qsl("d.MM.yy"));
}
}();
paintRowTopRight(p, dt, rectForName, active, selected);
PaintRowTopRight(p, dt, rectForName, active, selected);
}
void PaintNarrowCounter(
@ -159,6 +159,38 @@ int PaintWideCounter(
return availableWidth;
}
void PaintListEntryText(
Painter &p,
QRect rect,
bool active,
bool selected,
not_null<const Row*> row) {
if (rect.isEmpty()) {
return;
}
row->validateListEntryCache();
const auto &palette = active
? st::dialogsTextPaletteActive
: selected
? st::dialogsTextPaletteOver
: st::dialogsTextPalette;
const auto &color = active
? st::dialogsTextFgActive
: selected
? st::dialogsTextFgOver
: st::dialogsTextFg;
p.setTextPalette(palette);
p.setFont(st::dialogsTextFont);
p.setPen(color);
row->listEntryCache().drawElided(
p,
rect.left(),
rect.top(),
rect.width(),
rect.height() / st::dialogsTextFont->height);
p.restoreTextPalette();
}
enum class Flag {
Active = 0x01,
Selected = 0x02,
@ -255,7 +287,7 @@ void paintRow(
&& !(flags & (Flag::SearchResult/* | Flag::FeedSearchResult*/)); // #feed
if (promoted) {
const auto text = lang(lng_proxy_sponsor);
paintRowTopRight(p, text, rectForName, active, selected);
PaintRowTopRight(p, text, rectForName, active, selected);
} else if (from/* && !(flags & Flag::FeedSearchResult)*/) { // #feed
if (const auto chatTypeIcon = ChatTypeIcon(from, active, selected)) {
chatTypeIcon->paint(p, rectForName.topLeft(), fullWidth);
@ -274,7 +306,7 @@ void paintRow(
|| (supportMode
&& Auth().supportHelper().isOccupiedBySomeone(history))) {
if (!promoted) {
paintRowDate(p, date, rectForName, active, selected);
PaintRowDate(p, date, rectForName, active, selected);
}
auto availableWidth = namewidth;
@ -318,7 +350,7 @@ void paintRow(
}
} else if (!item->isEmpty()) {
if (!promoted) {
paintRowDate(p, date, rectForName, active, selected);
PaintRowDate(p, date, rectForName, active, selected);
}
paintItemCallback(nameleft, namewidth);
@ -560,7 +592,7 @@ void RowPainter::paint(
}
return nullptr;
}();
const auto displayDate = [item, cloudDraft] {
const auto displayDate = [&] {
if (item) {
if (cloudDraft) {
return (item->date() > cloudDraft->date)
@ -623,20 +655,22 @@ void RowPainter::paint(
: (selected
? st::dialogsTextFgServiceOver
: st::dialogsTextFgService);
const auto actionWasPainted = history ? history->paintSendAction(
p,
const auto itemRect = QRect(
nameleft,
texttop,
availableWidth,
st::dialogsTextFont->height);
const auto actionWasPainted = history ? history->paintSendAction(
p,
itemRect.x(),
itemRect.y(),
itemRect.width(),
fullWidth,
color,
ms) : false;
if (!actionWasPainted) {
const auto itemRect = QRect(
nameleft,
texttop,
availableWidth,
st::dialogsTextFont->height);
if (const auto folder = row->folder()) {
PaintListEntryText(p, itemRect, active, selected, row);
} else if (!actionWasPainted) {
item->drawInDialog(
p,
itemRect,

View file

@ -7,12 +7,49 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "dialogs/dialogs_row.h"
#include "styles/style_dialogs.h"
#include "ui/effects/ripple_animation.h"
#include "ui/text_options.h"
#include "dialogs/dialogs_entry.h"
#include "data/data_folder.h"
#include "history/history.h"
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "styles/style_dialogs.h"
namespace Dialogs {
namespace {
QString ComposeFolderListEntryText(not_null<Data::Folder*> folder) {
const auto &list = folder->lastUnreadHistories();
if (list.empty()) {
const auto count = folder->chatsListSize();
if (!count) {
return QString();
}
return lng_archived_chats(lt_count, count);
}
const auto count = std::max(
int(list.size()),
folder->unreadHistoriesCount());
if (list.size() == 1) {
return App::peerName(list[0]->peer);
} else if (count == 2) {
return lng_archived_unread_two(
lt_chat,
App::peerName(list[0]->peer),
lt_second_chat,
App::peerName(list[1]->peer));
}
return lng_archived_unread(
lt_count,
count - 2,
lt_chat,
App::peerName(list[0]->peer),
lt_second_chat,
App::peerName(list[1]->peer));
}
} // namespace
RippleRow::RippleRow() = default;
RippleRow::~RippleRow() = default;
@ -44,6 +81,22 @@ uint64 Row::sortKey() const {
return _id.entry()->sortKeyInChatList();
}
void Row::validateListEntryCache() const {
const auto folder = _id.folder();
if (!folder) {
return;
}
const auto version = folder->chatListViewVersion();
if (_listEntryCacheVersion == version) {
return;
}
_listEntryCacheVersion = version;
_listEntryCache.setText(
st::dialogsTextStyle,
ComposeFolderListEntryText(folder),
Ui::DialogTextOptions());
}
FakeRow::FakeRow(Key searchInChat, not_null<HistoryItem*> item)
: _searchInChat(searchInChat)
, _item(item)

View file

@ -62,6 +62,11 @@ public:
}
uint64 sortKey() const;
void validateListEntryCache() const;
const Text &listEntryCache() const {
return _listEntryCache;
}
// for any attached data, for example View in contacts list
void *attached = nullptr;
@ -70,6 +75,8 @@ private:
Key _id;
int _pos = 0;
mutable uint32 _listEntryCacheVersion = 0;
mutable Text _listEntryCache;
};

View file

@ -742,17 +742,18 @@ void HistoryItem::drawInDialog(
DrawInDialog way,
const HistoryItem *&cacheFor,
Text &cache) const {
if (r.isEmpty()) {
return;
}
if (cacheFor != this) {
cacheFor = this;
cache.setText(st::dialogsTextStyle, inDialogsText(way), Ui::DialogTextOptions());
}
if (r.width()) {
p.setTextPalette(active ? st::dialogsTextPaletteActive : (selected ? st::dialogsTextPaletteOver : st::dialogsTextPalette));
p.setFont(st::dialogsTextFont);
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
cache.drawElided(p, r.left(), r.top(), r.width(), r.height() / st::dialogsTextFont->height);
p.restoreTextPalette();
}
p.setTextPalette(active ? st::dialogsTextPaletteActive : (selected ? st::dialogsTextPaletteOver : st::dialogsTextPalette));
p.setFont(st::dialogsTextFont);
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
cache.drawElided(p, r.left(), r.top(), r.width(), r.height() / st::dialogsTextFont->height);
p.restoreTextPalette();
}
HistoryItem::~HistoryItem() {

View file

@ -796,8 +796,8 @@ void PeerMenuAddMuteAction(
void ToggleHistoryArchived(not_null<History*> history, bool archived) {
const auto callback = [=] {
Ui::Toast::Show(lang(archived
? lng_chat_archived
: lng_chat_unarchived));
? lng_archived_added
: lng_archived_removed));
};
history->session().api().toggleHistoryArchived(
history,