From 782e70b171eb2f3828d992b42590d497ed1987ac Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 5 Jan 2018 18:57:18 +0300 Subject: [PATCH] Support basic feed display in chats list. --- Telegram/SourceFiles/data/data_feed.cpp | 125 +++++++- Telegram/SourceFiles/data/data_feed.h | 76 ++++- .../SourceFiles/data/data_peer_values.cpp | 3 +- Telegram/SourceFiles/data/data_session.cpp | 19 +- Telegram/SourceFiles/dialogs/dialogs_common.h | 28 -- .../SourceFiles/dialogs/dialogs_entry.cpp | 166 +++++++++++ Telegram/SourceFiles/dialogs/dialogs_entry.h | 131 ++++++++ .../dialogs/dialogs_indexed_list.cpp | 6 +- .../dialogs/dialogs_indexed_list.h | 4 +- .../dialogs/dialogs_inner_widget.cpp | 280 +++++++++++------- .../dialogs/dialogs_inner_widget.h | 12 +- Telegram/SourceFiles/dialogs/dialogs_key.cpp | 23 +- Telegram/SourceFiles/dialogs/dialogs_key.h | 7 +- .../SourceFiles/dialogs/dialogs_layout.cpp | 149 ++++++---- Telegram/SourceFiles/dialogs/dialogs_layout.h | 4 +- Telegram/SourceFiles/dialogs/dialogs_row.h | 5 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 19 +- Telegram/SourceFiles/dialogs/dialogs_widget.h | 4 +- Telegram/SourceFiles/history/history.cpp | 201 +++++-------- Telegram/SourceFiles/history/history.h | 73 ++--- Telegram/SourceFiles/history/history_item.cpp | 44 ++- .../SourceFiles/history/history_service.cpp | 9 +- Telegram/SourceFiles/mainwidget.cpp | 49 +-- Telegram/SourceFiles/mainwidget.h | 4 +- .../SourceFiles/window/window_controller.cpp | 8 +- .../SourceFiles/window/window_peer_menu.cpp | 158 +++++++--- .../SourceFiles/window/window_peer_menu.h | 9 + Telegram/gyp/telegram_sources.txt | 3 +- 28 files changed, 1092 insertions(+), 527 deletions(-) delete mode 100644 Telegram/SourceFiles/dialogs/dialogs_common.h create mode 100644 Telegram/SourceFiles/dialogs/dialogs_entry.cpp create mode 100644 Telegram/SourceFiles/dialogs/dialogs_entry.h diff --git a/Telegram/SourceFiles/data/data_feed.cpp b/Telegram/SourceFiles/data/data_feed.cpp index e623bb33e..a90301473 100644 --- a/Telegram/SourceFiles/data/data_feed.cpp +++ b/Telegram/SourceFiles/data/data_feed.cpp @@ -7,17 +7,126 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_feed.h" +#include "dialogs/dialogs_key.h" + namespace Data { -Feed::Feed(FeedId id) : _id(id) { +FeedPosition::FeedPosition(const MTPFeedPosition &position) +: date(position.c_feedPosition().vdate.v) +, peerId(peerFromMTP(position.c_feedPosition().vpeer)) +, msgId(position.c_feedPosition().vid.v) { +} + +FeedPosition::FeedPosition(not_null item) +: date(toServerTime(item->date.toTime_t()).v) +, peerId(item->history()->peer->id) +, msgId(item->id) { +} + +bool FeedPosition::operator<(const FeedPosition &other) const { + if (date < other.date) { + return true; + } else if (other.date < date) { + return false; + } + const auto peer = peerToBareInt(peerId); + const auto otherPeer = peerToBareInt(other.peerId); + if (peer < otherPeer) { + return true; + } else if (otherPeer < peer) { + return false; + } + return (msgId < other.msgId); +} + +Feed::Feed(FeedId id) +: Entry(this) +, _id(id) { } void Feed::registerOne(not_null channel) { - _channels.emplace(channel); + const auto history = App::history(channel); + if (!base::contains(_channels, history)) { + _channels.push_back(history); + if (history->lastMsg) { + updateLastMessage(history->lastMsg); + } + } } void Feed::unregisterOne(not_null channel) { - _channels.remove(channel); + const auto history = App::history(channel); + _channels.erase(ranges::remove(_channels, history), end(_channels)); + if (_lastMessage->history() == history) { + messageRemoved(_lastMessage); + } +} + +void Feed::updateLastMessage(not_null item) { + if (justSetLastMessage(item)) { + setChatsListDate(_lastMessage->date); + } +} + +void Feed::loadUserpic() { + constexpr auto kPaintUserpicsCount = 4; + auto load = kPaintUserpicsCount; + for (const auto channel : _channels) { + channel->peer->loadUserpic(); + if (!--load) { + break; + } + } +} + +void Feed::paintUserpic( + Painter &p, + int x, + int y, + int size) const { + const auto small = (size - st::lineWidth) / 2; + const auto delta = size - small; + auto index = 0; + for (const auto channel : _channels) { + channel->peer->paintUserpic(p, x, y, small); + switch (++index) { + case 1: + case 3: x += delta; break; + case 2: x -= delta; y += delta; break; + } + } +} + +bool Feed::justSetLastMessage(not_null item) { + if (_lastMessage && FeedPosition(item) <= FeedPosition(_lastMessage)) { + return false; + } + _lastMessage = item; + return true; +} + +void Feed::messageRemoved(not_null item) { + if (_lastMessage == item) { + recountLastMessage(); + } +} + +void Feed::historyCleared(not_null history) { + if (_lastMessage->history() == history) { + recountLastMessage(); + } +} + +void Feed::recountLastMessage() { + _lastMessage = nullptr; + for (const auto history : _channels) { + if (const auto last = history->lastMsg) { + justSetLastMessage(last); + } + } + if (_lastMessage) { + setChatsListDate(_lastMessage->date); + } } void Feed::setUnreadCounts(int unreadCount, int unreadMutedCount) { @@ -25,14 +134,8 @@ void Feed::setUnreadCounts(int unreadCount, int unreadMutedCount) { _unreadMutedCount = unreadMutedCount; } -void Feed::cachePinnedIndex(int index) { - _pinnedIndex = index; -} - -uint64 Feed::sortKeyInChatList() const { - return 0ULL;/* isPinnedDialog() - ? pinnedDialogPos(_pinnedIndex) - : dialogPosFromDate(chatListDate());*/ +void Feed::setUnreadPosition(const FeedPosition &position) { + _unreadPosition = position; } } // namespace Data diff --git a/Telegram/SourceFiles/data/data_feed.h b/Telegram/SourceFiles/data/data_feed.h index f01a2e6f0..afdc2bd97 100644 --- a/Telegram/SourceFiles/data/data_feed.h +++ b/Telegram/SourceFiles/data/data_feed.h @@ -7,11 +7,47 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "dialogs/dialogs_entry.h" + class ChannelData; namespace Data { -class Feed { +struct FeedPosition { + FeedPosition() = default; + FeedPosition(const MTPFeedPosition &position); + FeedPosition(not_null item); + + explicit operator bool() const { + return (msgId != 0); + } + + bool operator<(const FeedPosition &other) const; + inline bool operator>(const FeedPosition &other) const { + return other < *this; + } + inline bool operator<=(const FeedPosition &other) const { + return !(other < *this); + } + inline bool operator>=(const FeedPosition &other) const { + return !(*this < other); + } + inline bool operator==(const FeedPosition &other) const { + return (date == other.date) + && (peerId == other.peerId) + && (msgId == other.msgId); + } + inline bool operator!=(const FeedPosition &other) const { + return !(*this == other); + } + + TimeId date = 0; + PeerId peerId = 0; + MsgId msgId = 0; + +}; + +class Feed : public Dialogs::Entry { public: Feed(FeedId id); @@ -21,20 +57,44 @@ public: void registerOne(not_null channel); void unregisterOne(not_null channel); - void setUnreadCounts(int unreadCount, int unreadMutedCount); + void updateLastMessage(not_null item); + void messageRemoved(not_null item); + void historyCleared(not_null history); - bool isPinnedDialog() const { - return _pinnedIndex > 0; + void setUnreadCounts(int unreadCount, int unreadMutedCount); + void setUnreadPosition(const FeedPosition &position); + + bool toImportant() const override { + return false; // TODO feeds workmode } - void cachePinnedIndex(int index); - uint64 sortKeyInChatList() const; + int chatListUnreadCount() const override { + return _unreadCount; + } + bool chatListMutedBadge() const override { + return _unreadCount <= _unreadMutedCount; + } + HistoryItem *chatsListItem() const override { + return _lastMessage; + } + void loadUserpic() override; + void paintUserpic( + Painter &p, + int x, + int y, + int size) const override; private: + void recountLastMessage(); + bool justSetLastMessage(not_null item); + FeedId _id = 0; - base::flat_set> _channels; + std::vector> _channels; + + HistoryItem *_lastMessage = nullptr; + + FeedPosition _unreadPosition; int _unreadCount = 0; int _unreadMutedCount = 0; - int _pinnedIndex = 0; bool _complete = false; }; diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index a0b812eac..5c5533966 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -176,8 +176,7 @@ TimeId SortByOnlineValue(not_null user, TimeId now) { } const auto online = user->onlineTill; const auto fromDate = [](const QDate &date) { - const auto shift = (unixtime() - myunixtime()); - return static_cast(QDateTime(date).toTime_t()) + shift; + return toServerTime(QDateTime(date).toTime_t()).v; }; if (online <= 0) { switch (online) { diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 533280bbc..3dcf0f1b9 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -235,8 +235,8 @@ void Session::reorderTwoPinnedDialogs( Assert(index2 >= 0 && index2 < order.size()); Assert(index1 != index2); std::swap(_pinnedDialogs[index1], _pinnedDialogs[index2]); - key1.cachePinnedIndex(index2 + 1); - key2.cachePinnedIndex(index1 + 1); + key1.entry()->cachePinnedIndex(index2 + 1); + key2.entry()->cachePinnedIndex(index1 + 1); } void Session::setIsPinned(const Dialogs::Key &key, bool pinned) { @@ -249,26 +249,31 @@ void Session::setIsPinned(const Dialogs::Key &key, bool pinned) { Assert(alreadyIndex < count); for (auto index = alreadyIndex + 1; index != count; ++index) { _pinnedDialogs[index - 1] = std::move(_pinnedDialogs[index]); - _pinnedDialogs[index - 1].cachePinnedIndex(index); + _pinnedDialogs[index - 1].entry()->cachePinnedIndex(index); } _pinnedDialogs.back() = std::move(saved); - _pinnedDialogs.back().cachePinnedIndex(count); + _pinnedDialogs.back().entry()->cachePinnedIndex(count); } else { _pinnedDialogs.push_back(key); if (_pinnedDialogs.size() > Global::PinnedDialogsCountMax()) { - _pinnedDialogs.front().cachePinnedIndex(0); + _pinnedDialogs.front().entry()->cachePinnedIndex(0); _pinnedDialogs.pop_front(); auto index = 0; for (const auto &pinned : _pinnedDialogs) { - pinned.cachePinnedIndex(++index); + pinned.entry()->cachePinnedIndex(++index); } } else { - key.cachePinnedIndex(_pinnedDialogs.size()); + key.entry()->cachePinnedIndex(_pinnedDialogs.size()); } } } else if (!pinned && already != _pinnedDialogs.end()) { + key.entry()->cachePinnedIndex(0); _pinnedDialogs.erase(already); + auto index = 0; + for (const auto &pinned : _pinnedDialogs) { + pinned.entry()->cachePinnedIndex(++index); + } } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_common.h b/Telegram/SourceFiles/dialogs/dialogs_common.h deleted file mode 100644 index 55257f6b8..000000000 --- a/Telegram/SourceFiles/dialogs/dialogs_common.h +++ /dev/null @@ -1,28 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#pragma once - -#include "base/flat_map.h" - -namespace Dialogs { - -class Row; -using RowsByLetter = base::flat_map; - -enum class SortMode { - Date = 0x00, - Name = 0x01, - Add = 0x02, -}; - -enum class Mode { - All = 0x00, - Important = 0x01, -}; - -} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp new file mode 100644 index 000000000..8a4bcab35 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp @@ -0,0 +1,166 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "dialogs/dialogs_entry.h" + +#include "dialogs/dialogs_key.h" +#include "dialogs/dialogs_indexed_list.h" +#include "mainwidget.h" +#include "styles/style_dialogs.h" + +namespace Dialogs { +namespace { + +auto DialogsPosToTopShift = 0; + +uint64 DialogPosFromDate(const QDateTime &date) { + if (date.isNull()) { + return 0; + } + return (uint64(date.toTime_t()) << 32) | (++DialogsPosToTopShift); +} + +uint64 PinnedDialogPos(int pinnedIndex) { + return 0xFFFFFFFF00000000ULL + pinnedIndex; +} + +} // namespace + +bool MessageIsLess(not_null a, not_null b) { + if (a->date < b->date) { + return true; + } else if (b->date < a->date) { + return false; + } + const auto apeer = a->history()->peer->bareId(); + const auto bpeer = b->history()->peer->bareId(); + if (apeer < bpeer) { + return true; + } else if (bpeer < apeer) { + return false; + } + return a->id < b->id; +} + +Entry::Entry(const Key &key) +: lastItemTextCache(st::dialogsTextWidthMin) +, _key(key) { +} + +void Entry::cachePinnedIndex(int index) { + if (_pinnedIndex != index) { + const auto wasPinned = isPinnedDialog(); + _pinnedIndex = index; + updateChatListSortPosition(); + updateChatListEntry(); + if (wasPinned != isPinnedDialog()) { + changedChatListPinHook(); + } + } +} + +void Entry::updateChatListSortPosition() { + _sortKeyInChatList = isPinnedDialog() + ? PinnedDialogPos(_pinnedIndex) + : DialogPosFromDate(adjustChatListDate()); + if (auto m = App::main()) { + if (needUpdateInChatList()) { + if (_sortKeyInChatList) { + m->createDialog(_key); + updateChatListEntry(); + } else { + removeDialog(); + } + } + } +} + +void Entry::removeDialog() { + if (const auto main = App::main()) { + main->removeDialog(_key); + } +} + +PositionChange Entry::adjustByPosInChatList( + Mode list, + not_null indexed) { + const auto lnk = mainChatListLink(list); + const auto movedFrom = lnk->pos(); + indexed->adjustByPos(chatListLinks(list)); + const auto movedTo = lnk->pos(); + return { movedFrom, movedTo }; +} + +void Entry::setChatsListDate(const QDateTime &date) { + if (!_lastMessageDate.isNull() && _lastMessageDate >= date) { + if (!needUpdateInChatList() || !inChatList(Dialogs::Mode::All)) { + return; + } + } + _lastMessageDate = date; + updateChatListSortPosition(); +} + +int Entry::posInChatList(Dialogs::Mode list) const { + return mainChatListLink(list)->pos(); +} + +not_null Entry::addToChatList( + Mode list, + not_null indexed) { + if (!inChatList(list)) { + chatListLinks(list) = indexed->addToEnd(_key); + changedInChatListHook(list, true); + } + return mainChatListLink(list); +} + +void Entry::removeFromChatList( + Dialogs::Mode list, + not_null indexed) { + if (inChatList(list)) { + indexed->del(_key); + chatListLinks(list).clear(); + changedInChatListHook(list, false); + } +} + +void Entry::removeChatListEntryByLetter(Mode list, QChar letter) { + Expects(letter != 0); + + if (inChatList(list)) { + chatListLinks(list).remove(letter); + } +} + +void Entry::addChatListEntryByLetter( + Mode list, + QChar letter, + not_null row) { + Expects(letter != 0); + + if (inChatList(list)) { + chatListLinks(list).emplace(letter, row); + } +} + +void Entry::updateChatListEntry() const { + if (const auto main = App::main()) { + if (inChatList(Mode::All)) { + main->dlgUpdated( + Mode::All, + mainChatListLink(Mode::All)); + if (inChatList(Mode::Important)) { + main->dlgUpdated( + Mode::Important, + mainChatListLink(Mode::Important)); + } + } + } +} + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h new file mode 100644 index 000000000..bcedaf436 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h @@ -0,0 +1,131 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/flat_map.h" + +#include "dialogs/dialogs_key.h" + +namespace Dialogs { + +class Row; +class IndexedList; +using RowsByLetter = base::flat_map>; + +enum class SortMode { + Date = 0x00, + Name = 0x01, + Add = 0x02, +}; + +enum class Mode { + All = 0x00, + Important = 0x01, +}; + +struct PositionChange { + int movedFrom; + int movedTo; +}; + +bool MessageIsLess(not_null a, not_null b); + +class Entry { +public: + Entry(const Key &key); + + PositionChange adjustByPosInChatList( + Mode list, + not_null indexed); + bool inChatList(Mode list) const { + return !chatListLinks(list).empty(); + } + int posInChatList(Mode list) const; + not_null addToChatList(Mode list, not_null indexed); + void removeFromChatList(Mode list, not_null indexed); + void removeChatListEntryByLetter(Mode list, QChar letter); + void addChatListEntryByLetter( + Mode list, + QChar letter, + not_null row); + void updateChatListEntry() const; + bool isPinnedDialog() const { + return _pinnedIndex > 0; + } + void cachePinnedIndex(int index); + uint64 sortKeyInChatList() const { + return _sortKeyInChatList; + } + void updateChatListSortPosition(); + void setChatsListDate(const QDateTime &date); + + virtual bool toImportant() const = 0; + + virtual bool needUpdateInChatList() const { + return true; + } + + virtual int chatListUnreadCount() const = 0; + virtual bool chatListMutedBadge() const = 0; + virtual HistoryItem *chatsListItem() const = 0; + + virtual void loadUserpic() = 0; + virtual void paintUserpic( + Painter &p, + int x, + int y, + int size) const = 0; + void paintUserpicLeft( + Painter &p, + int x, + int y, + int w, + int size) const { + paintUserpic(p, rtl() ? (w - x - size) : x, y, size); + } + + QDateTime chatsListDate() const { + return _lastMessageDate; + } + + virtual ~Entry() = default; + + mutable const HistoryItem *textCachedFor = nullptr; // cache + mutable Text lastItemTextCache; + +private: + virtual QDateTime adjustChatListDate() const { + return chatsListDate(); + } + virtual void removeDialog(); + virtual void changedInChatListHook(Dialogs::Mode list, bool added) { + } + virtual void changedChatListPinHook() { + } + + RowsByLetter &chatListLinks(Mode list) { + return _chatListLinks[static_cast(list)]; + } + const RowsByLetter &chatListLinks(Mode list) const { + return _chatListLinks[static_cast(list)]; + } + Row *mainChatListLink(Mode list) const { + auto it = chatListLinks(list).find(0); + Assert(it != chatListLinks(list).cend()); + return it->second; + } + + Dialogs::Key _key; + RowsByLetter _chatListLinks[2]; + uint64 _sortKeyInChatList = 0; + int _pinnedIndex = 0; + QDateTime _lastMessageDate; + +}; + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp index 9420ab4b6..0a5461d7c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp @@ -152,14 +152,12 @@ void IndexedList::adjustByName( void IndexedList::adjustNames( Mode list, - Key key, + not_null history, const PeerData::NameFirstChars &oldChars) { + const auto key = Dialogs::Key(history); auto mainRow = _list.getRow(key); if (!mainRow) return; - // #TODO feeds dialogs - auto history = mainRow->history(); - PeerData::NameFirstChars toRemove = oldChars, toAdd; for (auto ch : key.nameFirstChars()) { auto j = toRemove.find(ch); diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h index 0022e74dc..5550f7c4b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "dialogs/dialogs_common.h" +#include "dialogs/dialogs_entry.h" #include "dialogs/dialogs_list.h" class History; @@ -80,7 +80,7 @@ private: const PeerData::NameFirstChars &oldChars); void adjustNames( Mode list, - Key key, + not_null history, const PeerData::NameFirstChars &oldChars); SortMode _sortMode; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index a1bfd7a1d..1d293e387 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -186,17 +186,24 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO p.translate(0, st::dialogsImportantBarHeight); } auto otherStart = rows->size() * st::dialogsRowHeight; - auto active = App::main()->activePeer(); - // #TODO feeds dialogs - auto selected = _menuPeer - ? _menuPeer + // #TODO feeds show + const auto active = [&] { + if (const auto peer = App::main()->activePeer()) { + if (const auto history = App::historyLoaded(peer)) { + return Dialogs::Key(history); + } + } + return Dialogs::Key(); + }(); + const auto selected = _menuKey + ? _menuKey : (isPressed() ? (_pressed - ? _pressed->history()->peer - : nullptr) + ? _pressed->key() + : Dialogs::Key()) : (_selected - ? _selected->history()->peer - : nullptr)); + ? _selected->key() + : Dialogs::Key())); if (otherStart) { auto reorderingPinned = (_aboveIndex >= 0 && !_pinnedRows.empty()); auto &list = rows->all(); @@ -302,17 +309,31 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO auto to = ceilclamp(r.y() + r.height() - skip, st::dialogsRowHeight, 0, _filterResults.size()); p.translate(0, from * st::dialogsRowHeight); if (from < _filterResults.size()) { - auto activePeer = App::main()->activePeer(); - auto activeMsgId = App::main()->activeMsgId(); + const auto activePeer = App::main()->activePeer(); + const auto activeMsgId = App::main()->activeMsgId(); for (; from < to; ++from) { - auto row = _filterResults[from]; - // #TODO feeds dialogs - auto peer = row->history()->peer; - auto active = !activeMsgId && ((peer == activePeer) - || (peer->migrateTo() - && peer->migrateTo() == activePeer)); - auto selected = _menuPeer ? (peer == _menuPeer) : (from == (isPressed() ? _filteredPressed : _filteredSelected)); - Dialogs::Layout::RowPainter::paint(p, _filterResults[from], fullWidth, active, selected, paintingOther, ms); + const auto row = _filterResults[from]; + const auto key = row->key(); + const auto history = key.history(); + const auto peer = history ? history->peer : nullptr; + const auto active = !activeMsgId + && peer + && activePeer + && ((peer == activePeer) + || (peer->migrateTo() == activePeer)); + const auto selected = _menuKey + ? (key == _menuKey) + : (from == (isPressed() + ? _filteredPressed + : _filteredSelected)); + Dialogs::Layout::RowPainter::paint( + p, + _filterResults[from], + fullWidth, + active, + selected, + paintingOther, + ms); p.translate(0, st::dialogsRowHeight); } } @@ -335,10 +356,15 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO auto activePeer = App::main()->activePeer(); auto activeMsgId = App::main()->activeMsgId(); for (; from < to; ++from) { - auto &result = _peerSearchResults[from]; - auto peer = result->peer; - auto active = ((peer == activePeer) || (peer->migrateTo() && peer->migrateTo() == activePeer)) && !activeMsgId; - auto selected = false ? (peer == _menuPeer) : (from == (isPressed() ? _peerSearchPressed : _peerSearchSelected)); + const auto &result = _peerSearchResults[from]; + const auto peer = result->peer; + const auto active = !activeMsgId + && activePeer + && ((peer == activePeer) + || (peer->migrateTo() == activePeer)); + const auto selected = (from == (isPressed() + ? _peerSearchPressed + : _peerSearchSelected)); paintPeerSearchResult(p, result.get(), fullWidth, active, selected, paintingOther, ms); p.translate(0, st::dialogsRowHeight); } @@ -377,11 +403,17 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO auto activePeer = App::main()->activePeer(); auto activeMsgId = App::main()->activeMsgId(); for (; from < to; ++from) { - auto &result = _searchResults[from]; - auto item = result->item(); - auto peer = item->history()->peer; - auto active = (peer == activePeer && item->id == activeMsgId) || (peer->migrateTo() && peer->migrateTo() == activePeer && item->id == -activeMsgId); - auto selected = false ? (peer == _menuPeer) : (from == (isPressed() ? _searchedPressed : _searchedSelected)); + const auto &result = _searchResults[from]; + const auto item = result->item(); + const auto peer = item->history()->peer; + const auto active = (peer == activePeer + && item->id == activeMsgId) + || (peer->migrateTo() + && peer->migrateTo() == activePeer + && item->id == -activeMsgId); + const auto selected = (from == (isPressed() + ? _searchedPressed + : _searchedSelected)); Dialogs::Layout::RowPainter::paint(p, result.get(), fullWidth, active, selected, paintingOther, ms); p.translate(0, st::dialogsRowHeight); } @@ -394,8 +426,8 @@ void DialogsInner::paintDialog( Painter &p, not_null row, int fullWidth, - PeerData *active, - PeerData *selected, + Dialogs::Key active, + Dialogs::Key selected, bool onlyBackground, TimeMs ms) { auto pos = row->pos(); @@ -404,11 +436,8 @@ void DialogsInner::paintDialog( yadd = qRound(_pinnedRows[pos].yadd.current()); } if (xadd || yadd) p.translate(xadd, yadd); - // #TODO feeds dialogs - auto isActive = (row->history()->peer == active) - || (row->history()->peer->migrateTo() - && row->history()->peer->migrateTo() == active); - auto isSelected = (row->history()->peer == selected); + const auto isActive = (row->key() == active); + const auto isSelected = (row->key() == selected); Dialogs::Layout::RowPainter::paint( p, row, @@ -697,8 +726,7 @@ void DialogsInner::mousePressEvent(QMouseEvent *e) { auto row = _pressed; row->addRipple(e->pos() - QPoint(0, dialogsOffset() + _pressed->pos() * st::dialogsRowHeight), QSize(getFullWidth(), st::dialogsRowHeight), [this, row] { if (!_a_pinnedShifting.animating()) { - // #TODO feeds dialogs - row->history()->updateChatListEntry(); + row->entry()->updateChatListEntry(); } }); _dragStart = e->pos(); @@ -1072,41 +1100,57 @@ void DialogsInner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRo } } -void DialogsInner::createDialog(not_null history) { - if (history->peer->loadedStatus != PeerData::LoadedStatus::FullLoaded) { - LOG(("API Error: DialogsInner::createDialog() called for a non loaded peer!")); - return; +void DialogsInner::createDialog(Dialogs::Key key) { + if (const auto history = key.history()) { + if (history->peer->loadedStatus + != PeerData::LoadedStatus::FullLoaded) { + LOG(("API Error: " + "DialogsInner::createDialog() called for a non loaded peer!" + )); + return; + } } - bool creating = !history->inChatList(Dialogs::Mode::All); + const auto entry = key.entry(); + auto creating = !entry->inChatList(Dialogs::Mode::All); if (creating) { - auto mainRow = history->addToChatList(Dialogs::Mode::All, _dialogs.get()); - _contactsNoDialogs->del(history, mainRow); + const auto mainRow = entry->addToChatList( + Dialogs::Mode::All, + _dialogs.get()); + _contactsNoDialogs->del(key, mainRow); } - if (_dialogsImportant && !history->inChatList(Dialogs::Mode::Important) && !history->mute()) { + if (_dialogsImportant + && !entry->inChatList(Dialogs::Mode::Important) + && entry->toImportant()) { if (Global::DialogsMode() == Dialogs::Mode::Important) { creating = true; } - history->addToChatList(Dialogs::Mode::Important, _dialogsImportant.get()); + entry->addToChatList( + Dialogs::Mode::Important, + _dialogsImportant.get()); } - auto changed = history->adjustByPosInChatList(Dialogs::Mode::All, _dialogs.get()); + auto changed = entry->adjustByPosInChatList( + Dialogs::Mode::All, + _dialogs.get()); if (_dialogsImportant) { - if (history->mute()) { + if (!entry->toImportant()) { if (Global::DialogsMode() == Dialogs::Mode::Important) { return; } } else { - auto importantChanged = history->adjustByPosInChatList(Dialogs::Mode::Important, _dialogsImportant.get()); + const auto importantChanged = entry->adjustByPosInChatList( + Dialogs::Mode::Important, + _dialogsImportant.get()); if (Global::DialogsMode() == Dialogs::Mode::Important) { changed = importantChanged; } } } - int from = dialogsOffset() + changed.movedFrom * st::dialogsRowHeight; - int to = dialogsOffset() + changed.movedTo * st::dialogsRowHeight; + const auto from = dialogsOffset() + changed.movedFrom * st::dialogsRowHeight; + const auto to = dialogsOffset() + changed.movedTo * st::dialogsRowHeight; if (!_dragging) { // Don't jump in chats list scroll position while dragging. emit dialogMoved(from, to); @@ -1119,30 +1163,35 @@ void DialogsInner::createDialog(not_null history) { } } -void DialogsInner::removeDialog(not_null history) { - // #TODO feeds dialogs - if (history->peer == _menuPeer && _menu) { +void DialogsInner::removeDialog(Dialogs::Key key) { + if (key == _menuKey && _menu) { InvokeQueued(this, [this] { _menu = nullptr; }); } - if (_selected && _selected->history() == history) { + if (_selected && _selected->key() == key) { _selected = nullptr; } - if (_pressed && _pressed->history() == history) { + if (_pressed && _pressed->key() == key) { setPressed(nullptr); } - history->removeFromChatList(Dialogs::Mode::All, _dialogs.get()); + const auto entry = key.entry(); + entry->removeFromChatList( + Dialogs::Mode::All, + _dialogs.get()); if (_dialogsImportant) { - history->removeFromChatList(Dialogs::Mode::Important, _dialogsImportant.get()); + entry->removeFromChatList( + Dialogs::Mode::Important, + _dialogsImportant.get()); } - Auth().notifications().clearFromHistory(history); - if (_contacts->contains(history)) { - if (!_contactsNoDialogs->contains(history)) { - _contactsNoDialogs->addByName(history); + if (const auto history = key.history()) { + Auth().notifications().clearFromHistory(history); + Local::removeSavedPeer(history->peer); + } + if (_contacts->contains(key)) { + if (!_contactsNoDialogs->contains(key)) { + _contactsNoDialogs->addByName(key); } } - Local::removeSavedPeer(history->peer); - emit App::main()->dialogsUpdated(); refresh(); @@ -1257,29 +1306,28 @@ void DialogsInner::enterEventHook(QEvent *e) { updateSelected(); } -void DialogsInner::updateSelectedRow(PeerData *peer) { - // #TODO feeds dialogs +void DialogsInner::updateSelectedRow(Dialogs::Key key) { if (_state == DefaultState) { - if (peer) { - if (auto h = App::historyLoaded(peer->id)) { - if (h->inChatList(Global::DialogsMode())) { - auto position = h->posInChatList(Global::DialogsMode()); - auto top = dialogsOffset(); - if (position >= 0 && position < _pinnedRows.size()) { - top += qRound(_pinnedRows[position].yadd.current()); - } - update(0, top + position * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); - } + if (key) { + const auto entry = key.entry(); + if (!entry->inChatList(Global::DialogsMode())) { + return; } + auto position = entry->posInChatList(Global::DialogsMode()); + auto top = dialogsOffset(); + if (position >= 0 && position < _pinnedRows.size()) { + top += qRound(_pinnedRows[position].yadd.current()); + } + update(0, top + position * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); } else if (_selected) { update(0, dialogsOffset() + _selected->pos() * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); } else if (_importantSwitchSelected) { update(0, 0, getFullWidth(), st::dialogsImportantBarHeight); } } else if (_state == FilteredState || _state == SearchedState) { - if (peer) { + if (key) { for (auto i = 0, l = _filterResults.size(); i != l; ++i) { - if (_filterResults[i]->history()->peer == peer) { + if (_filterResults[i]->key() == key) { update(0, filteredOffset() + i * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); break; } @@ -1325,37 +1373,46 @@ void DialogsInner::contextMenuEvent(QContextMenuEvent *e) { updateSelected(); } - // #TODO feeds context menu - auto history = [&]() -> History* { + const auto key = [&]() -> Dialogs::Key { if (_state == DefaultState) { if (_selected) { - return _selected->history(); + return _selected->key(); } } else if (_state == FilteredState || _state == SearchedState) { if (_filteredSelected >= 0 && _filteredSelected < _filterResults.size()) { - return _filterResults[_filteredSelected]->history(); + return _filterResults[_filteredSelected]->key(); } } - return nullptr; + return Dialogs::Key(); }(); - if (!history) return; + if (!key) return; - _menuPeer = history->peer; + _menuKey = key; if (_pressButton != Qt::LeftButton) { mousePressReleased(_pressButton); } _menu = base::make_unique_q(nullptr); - Window::FillPeerMenu( - _controller, - _menuPeer, - [this](const QString &text, base::lambda callback) { - return _menu->addAction(text, std::move(callback)); - }, - Window::PeerMenuSource::ChatsList); + if (const auto history = key.history()) { + Window::FillPeerMenu( + _controller, + history->peer, + [&](const QString &text, base::lambda callback) { + return _menu->addAction(text, std::move(callback)); + }, + Window::PeerMenuSource::ChatsList); + } else if (const auto feed = key.feed()) { + Window::FillFeedMenu( + _controller, + feed, + [&](const QString &text, base::lambda callback) { + return _menu->addAction(text, std::move(callback)); + }, + Window::PeerMenuSource::ChatsList); + } connect(_menu.get(), &QObject::destroyed, [this] { - if (_menuPeer) { - updateSelectedRow(base::take(_menuPeer)); + if (_menuKey) { + updateSelectedRow(base::take(_menuKey)); } auto localPos = mapFromGlobal(QCursor::pos()); if (rect().contains(localPos)) { @@ -1428,10 +1485,12 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) { } } } - _filterResults.reserve((toFilter ? toFilter->size() : 0) + (toFilterContacts ? toFilterContacts->size() : 0)); + _filterResults.reserve((toFilter ? toFilter->size() : 0) + + (toFilterContacts ? toFilterContacts->size() : 0)); if (toFilter) { for_const (auto row, *toFilter) { - // #TODO feeds dialogs + if (!row->history()) continue; + // #TODO feeds name const auto &nameWords = row->history()->peer->nameWords(); auto nb = nameWords.cbegin(), ne = nameWords.cend(), ni = nb; for (fi = fb; fi != fe; ++fi) { @@ -1452,7 +1511,8 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) { } if (toFilterContacts) { for_const (auto row, *toFilterContacts) { - // #TODO feeds dialogs + if (!row->history()) continue; + // #TODO feeds name const auto &nameWords = row->history()->peer->nameWords(); auto nb = nameWords.cbegin(), ne = nameWords.cend(), ni = nb; for (fi = fb; fi != fe; ++fi) { @@ -1524,14 +1584,19 @@ void DialogsInner::clearSearchResults(bool clearPeerSearchResults) { } PeerData *DialogsInner::updateFromParentDrag(QPoint globalPos) { - // #TODO feeds dialogs _mouseSelection = true; updateSelected(mapFromGlobal(globalPos)); + const auto getPeerFromRow = [](Dialogs::Row *row) -> PeerData* { + if (const auto history = row ? row->history() : nullptr) { + return history->peer; + } + return nullptr; + }; if (_state == DefaultState) { - if (_selected) return _selected->history()->peer; + return getPeerFromRow(_selected); } else if (_state == FilteredState || _state == SearchedState) { if (_filteredSelected >= 0 && _filteredSelected < _filterResults.size()) { - return _filterResults[_filteredSelected]->history()->peer; + return getPeerFromRow(_filterResults[_filteredSelected]); } else if (_peerSearchSelected >= 0 && _peerSearchSelected < _peerSearchResults.size()) { return _peerSearchResults[_peerSearchSelected]->peer; } else if (_searchedSelected >= 0 && _searchedSelected < _searchResults.size()) { @@ -1618,8 +1683,8 @@ void DialogsInner::applyDialog(const MTPDdialog &dialog) { dialog.vnotify_settings, history); - if (!history->isPinnedDialog() && !history->lastMsgDate.isNull()) { - addSavedPeersAfter(history->lastMsgDate); + if (!history->isPinnedDialog() && !history->chatsListDate().isNull()) { + addSavedPeersAfter(history->chatsListDate()); } _contactsNoDialogs->del(history); if (peer->migrateFrom()) { @@ -1654,9 +1719,12 @@ void DialogsInner::applyFeedDialog(const MTPDdialogFeed &dialog) { feed->setUnreadCounts( dialog.vunread_count.v, dialog.vunread_muted_count.v); + if (!feed->isPinnedDialog() && !feed->chatsListDate().isNull()) { + addSavedPeersAfter(feed->chatsListDate()); + } if (dialog.has_read_max_position()) { - // #TODO feeds -// feed->set + const auto position = Data::FeedPosition(dialog.vread_max_position); + feed->setUnreadPosition(position); } } @@ -2082,8 +2150,7 @@ void DialogsInner::loadPeerPhotos() { if (((*i)->pos() * st::dialogsRowHeight) >= yTo) { break; } - // #TODO feeds dialogs - (*i)->history()->peer->loadUserpic(); + (*i)->entry()->loadUserpic(); } yFrom = 0; } else { @@ -2098,8 +2165,7 @@ void DialogsInner::loadPeerPhotos() { if (to > _filterResults.size()) to = _filterResults.size(); for (; from < to; ++from) { - // #TODO feeds dialogs - _filterResults[from]->history()->peer->loadUserpic(); + _filterResults[from]->entry()->loadUserpic(); } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 1aec0554b..5ece1e43d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -45,10 +45,10 @@ public: void selectSkip(int32 direction); void selectSkipPage(int32 pixels, int32 direction); - void createDialog(not_null history); + void createDialog(Dialogs::Key key); + void removeDialog(Dialogs::Key key); void dlgUpdated(Dialogs::Mode list, not_null row); void dlgUpdated(not_null history, MsgId msgId); - void removeDialog(not_null history); void dragLeft(); @@ -207,8 +207,8 @@ private: Painter &p, not_null row, int fullWidth, - PeerData *active, - PeerData *selected, + Dialogs::Key active, + Dialogs::Key selected, bool onlyBackground, TimeMs ms); void paintPeerSearchResult( @@ -233,7 +233,7 @@ private: void clearSelection(); void clearSearchResults(bool clearPeerSearchResults = true); - void updateSelectedRow(PeerData *peer = 0); + void updateSelectedRow(Dialogs::Key key = Dialogs::Key()); Dialogs::IndexedList *shownDialogs() const { return (Global::DialogsMode() == Dialogs::Mode::Important) ? _dialogsImportant.get() : _dialogs.get(); @@ -323,7 +323,7 @@ private: UserData *_searchFromUser = nullptr; Text _searchFromUserText; Text _searchInSavedText; - PeerData *_menuPeer = nullptr; + Dialogs::Key _menuKey; base::lambda _loadMoreCallback; diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.cpp b/Telegram/SourceFiles/dialogs/dialogs_key.cpp index 260e1641b..93375427f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_key.cpp @@ -29,24 +29,13 @@ const PeerData::NameFirstChars &Key::nameFirstChars() const { return empty; } -uint64 Key::sortKey() const { - if (const auto h = history()) { - return h->sortKeyInChatList(); - } else if (const auto f = feed()) { - return f->sortKeyInChatList(); - } else { - Unexpected("Key value in Key::sortKey"); - } -} - -void Key::cachePinnedIndex(int index) const { - if (const auto h = history()) { - h->cachePinnedIndex(index); - } else if (const auto f = feed()) { - f->cachePinnedIndex(index); - } else { - Unexpected("Key value in Key::setPinnedIndex"); +not_null Key::entry() const { + if (const auto p = base::get_if>(&_value)) { + return *p; + } else if (const auto p = base::get_if>(&_value)) { + return *p; } + Unexpected("Dialogs entry() call on empty Key."); } History *Key::history() const { diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h index 389ec7a13..202ad1cad 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.h +++ b/Telegram/SourceFiles/dialogs/dialogs_key.h @@ -9,12 +9,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/value_ordering.h" +class History; + namespace Data { class Feed; } // namespace Data namespace Dialogs { +class Entry; + class Key { public: Key() = default; @@ -30,13 +34,12 @@ public: explicit operator bool() const { return !!_value; } + not_null entry() const; History *history() const; Data::Feed *feed() const; const QString &name() const; const PeerData::NameFirstChars &nameFirstChars() const; - uint64 sortKey() const; - void cachePinnedIndex(int index) const; inline bool operator<(const Key &other) const { return _value < other._value; diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp index c18b9c48d..38ce0c7e0 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp @@ -59,10 +59,11 @@ template void paintRow( Painter &p, const RippleRow *row, + not_null entry, History *history, - not_null from, + PeerData *from, HistoryItem *item, - Data::Draft *draft, + const Data::Draft *draft, QDateTime date, int fullWidth, base::flags flags, @@ -94,13 +95,20 @@ void paintRow( st::dialogsPadding.y(), fullWidth, st::dialogsPhotoSize); - } else { + } else if (from) { from->paintUserpicLeft( p, st::dialogsPadding.x(), st::dialogsPadding.y(), fullWidth, st::dialogsPhotoSize); + } else { + entry->paintUserpicLeft( + p, + st::dialogsPadding.x(), + st::dialogsPadding.y(), + fullWidth, + st::dialogsPhotoSize); } auto nameleft = st::dialogsPadding.x() @@ -120,11 +128,12 @@ void paintRow( namewidth, st::msgNameFont->height); - if (auto chatTypeIcon = ChatTypeIcon(from, active, selected)) { - chatTypeIcon->paint(p, rectForName.topLeft(), fullWidth); - rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip); + if (from) { + if (auto chatTypeIcon = ChatTypeIcon(from, active, selected)) { + chatTypeIcon->paint(p, rectForName.topLeft(), fullWidth); + rectForName.setLeft(rectForName.left() + st::dialogsChatTypeSkip); + } } - auto texttop = st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip; @@ -132,7 +141,7 @@ void paintRow( paintRowDate(p, date, rectForName, active, selected); auto availableWidth = namewidth; - if (history->isPinnedDialog()) { + if (entry->isPinnedDialog()) { auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon)); icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth); availableWidth -= icon.width() + st::dialogsUnreadPadding; @@ -140,7 +149,7 @@ void paintRow( p.setFont(st::dialogsTextFont); auto &color = active ? st::dialogsTextFgServiceActive : (selected ? st::dialogsTextFgServiceOver : st::dialogsTextFgService); - if (!history->paintSendAction(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) { + if (history && !history->paintSendAction(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) { if (history->cloudDraftTextCache.isEmpty()) { auto draftWrapped = textcmdLink(1, lng_dialogs_text_from_wrapped(lt_from, lang(lng_from_draft))); auto draftText = lng_dialogs_text_with_from(lt_from_part, draftWrapped, lt_message, TextUtilities::Clean(draft->textWithTags.text)); @@ -153,7 +162,7 @@ void paintRow( } } else if (!item) { auto availableWidth = namewidth; - if (history->isPinnedDialog()) { + if (entry->isPinnedDialog()) { auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon)); icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth); availableWidth -= icon.width() + st::dialogsUnreadPadding; @@ -161,14 +170,14 @@ void paintRow( auto &color = active ? st::dialogsTextFgServiceActive : (selected ? st::dialogsTextFgServiceOver : st::dialogsTextFgService); p.setFont(st::dialogsTextFont); - if (!history->paintSendAction(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) { + if (history && !history->paintSendAction(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) { // Empty history } } else if (!item->isEmpty()) { paintRowDate(p, date, rectForName, active, selected); paintItemCallback(nameleft, namewidth); - } else if (history->isPinnedDialog()) { + } else if (entry->isPinnedDialog()) { auto availableWidth = namewidth; auto &icon = (active ? st::dialogsPinnedIconActive : (selected ? st::dialogsPinnedIconOver : st::dialogsPinnedIcon)); icon.paint(p, fullWidth - st::dialogsPadding.x() - icon.width(), texttop, fullWidth); @@ -209,13 +218,22 @@ void paintRow( text = st::msgNameFont->elided(text, rectForName.width()); } p.drawTextLeft(rectForName.left(), rectForName.top(), fullWidth, text); - } else { + } else if (from) { if (!(flags & Flag::SearchResult) && from->isVerified()) { auto icon = &(active ? st::dialogsVerifiedIconActive : (selected ? st::dialogsVerifiedIconOver : st::dialogsVerifiedIcon)); rectForName.setWidth(rectForName.width() - icon->width()); icon->paint(p, rectForName.topLeft() + QPoint(qMin(from->dialogName().maxWidth(), rectForName.width()), 0), fullWidth); } from->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width()); + } else { + // TODO feeds name + p.setFont(st::msgNameFont); + auto text = QString("Feed"); + auto textWidth = st::msgNameFont->width(text); + if (textWidth > rectForName.width()) { + text = st::msgNameFont->elided(text, rectForName.width()); + } + p.drawTextLeft(rectForName.left(), rectForName.top(), fullWidth, text); } } @@ -328,69 +346,79 @@ void paintUnreadCount(Painter &p, const QString &text, int x, int y, const Unrea void RowPainter::paint( Painter &p, - const Row *row, + not_null row, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms) { - // #TODO feeds show - auto history = row->history(); - auto item = history->lastMsg; - auto cloudDraft = history->cloudDraft(); - if (Data::draftIsNull(cloudDraft)) { - cloudDraft = nullptr; - } - auto displayDate = [item, cloudDraft]() { + const auto entry = row->entry(); + const auto history = row->history(); + const auto peer = history ? history->peer : nullptr; + const auto unreadCount = entry->chatListUnreadCount(); + const auto unreadMuted = entry->chatListMutedBadge(); + const auto item = entry->chatsListItem(); + const auto cloudDraft = [&]() -> const Data::Draft*{ + if (history && (!item || !unreadCount)) { + // Draw item, if there are unread messages. + if (const auto draft = history->cloudDraft()) { + if (!Data::draftIsNull(draft)) { + return draft; + } + } + } + return nullptr; + }(); + const auto displayDate = [item, cloudDraft] { if (item) { if (cloudDraft) { - return (item->date > cloudDraft->date) ? item->date : cloudDraft->date; + return (item->date > cloudDraft->date) + ? item->date + : cloudDraft->date; } return item->date; } return cloudDraft ? cloudDraft->date : QDateTime(); - }; - auto unreadCount = history->unreadCount(); - if (history->peer->migrateFrom()) { - if (auto migrated = App::historyLoaded(history->peer->migrateFrom()->id)) { - unreadCount += migrated->unreadCount(); - } - } + }(); - if (item && cloudDraft && unreadCount > 0) { - cloudDraft = nullptr; // Draw item, if draft is older. - } - auto from = history->peer->migrateTo() - ? history->peer->migrateTo() - : history->peer; + const auto from = history + ? (history->peer->migrateTo() + ? history->peer->migrateTo() + : history->peer) + : nullptr; const auto flags = (active ? Flag::Active : Flag(0)) | (selected ? Flag::Selected : Flag(0)) | (onlyBackground ? Flag::OnlyBackground : Flag(0)) - | (history->peer->isSelf() ? Flag::SavedMessages : Flag(0)); - auto paintItemCallback = [&](int nameleft, int namewidth) { + | (peer && peer->isSelf() ? Flag::SavedMessages : Flag(0)); + const auto paintItemCallback = [&](int nameleft, int namewidth) { auto availableWidth = namewidth; auto texttop = st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip; auto hadOneBadge = false; - auto displayUnreadCounter = (unreadCount != 0); - auto displayMentionBadge = history->hasUnreadMentions(); - auto displayPinnedIcon = !displayUnreadCounter && history->isPinnedDialog(); - if (displayMentionBadge - && unreadCount == 1 - && item - && item->isMediaUnread() - && item->mentionsMe()) { - displayUnreadCounter = false; - } + const auto displayMentionBadge = history + ? history->hasUnreadMentions() + : false; + const auto displayUnreadCounter = [&] { + if (displayMentionBadge + && unreadCount == 1 + && item + && item->isMediaUnread() + && item->mentionsMe()) { + return false; + } + return (unreadCount > 0); + }(); + const auto displayPinnedIcon = !displayUnreadCounter + && !displayMentionBadge + && entry->isPinnedDialog(); if (displayUnreadCounter) { auto counter = QString::number(unreadCount); - auto mutedCounter = history->mute(); auto unreadRight = fullWidth - st::dialogsPadding.x(); auto unreadTop = texttop + st::dialogsTextFont->ascent - st::dialogsUnreadFont->ascent - (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2; auto unreadWidth = 0; UnreadBadgeStyle st; st.active = active; - st.muted = history->mute(); + st.muted = unreadMuted; paintUnreadCount(p, counter, unreadRight, unreadTop, st, &unreadWidth); availableWidth -= unreadWidth + st.padding; @@ -416,19 +444,19 @@ void RowPainter::paint( paintUnreadCount(p, counter, unreadRight, unreadTop, st, &unreadWidth); availableWidth -= unreadWidth + st.padding + (hadOneBadge ? st::dialogsUnreadPadding : 0); } - auto &color = active + const auto &color = active ? st::dialogsTextFgServiceActive : (selected ? st::dialogsTextFgServiceOver : st::dialogsTextFgService); - auto actionWasPainted = history->paintSendAction( + const auto actionWasPainted = history ? history->paintSendAction( p, nameleft, texttop, availableWidth, fullWidth, color, - ms); + ms) : false; if (!actionWasPainted) { auto itemRect = QRect( nameleft, @@ -441,35 +469,35 @@ void RowPainter::paint( active, selected, HistoryItem::DrawInDialog::Normal, - history->textCachedFor, - history->lastItemTextCache); + entry->textCachedFor, + entry->lastItemTextCache); } }; - auto paintCounterCallback = [&] { + const auto paintCounterCallback = [&] { if (unreadCount) { auto counter = QString::number(unreadCount); if (counter.size() > 4) { counter = qsl("..") + counter.mid(counter.size() - 3); } - auto mutedCounter = history->mute(); auto unreadRight = st::dialogsPadding.x() + st::dialogsPhotoSize; auto unreadTop = st::dialogsPadding.y() + st::dialogsPhotoSize - st::dialogsUnreadHeight; auto unreadWidth = 0; UnreadBadgeStyle st; st.active = active; - st.muted = history->mute(); + st.muted = unreadMuted; paintUnreadCount(p, counter, unreadRight, unreadTop, st, &unreadWidth); } }; paintRow( p, row, + entry, history, from, item, cloudDraft, - displayDate(), + displayDate, fullWidth, flags, ms, @@ -479,7 +507,7 @@ void RowPainter::paint( void RowPainter::paint( Painter &p, - const FakeRow *row, + not_null row, int fullWidth, bool active, bool selected, @@ -530,6 +558,7 @@ void RowPainter::paint( p, row, history, + history, from, item, cloudDraft, diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.h b/Telegram/SourceFiles/dialogs/dialogs_layout.h index 7d21ef0fc..c26348440 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.h +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.h @@ -23,7 +23,7 @@ class RowPainter { public: static void paint( Painter &p, - const Row *row, + not_null row, int fullWidth, bool active, bool selected, @@ -31,7 +31,7 @@ public: TimeMs ms); static void paint( Painter &p, - const FakeRow *row, + not_null row, int fullWidth, bool active, bool selected, diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.h b/Telegram/SourceFiles/dialogs/dialogs_row.h index 7831b3887..b7bf6a6f5 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.h +++ b/Telegram/SourceFiles/dialogs/dialogs_row.h @@ -58,6 +58,9 @@ public: Data::Feed *feed() const { return _id.feed(); } + not_null entry() const { + return _id.entry(); + } QString name() const { return _id.name(); } @@ -65,7 +68,7 @@ public: return _pos; } uint64 sortKey() const { - return _id.sortKey(); + return _id.entry()->sortKeyInChatList(); } // for any attached data, for example View in contacts list diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 1dcee945d..5af460888 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -182,11 +182,13 @@ void DialogsWidget::activate() { _inner->activate(); } -void DialogsWidget::createDialog(not_null history) { - auto creating = !history->inChatList(Dialogs::Mode::All); - _inner->createDialog(history); - if (creating && history->peer->migrateFrom()) { - if (const auto migrated = App::historyLoaded(history->peer->migrateFrom())) { +void DialogsWidget::createDialog(Dialogs::Key key) { + const auto creating = !key.entry()->inChatList(Dialogs::Mode::All); + _inner->createDialog(key); + const auto history = key.history(); + if (creating && history && history->peer->migrateFrom()) { + if (const auto migrated = App::historyLoaded( + history->peer->migrateFrom())) { if (migrated->inChatList(Dialogs::Mode::All)) { removeDialog(migrated); } @@ -747,8 +749,7 @@ void DialogsWidget::dragMoveEvent(QDragMoveEvent *e) { } else { _chooseByDragTimer.start(ChoosePeerByDragTimeout); } - PeerData *p = _inner->updateFromParentDrag(mapToGlobal(e->pos())); - if (p) { + if (_inner->updateFromParentDrag(mapToGlobal(e->pos()))) { e->setDropAction(Qt::CopyAction); } else { e->setDropAction(Qt::IgnoreAction); @@ -1118,8 +1119,8 @@ void DialogsWidget::scrollToPeer(not_null history, MsgId msgId) { _inner->scrollToPeer(history, msgId); } -void DialogsWidget::removeDialog(not_null history) { - _inner->removeDialog(history); +void DialogsWidget::removeDialog(Dialogs::Key key) { + _inner->removeDialog(key); onFilterUpdate(); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 562354d9d..8ded93941 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -55,8 +55,8 @@ public: void loadDialogs(); void loadPinnedDialogs(); - void createDialog(not_null history); - void removeDialog(not_null history); + void createDialog(Dialogs::Key key); + void removeDialog(Dialogs::Key key); void dlgUpdated(Dialogs::Mode list, not_null row); void dlgUpdated(not_null history, MsgId msgId); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 23aa030a5..fa08c60a6 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" #include "data/data_channel_admins.h" +#include "data/data_feed.h" #include "ui/text_options.h" #include "core/crash_reports.h" @@ -58,8 +59,8 @@ HistoryItem *createUnsupportedMessage(History *history, MsgId msgId, MTPDmessage } // namespace History::History(const PeerId &peerId) -: peer(App::peer(peerId)) -, lastItemTextCache(st::dialogsTextWidthMin) +: Entry(this) +, peer(App::peer(peerId)) , cloudDraftTextCache(st::dialogsTextWidthMin) , _mute(peer->isMuted()) , _sendActionText(st::dialogsTextWidthMin) { @@ -450,9 +451,14 @@ HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) { } if (item->date <= inviteDate) { ++itemIndex; - _joinedMessage = HistoryJoined::create(this, inviteDate, inviter, flags); + _joinedMessage = HistoryJoined::create( + this, + inviteDate, + inviter, + flags); addNewInTheMiddle(_joinedMessage, blockIndex, itemIndex); - if (lastMsgDate.isNull() || inviteDate >= lastMsgDate) { + const auto lastDate = chatsListDate(); + if (lastDate.isNull() || inviteDate >= lastDate) { setLastMessage(_joinedMessage); if (unread) { newItemAdded(_joinedMessage); @@ -1855,6 +1861,16 @@ void History::getNextShowFrom(HistoryBlock *block, int i) { showFrom = nullptr; } +QDateTime History::adjustChatListDate() const { + const auto result = chatsListDate(); + if (const auto draft = cloudDraft()) { + if (!Data::draftIsNull(draft) && draft->date > result) { + return draft->date; + } + } + return result; +} + void History::countScrollState(int top) { countScrollTopItem(top); if (scrollTopItem) { @@ -2039,6 +2055,36 @@ void History::recountGroupingAround(not_null item) { } } +int History::chatListUnreadCount() const { + const auto result = unreadCount(); + if (const auto from = peer->migrateFrom()) { + if (const auto migrated = App::historyLoaded(from)) { + return result + migrated->unreadCount(); + } + } + return result; +} + +bool History::chatListMutedBadge() const { + return mute(); +} + +HistoryItem *History::chatsListItem() const { + return lastMsg; +} + +void History::loadUserpic() { + peer->loadUserpic(); +} + +void History::paintUserpic( + Painter &p, + int x, + int y, + int size) const { + peer->paintUserpic(p, x, y, size); +} + auto History::recountGroupingFromTill(not_null item) -> std::pair, not_null> { const auto recountFromItem = [&] { @@ -2208,22 +2254,18 @@ uint32 _dialogsPosToTopShift = 0x80000000UL; } // namespace -inline uint64 dialogPosFromDate(const QDateTime &date) { - if (date.isNull()) return 0; - return (uint64(date.toTime_t()) << 32) | (++_dialogsPosToTopShift); -} - -inline uint64 pinnedDialogPos(int pinnedIndex) { - return 0xFFFFFFFF00000000ULL + pinnedIndex; -} - void History::setLastMessage(HistoryItem *msg) { if (msg) { - if (!lastMsg) Local::removeSavedPeer(peer); + if (!lastMsg) { + Local::removeSavedPeer(peer); + } lastMsg = msg; + if (const auto feed = peer->feed()) { + feed->updateLastMessage(msg); + } setChatsListDate(msg->date); - } else { - lastMsg = 0; + } else if (lastMsg) { + lastMsg = nullptr; updateChatListEntry(); } } @@ -2235,43 +2277,10 @@ bool History::needUpdateInChatList() const { return false; } else if (isPinnedDialog()) { return true; + } else if (const auto channel = peer->asChannel()) { + return !channel->feed() && channel->amIn(); } - return !peer->isChannel() || peer->asChannel()->amIn(); -} - -void History::setChatsListDate(const QDateTime &date) { - if (!lastMsgDate.isNull() && lastMsgDate >= date) { - if (!needUpdateInChatList() || !inChatList(Dialogs::Mode::All)) { - return; - } - } - lastMsgDate = date; - updateChatListSortPosition(); -} - -void History::updateChatListSortPosition() { - auto chatListDate = [this]() { - if (auto draft = cloudDraft()) { - if (!Data::draftIsNull(draft) && draft->date > lastMsgDate) { - return draft->date; - } - } - return lastMsgDate; - }; - - _sortKeyInChatList = isPinnedDialog() - ? pinnedDialogPos(_pinnedIndex) - : dialogPosFromDate(chatListDate()); - if (auto m = App::main()) { - if (needUpdateInChatList()) { - if (_sortKeyInChatList) { - m->createDialog(this); - updateChatListEntry(); - } else { - m->deleteConversation(peer, false); - } - } - } + return true; } void History::fixLastMessage(bool wasAtBottom) { @@ -2406,6 +2415,10 @@ void History::clear(bool leaveItems) { setUnreadCount(0); if (auto channel = peer->asChannel()) { channel->clearPinnedMessage(); + if (const auto feed = channel->feed()) { + // Should be after setLastMessage(nullptr); + feed->historyCleared(this); + } } clearLastKeyboard(); } @@ -2481,84 +2494,24 @@ void History::clearOnDestroy() { clearBlocks(false); } -History::PositionInChatListChange History::adjustByPosInChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed) { - Assert(indexed != nullptr); - Dialogs::Row *lnk = mainChatListLink(list); - int32 movedFrom = lnk->pos(); - indexed->adjustByPos(chatListLinks(list)); - int32 movedTo = lnk->pos(); - return { movedFrom, movedTo }; -} - -int History::posInChatList(Dialogs::Mode list) const { - return mainChatListLink(list)->pos(); -} - -Dialogs::Row *History::addToChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed) { - Assert(indexed != nullptr); - if (!inChatList(list)) { - chatListLinks(list) = indexed->addToEnd(this); - if (list == Dialogs::Mode::All && unreadCount()) { - App::histories().unreadIncrement(unreadCount(), mute()); - Notify::unreadCounterUpdated(); - } - } - return mainChatListLink(list); -} - -void History::removeFromChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed) { - Assert(indexed != nullptr); - if (inChatList(list)) { - indexed->del(this); - chatListLinks(list).clear(); - if (list == Dialogs::Mode::All && unreadCount()) { - App::histories().unreadIncrement(-unreadCount(), mute()); - Notify::unreadCounterUpdated(); - } +void History::removeDialog() { + if (const auto main = App::main()) { + main->deleteConversation(peer, false); } } -void History::removeChatListEntryByLetter(Dialogs::Mode list, QChar letter) { - Assert(letter != 0); - if (inChatList(list)) { - chatListLinks(list).remove(letter); +void History::changedInChatListHook(Dialogs::Mode list, bool added) { + if (list == Dialogs::Mode::All && unreadCount()) { + const auto delta = added ? unreadCount() : -unreadCount(); + App::histories().unreadIncrement(delta, mute()); + Notify::unreadCounterUpdated(); } } -void History::addChatListEntryByLetter(Dialogs::Mode list, QChar letter, Dialogs::Row *row) { - Assert(letter != 0); - if (inChatList(list)) { - chatListLinks(list).emplace(letter, row); - } -} - -void History::updateChatListEntry() const { - if (auto main = App::main()) { - if (inChatList(Dialogs::Mode::All)) { - main->dlgUpdated( - Dialogs::Mode::All, - mainChatListLink(Dialogs::Mode::All)); - if (inChatList(Dialogs::Mode::Important)) { - main->dlgUpdated( - Dialogs::Mode::Important, - mainChatListLink(Dialogs::Mode::Important)); - } - } - } -} - -void History::cachePinnedIndex(int pinnedIndex) { - if (_pinnedIndex != pinnedIndex) { - auto wasPinned = isPinnedDialog(); - _pinnedIndex = pinnedIndex; - updateChatListSortPosition(); - updateChatListEntry(); - if (wasPinned != isPinnedDialog()) { - Notify::peerUpdatedDelayed( - peer, - Notify::PeerUpdate::Flag::PinnedChanged); - } - } +void History::changedChatListPinHook() { + Notify::peerUpdatedDelayed( + peer, + Notify::PeerUpdate::Flag::PinnedChanged); } void History::changeMsgId(MsgId oldId, MsgId newId) { diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index ac20eae05..c6b03dd41 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_types.h" #include "data/data_peer.h" -#include "dialogs/dialogs_common.h" +#include "dialogs/dialogs_entry.h" #include "ui/effects/send_action_animations.h" #include "base/observer.h" #include "base/timer.h" @@ -162,7 +162,7 @@ class IndexedList; } // namespace Dialogs class ChannelHistory; -class History { +class History : public Dialogs::Entry { public: History(const PeerId &peerId); History(const History &) = delete; @@ -244,32 +244,6 @@ public: void setLastMessage(HistoryItem *msg); void fixLastMessage(bool wasAtBottom); - bool needUpdateInChatList() const; - void updateChatListSortPosition(); - void setChatsListDate(const QDateTime &date); - uint64 sortKeyInChatList() const { - return _sortKeyInChatList; - } - struct PositionInChatListChange { - int movedFrom; - int movedTo; - }; - PositionInChatListChange adjustByPosInChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed); - bool inChatList(Dialogs::Mode list) const { - return !chatListLinks(list).empty(); - } - int posInChatList(Dialogs::Mode list) const; - Dialogs::Row *addToChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed); - void removeFromChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed); - void removeChatListEntryByLetter(Dialogs::Mode list, QChar letter); - void addChatListEntryByLetter(Dialogs::Mode list, QChar letter, Dialogs::Row *row); - void updateChatListEntry() const; - - bool isPinnedDialog() const { - return (_pinnedIndex > 0); - } - void cachePinnedIndex(int newPinnedIndex); - MsgId minMsgId() const; MsgId maxMsgId() const; MsgId msgIdForRead() const; @@ -362,18 +336,17 @@ public: bool newLoaded = true; HistoryItem *lastMsg = nullptr; HistoryItem *lastSentMsg = nullptr; - QDateTime lastMsgDate; typedef QList NotifyQueue; NotifyQueue notifies; - Data::Draft *localDraft() { + Data::Draft *localDraft() const { return _localDraft.get(); } - Data::Draft *cloudDraft() { + Data::Draft *cloudDraft() const { return _cloudDraft.get(); } - Data::Draft *editDraft() { + Data::Draft *editDraft() const { return _editDraft.get(); } void setLocalDraft(std::unique_ptr &&draft); @@ -397,6 +370,20 @@ public: void setForwardDraft(MessageIdsList &&items); void recountGroupingAround(not_null item); + bool needUpdateInChatList() const override; + bool toImportant() const override { + return !mute(); + } + int chatListUnreadCount() const override; + bool chatListMutedBadge() const override; + HistoryItem *chatsListItem() const override; + void loadUserpic() override; + void paintUserpic( + Painter &p, + int x, + int y, + int size) const override; + // some fields below are a property of a currently displayed instance of this // conversation history not a property of the conversation history itself public: @@ -436,9 +423,6 @@ public: mtpRequestId sendRequestId = 0; - mutable const HistoryItem *textCachedFor = nullptr; // cache - mutable Text lastItemTextCache; - void changeMsgId(MsgId oldId, MsgId newId); Text cloudDraftTextCache; @@ -482,6 +466,11 @@ protected: } private: + QDateTime adjustChatListDate() const override; + void removeDialog() override; + void changedInChatListHook(Dialogs::Mode list, bool added) override; + void changedChatListPinHook() override; + // After adding a new history slice check the lastMsg and newLoaded. void checkLastMsg(); @@ -520,20 +509,6 @@ private: base::optional _unreadMentionsCount; base::flat_set _unreadMentions; - Dialogs::RowsByLetter _chatListLinks[2]; - Dialogs::RowsByLetter &chatListLinks(Dialogs::Mode list) { - return _chatListLinks[static_cast(list)]; - } - const Dialogs::RowsByLetter &chatListLinks(Dialogs::Mode list) const { - return _chatListLinks[static_cast(list)]; - } - Dialogs::Row *mainChatListLink(Dialogs::Mode list) const { - auto it = chatListLinks(list).find(0); - Assert(it != chatListLinks(list).cend()); - return it->second; - } - uint64 _sortKeyInChatList = 0; // like ((unixtime) << 32) | (incremented counter) - // A pointer to the block that is currently being built. // We hold this pointer so we can destroy it while building // and then create a new one if it is necessary. diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index dd6089f5a..fb77fea2d 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_controller.h" #include "core/crash_reports.h" #include "data/data_session.h" +#include "data/data_feed.h" namespace { @@ -164,7 +165,7 @@ ReplyKeyboard *HistoryItem::inlineReplyKeyboard() { void HistoryItem::invalidateChatsListEntry() { if (App::main()) { - // #TODO feeds dialogs + // #TODO feeds search results App::main()->dlgUpdated(history(), id); } @@ -172,6 +173,12 @@ void HistoryItem::invalidateChatsListEntry() { if (history()->textCachedFor == this) { history()->textCachedFor = nullptr; } + if (const auto feed = history()->peer->feed()) { + if (feed->textCachedFor == this) { + feed->textCachedFor = nullptr; + feed->updateChatListEntry(); + } + } } void HistoryItem::finishEditionToEmpty() { @@ -280,6 +287,7 @@ UserData *HistoryItem::viaBot() const { } void HistoryItem::destroy() { + const auto history = this->history(); if (isLogEntry()) { Assert(detached()); } else { @@ -288,7 +296,7 @@ void HistoryItem::destroy() { if (IsServerMsgId(id)) { if (const auto types = sharedMediaTypes()) { Auth().storage().remove(Storage::SharedMediaRemoveOne( - history()->peer->id, + history->peer->id, types, id)); } @@ -296,22 +304,30 @@ void HistoryItem::destroy() { Auth().api().cancelLocalItem(this); } - auto wasAtBottom = history()->loadedAtBottom(); - _history->removeNotification(this); + const auto wasAtBottom = history->loadedAtBottom(); + history->removeNotification(this); + detach(); - if (const auto channel = history()->peer->asChannel()) { + + if (history->lastMsg == this) { + history->fixLastMessage(wasAtBottom); + } + if (history->lastKeyboardId == id) { + history->clearLastKeyboard(); + } + if ((!out() || isPost()) && unread() && history->unreadCount() > 0) { + history->setUnreadCount(history->unreadCount() - 1); + } + if (const auto channel = history->peer->asChannel()) { if (channel->pinnedMessageId() == id) { channel->clearPinnedMessage(); } - } - if (history()->lastMsg == this) { - history()->fixLastMessage(wasAtBottom); - } - if (history()->lastKeyboardId == id) { - history()->clearLastKeyboard(); - } - if ((!out() || isPost()) && unread() && history()->unreadCount() > 0) { - history()->setUnreadCount(history()->unreadCount() - 1); + if (const auto feed = channel->feed()) { + // Must be after histroy->lastMsg is cleared. + // Otherwise feed last message will be this value again. + feed->messageRemoved(this); + // #TODO feeds unread + } } } Global::RefPendingRepaintItems().remove(this); diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index 9d79de344..e3a205e98 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_media_types.h" #include "history/history_message.h" #include "history/history_item_components.h" +#include "data/data_feed.h" #include "auth_session.h" #include "window/notifications_manager.h" #include "storage/storage_shared_media.h" @@ -774,8 +775,14 @@ void HistoryService::updateDependentText() { if (history()->textCachedFor == this) { history()->textCachedFor = nullptr; } + if (const auto feed = history()->peer->feed()) { + if (feed->textCachedFor == this) { + feed->textCachedFor = nullptr; + feed->updateChatListEntry(); + } + } if (App::main()) { - // #TODO feeds dialogs + // #TODO feeds search results App::main()->dlgUpdated(history(), id); } App::historyUpdateDependent(this); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 092afec8c..c587f80b2 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1091,8 +1091,8 @@ void MainWidget::deletedContact(UserData *user, const MTPcontacts_Link &result) App::feedUserLink(MTP_int(peerToUser(user->id)), d.vmy_link, d.vforeign_link); } -void MainWidget::removeDialog(not_null history) { - _dialogs->removeDialog(history); +void MainWidget::removeDialog(Dialogs::Key key) { + _dialogs->removeDialog(key); } void MainWidget::deleteConversation(PeerData *peer, bool deleteHistory) { @@ -1306,31 +1306,36 @@ void MainWidget::checkedHistory(PeerData *peer, const MTPmessages_Messages &resu if (!v || v->isEmpty()) { if (peer->isChat() && !peer->asChat()->haveLeft()) { - auto h = App::historyLoaded(peer->id); - if (h) Local::addSavedPeer(peer, h->lastMsgDate); - } else if (peer->isChannel()) { - if (peer->asChannel()->inviter > 0 && peer->asChannel()->amIn()) { - if (auto from = App::userLoaded(peer->asChannel()->inviter)) { - auto h = App::history(peer->id); - h->clear(true); - h->addNewerSlice(QVector()); - h->asChannelHistory()->insertJoinedMessage(true); - _history->peerMessagesUpdated(h->peer->id); + if (const auto history = App::historyLoaded(peer->id)) { + Local::addSavedPeer(peer, history->chatsListDate()); + } + } else if (const auto channel = peer->asChannel()) { + if (channel->inviter > 0 && channel->amIn()) { + if (const auto from = App::userLoaded(channel->inviter)) { + const auto history = App::history(peer->id); + history->clear(true); + history->addNewerSlice(QVector()); + history->asChannelHistory()->insertJoinedMessage(true); + _history->peerMessagesUpdated(peer->id); } } } else { deleteConversation(peer, false); } } else { - auto h = App::history(peer->id); - if (!h->lastMsg) { - h->addNewMessage((*v)[0], NewMessageLast); + const auto history = App::history(peer->id); + if (!history->lastMsg) { + history->addNewMessage((*v)[0], NewMessageLast); } - if (!h->lastMsgDate.isNull() && h->loadedAtBottom()) { - if (peer->isChannel() && peer->asChannel()->inviter > 0 && h->lastMsgDate <= peer->asChannel()->inviteDate && peer->asChannel()->amIn()) { - if (auto from = App::userLoaded(peer->asChannel()->inviter)) { - h->asChannelHistory()->insertJoinedMessage(true); - _history->peerMessagesUpdated(h->peer->id); + if (!history->chatsListDate().isNull() && history->loadedAtBottom()) { + if (const auto channel = peer->asChannel()) { + if (channel->inviter > 0 + && history->chatsListDate() <= channel->inviteDate + && channel->amIn()) { + if (const auto from = App::userLoaded(channel->inviter)) { + history->asChannelHistory()->insertJoinedMessage(true); + _history->peerMessagesUpdated(peer->id); + } } } } @@ -2200,8 +2205,8 @@ bool MainWidget::viewsIncrementFail(const RPCError &error, mtpRequestId req) { return false; } -void MainWidget::createDialog(not_null history) { - _dialogs->createDialog(history); +void MainWidget::createDialog(Dialogs::Key key) { + _dialogs->createDialog(key); } void MainWidget::choosePeer(PeerId peerId, MsgId showAtMsgId) { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 3782373ca..8aaced81f 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -106,8 +106,8 @@ public: void activate(); - void createDialog(not_null history); - void removeDialog(not_null history); + void createDialog(Dialogs::Key key); + void removeDialog(Dialogs::Key key); void dlgUpdated(Dialogs::Mode list, not_null row); void dlgUpdated(not_null history, MsgId msgId); diff --git a/Telegram/SourceFiles/window/window_controller.cpp b/Telegram/SourceFiles/window/window_controller.cpp index e7e8f934d..2a85d2fad 100644 --- a/Telegram/SourceFiles/window/window_controller.cpp +++ b/Telegram/SourceFiles/window/window_controller.cpp @@ -261,8 +261,8 @@ void Controller::showJumpToDate(not_null peer, QDate requestedDate) { return history->blocks.front()->items.front()->date.date(); } } - } else if (!history->lastMsgDate.isNull()) { - return history->lastMsgDate.date(); + } else if (!history->chatsListDate().isNull()) { + return history->chatsListDate().date(); } } return QDate::currentDate(); @@ -272,8 +272,8 @@ void Controller::showJumpToDate(not_null peer, QDate requestedDate) { peer = channel; } if (auto history = App::historyLoaded(peer)) { - if (!history->lastMsgDate.isNull()) { - return history->lastMsgDate.date(); + if (!history->chatsListDate().isNull()) { + return history->chatsListDate().date(); } } return QDate::currentDate(); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 347de8a76..557513232 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -25,18 +25,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_boxes.h" #include "window/window_controller.h" #include "data/data_session.h" +#include "data/data_feed.h" +#include "dialogs/dialogs_key.h" namespace Window { namespace { -void AddChatMembers(not_null chat) { - if (chat->count >= Global::ChatSizeMax() && chat->amCreator()) { - Ui::show(Box(chat)); - } else { - AddParticipantsBoxController::Start(chat); - } -} - class Filler { public: Filler( @@ -64,6 +58,25 @@ private: }; +class FeedFiller { +public: + FeedFiller( + not_null controller, + not_null feed, + const PeerMenuCallback &addAction, + PeerMenuSource source); + void fill(); + +private: + void addPinToggle(); + + not_null _controller; + not_null _feed; + const PeerMenuCallback &_addAction; + PeerMenuSource _source; + +}; + History *FindWastedPin() { const auto &order = Auth().data().pinnedDialogsOrder(); for (const auto pinned : order) { @@ -78,6 +91,58 @@ History *FindWastedPin() { return nullptr; } +void AddChatMembers(not_null chat) { + if (chat->count >= Global::ChatSizeMax() && chat->amCreator()) { + Ui::show(Box(chat)); + } else { + AddParticipantsBoxController::Start(chat); + } +} + +bool PinnedLimitReached(Dialogs::Key key) { + const auto pinnedCount = Auth().data().pinnedDialogsCount(); + const auto pinnedMax = Global::PinnedDialogsCountMax(); + if (pinnedCount < pinnedMax) { + return false; + } + // Some old chat, that was converted, maybe is still pinned. + if (auto wasted = FindWastedPin()) { + Auth().data().setPinnedDialog(wasted, false); + Auth().data().setPinnedDialog(key, true); + Auth().api().savePinnedOrder(); + } else { + auto errorText = lng_error_pinned_max( + lt_count, + pinnedMax); + Ui::show(Box(errorText)); + } + return true; +} + +void TogglePinnedDialog(Dialogs::Key key) { + const auto isPinned = !key.entry()->isPinnedDialog(); + if (isPinned && PinnedLimitReached(key)) { + return; + } + + Auth().data().setPinnedDialog(key, isPinned); + auto flags = MTPmessages_ToggleDialogPin::Flags(0); + if (isPinned) { + flags |= MTPmessages_ToggleDialogPin::Flag::f_pinned; + } + MTP::send(MTPmessages_ToggleDialogPin( + MTP_flags(flags), + key.history() + ? MTP_inputDialogPeer(key.history()->peer->input) + : MTP_inputDialogPeerFeed(MTP_int(key.feed()->id())))); + if (isPinned) { + if (const auto main = App::main()) { + main->dialogsToUp(); + } + } + +} + Filler::Filler( not_null controller, not_null peer, @@ -115,40 +180,8 @@ void Filler::addPinToggle() { ? lng_context_unpin_from_top : lng_context_pin_to_top); }; - auto pinToggle = [peer] { - auto history = App::history(peer); - auto isPinned = !history->isPinnedDialog(); - const auto pinnedCount = Auth().data().pinnedDialogsCount(); - const auto pinnedMax = Global::PinnedDialogsCountMax(); - if (isPinned && pinnedCount >= pinnedMax) { - // Some old chat, that was converted to supergroup, maybe is still pinned. - if (auto wasted = FindWastedPin()) { - Auth().data().setPinnedDialog(wasted, false); - Auth().data().setPinnedDialog(history, true); - Auth().api().savePinnedOrder(); - } else { - auto errorText = lng_error_pinned_max( - lt_count, - pinnedMax); - Ui::show(Box(errorText)); - } - return; - } - - Auth().data().setPinnedDialog(history, isPinned); - auto flags = MTPmessages_ToggleDialogPin::Flags(0); - if (isPinned) { - flags |= MTPmessages_ToggleDialogPin::Flag::f_pinned; - } - MTP::send( - MTPmessages_ToggleDialogPin( - MTP_flags(flags), - MTP_inputDialogPeer(peer->input))); - if (isPinned) { - if (auto main = App::main()) { - main->dialogsToUp(); - } - } + auto pinToggle = [=] { + TogglePinnedDialog(App::history(peer)); }; auto pinAction = _addAction(pinText(isPinned), pinToggle); @@ -382,6 +415,37 @@ void Filler::fill() { } } +FeedFiller::FeedFiller( + not_null controller, + not_null feed, + const PeerMenuCallback &addAction, + PeerMenuSource source) + : _controller(controller) + , _feed(feed) + , _addAction(addAction) + , _source(source) { +} + +void FeedFiller::fill() { + if (_source == PeerMenuSource::ChatsList) { + addPinToggle(); + } +} + +void FeedFiller::addPinToggle() { + auto feed = _feed; + auto isPinned = feed->isPinnedDialog(); + auto pinText = [](bool isPinned) { + return lang(isPinned + ? lng_context_unpin_from_top + : lng_context_pin_to_top); + }; + auto pinToggle = [=] { + TogglePinnedDialog(feed); + }; + _addAction(pinText(isPinned), pinToggle); +} + } // namespace void PeerMenuDeleteContact(not_null user) { @@ -599,4 +663,14 @@ void FillPeerMenu( filler.fill(); } +void FillFeedMenu( + not_null controller, + not_null feed, + const PeerMenuCallback &callback, + PeerMenuSource source) { + // TODO feeds context menu + FeedFiller filler(controller, feed, callback, source); + filler.fill(); +} + } // namespace Window diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index a31d2b48a..2a9a979fa 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -11,6 +11,10 @@ namespace Ui { class RpWidget; } // namespace Ui +namespace Data { +class Feed; +} // namespace Data + namespace Window { class Controller; @@ -30,6 +34,11 @@ void FillPeerMenu( not_null peer, const PeerMenuCallback &addAction, PeerMenuSource source); +void FillFeedMenu( + not_null controller, + not_null feed, + const PeerMenuCallback &addAction, + PeerMenuSource source); void PeerMenuDeleteContact(not_null user); void PeerMenuShareContactBox(not_null user); diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 410429230..c1eea427d 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -193,7 +193,8 @@ <(src_loc)/data/data_user_photos.h <(src_loc)/data/data_web_page.cpp <(src_loc)/data/data_web_page.h -<(src_loc)/dialogs/dialogs_common.h +<(src_loc)/dialogs/dialogs_entry.cpp +<(src_loc)/dialogs/dialogs_entry.h <(src_loc)/dialogs/dialogs_indexed_list.cpp <(src_loc)/dialogs/dialogs_indexed_list.h <(src_loc)/dialogs/dialogs_inner_widget.cpp