dialogs_entry.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "dialogs/dialogs_entry.h"
  8. #include "dialogs/dialogs_key.h"
  9. #include "dialogs/dialogs_indexed_list.h"
  10. #include "data/data_changes.h"
  11. #include "data/data_session.h"
  12. #include "data/data_folder.h"
  13. #include "data/data_forum_topic.h"
  14. #include "data/data_chat_filters.h"
  15. #include "data/data_saved_sublist.h"
  16. #include "core/application.h"
  17. #include "core/core_settings.h"
  18. #include "mainwidget.h"
  19. #include "main/main_session.h"
  20. #include "main/main_session_settings.h"
  21. #include "ui/text/text_options.h"
  22. #include "ui/ui_utility.h"
  23. #include "history/history.h"
  24. #include "history/history_item.h"
  25. #include "styles/style_dialogs.h" // st::dialogsTextWidthMin
  26. namespace Dialogs {
  27. namespace {
  28. auto DialogsPosToTopShift = 0;
  29. uint64 DialogPosFromDate(TimeId date) {
  30. if (!date) {
  31. return 0;
  32. }
  33. return (uint64(date) << 32) | (++DialogsPosToTopShift);
  34. }
  35. uint64 FixedOnTopDialogPos(int index) {
  36. return 0xFFFFFFFFFFFF000FULL - index;
  37. }
  38. uint64 PinnedDialogPos(int pinnedIndex) {
  39. return 0xFFFFFFFF000000FFULL - pinnedIndex;
  40. }
  41. } // namespace
  42. BadgesState BadgesForUnread(
  43. const UnreadState &state,
  44. CountInBadge count,
  45. IncludeInBadge include) {
  46. const auto countMessages = (count == CountInBadge::Messages)
  47. || ((count == CountInBadge::Default)
  48. && Core::App().settings().countUnreadMessages());
  49. const auto counterFull = state.marks
  50. + (countMessages ? state.messages : state.chats);
  51. const auto counterMuted = state.marksMuted
  52. + (countMessages ? state.messagesMuted : state.chatsMuted);
  53. const auto unreadMuted = (counterFull <= counterMuted);
  54. const auto includeMuted = (include == IncludeInBadge::All)
  55. || (include == IncludeInBadge::UnmutedOrAll && unreadMuted)
  56. || ((include == IncludeInBadge::Default)
  57. && Core::App().settings().includeMutedCounter());
  58. const auto marks = state.marks - (includeMuted ? 0 : state.marksMuted);
  59. const auto counter = counterFull - (includeMuted ? 0 : counterMuted);
  60. const auto mark = (counter == 1) && (marks == 1);
  61. return {
  62. .unreadCounter = mark ? 0 : counter,
  63. .unread = (counter > 0),
  64. .unreadMuted = includeMuted && (counter <= counterMuted),
  65. .mention = (state.mentions > 0),
  66. .reaction = (state.reactions > 0),
  67. .reactionMuted = (state.reactions <= state.reactionsMuted),
  68. };
  69. }
  70. Entry::Entry(not_null<Data::Session*> owner, Type type)
  71. : _owner(owner)
  72. , _flags((type == Type::History)
  73. ? (Flag::IsThread | Flag::IsHistory)
  74. : (type == Type::ForumTopic)
  75. ? Flag::IsThread
  76. : (type == Type::SavedSublist)
  77. ? Flag::IsSavedSublist
  78. : Flag(0)) {
  79. }
  80. Entry::~Entry() = default;
  81. Data::Session &Entry::owner() const {
  82. return *_owner;
  83. }
  84. Main::Session &Entry::session() const {
  85. return _owner->session();
  86. }
  87. History *Entry::asHistory() {
  88. return (_flags & Flag::IsHistory)
  89. ? static_cast<History*>(this)
  90. : nullptr;
  91. }
  92. Data::Forum *Entry::asForum() {
  93. return (_flags & Flag::IsHistory)
  94. ? static_cast<History*>(this)->peer->forum()
  95. : nullptr;
  96. }
  97. Data::Folder *Entry::asFolder() {
  98. return (_flags & (Flag::IsThread | Flag::IsSavedSublist))
  99. ? nullptr
  100. : static_cast<Data::Folder*>(this);
  101. }
  102. Data::Thread *Entry::asThread() {
  103. return (_flags & Flag::IsThread)
  104. ? static_cast<Data::Thread*>(this)
  105. : nullptr;
  106. }
  107. Data::ForumTopic *Entry::asTopic() {
  108. return ((_flags & Flag::IsThread) && !(_flags & Flag::IsHistory))
  109. ? static_cast<Data::ForumTopic*>(this)
  110. : nullptr;
  111. }
  112. Data::SavedSublist *Entry::asSublist() {
  113. return (_flags & Flag::IsSavedSublist)
  114. ? static_cast<Data::SavedSublist*>(this)
  115. : nullptr;
  116. }
  117. const History *Entry::asHistory() const {
  118. return const_cast<Entry*>(this)->asHistory();
  119. }
  120. const Data::Forum *Entry::asForum() const {
  121. return const_cast<Entry*>(this)->asForum();
  122. }
  123. const Data::Folder *Entry::asFolder() const {
  124. return const_cast<Entry*>(this)->asFolder();
  125. }
  126. const Data::Thread *Entry::asThread() const {
  127. return const_cast<Entry*>(this)->asThread();
  128. }
  129. const Data::ForumTopic *Entry::asTopic() const {
  130. return const_cast<Entry*>(this)->asTopic();
  131. }
  132. const Data::SavedSublist *Entry::asSublist() const {
  133. return const_cast<Entry*>(this)->asSublist();
  134. }
  135. void Entry::pinnedIndexChanged(FilterId filterId, int was, int now) {
  136. if (!filterId && session().supportMode()) {
  137. // Force reorder in support mode.
  138. _sortKeyInChatList = 0;
  139. }
  140. updateChatListSortPosition();
  141. updateChatListEntry();
  142. if ((was != 0) != (now != 0)) {
  143. changedChatListPinHook();
  144. }
  145. }
  146. void Entry::cachePinnedIndex(FilterId filterId, int index) {
  147. const auto i = _pinnedIndex.find(filterId);
  148. const auto was = (i != end(_pinnedIndex)) ? i->second : 0;
  149. if (index == was) {
  150. return;
  151. }
  152. if (!index) {
  153. _pinnedIndex.erase(i);
  154. } else if (!was) {
  155. _pinnedIndex.emplace(filterId, index);
  156. } else {
  157. i->second = index;
  158. }
  159. pinnedIndexChanged(filterId, was, index);
  160. }
  161. bool Entry::needUpdateInChatList() const {
  162. return inChatList() || shouldBeInChatList();
  163. }
  164. void Entry::updateChatListSortPosition() {
  165. if (session().supportMode()
  166. && _sortKeyInChatList != 0
  167. && session().settings().supportFixChatsOrder()) {
  168. updateChatListEntry();
  169. return;
  170. }
  171. _sortKeyByDate = DialogPosFromDate(adjustedChatListTimeId());
  172. const auto fixedIndex = fixedOnTopIndex();
  173. _sortKeyInChatList = fixedIndex
  174. ? FixedOnTopDialogPos(fixedIndex)
  175. : computeSortPosition(0);
  176. if (needUpdateInChatList()) {
  177. setChatListExistence(true);
  178. } else {
  179. _sortKeyInChatList = _sortKeyByDate = 0;
  180. }
  181. }
  182. int Entry::lookupPinnedIndex(FilterId filterId) const {
  183. if (filterId) {
  184. const auto i = _pinnedIndex.find(filterId);
  185. return (i != end(_pinnedIndex)) ? i->second : 0;
  186. } else if (!_pinnedIndex.empty()) {
  187. return _pinnedIndex.front().first
  188. ? 0
  189. : _pinnedIndex.front().second;
  190. }
  191. return 0;
  192. }
  193. uint64 Entry::computeSortPosition(FilterId filterId) const {
  194. const auto index = lookupPinnedIndex(filterId);
  195. return index ? PinnedDialogPos(index) : _sortKeyByDate;
  196. }
  197. void Entry::updateChatListExistence() {
  198. setChatListExistence(shouldBeInChatList());
  199. }
  200. void Entry::notifyUnreadStateChange(const UnreadState &wasState) {
  201. Expects(folderKnown());
  202. Expects(inChatList());
  203. const auto nowState = chatListUnreadState();
  204. owner().chatsListFor(this)->unreadStateChanged(wasState, nowState);
  205. auto &filters = owner().chatsFilters();
  206. for (const auto &[filterId, links] : _chatListLinks) {
  207. if (filterId) {
  208. filters.chatsList(filterId)->unreadStateChanged(
  209. wasState,
  210. nowState);
  211. }
  212. }
  213. if (const auto history = asHistory()) {
  214. session().changes().historyUpdated(
  215. history,
  216. Data::HistoryUpdate::Flag::UnreadView);
  217. const auto isForFilters = [](UnreadState state) {
  218. return state.messages || state.marks || state.mentions;
  219. };
  220. if (isForFilters(wasState) != isForFilters(nowState)) {
  221. const auto wasTags = _tagColors.size();
  222. owner().chatsFilters().refreshHistory(history);
  223. // Hack for History::fakeUnreadWhileOpened().
  224. if (!isForFilters(nowState)
  225. && (wasTags > 0)
  226. && (wasTags == _tagColors.size())) {
  227. auto updateRequested = false;
  228. for (const auto &filter : filters.list()) {
  229. if (!(filter.flags() & Data::ChatFilter::Flag::NoRead)
  230. || !_chatListLinks.contains(filter.id())
  231. || filter.contains(history, true)) {
  232. continue;
  233. }
  234. const auto wasTagsCount = _tagColors.size();
  235. setColorIndexForFilterId(filter.id(), std::nullopt);
  236. updateRequested |= (wasTagsCount != _tagColors.size());
  237. }
  238. if (updateRequested) {
  239. updateChatListEntryHeight();
  240. session().changes().peerUpdated(
  241. history->peer,
  242. Data::PeerUpdate::Flag::Name);
  243. }
  244. }
  245. }
  246. }
  247. updateChatListEntryPostponed();
  248. }
  249. const Ui::Text::String &Entry::chatListNameText() const {
  250. const auto version = chatListNameVersion();
  251. if (_chatListNameVersion < version) {
  252. _chatListNameVersion = version;
  253. _chatListNameText.setText(
  254. st::semiboldTextStyle,
  255. chatListName(),
  256. Ui::NameTextOptions());
  257. }
  258. return _chatListNameText;
  259. }
  260. void Entry::setChatListExistence(bool exists) {
  261. if (exists && _sortKeyInChatList) {
  262. owner().refreshChatListEntry(this);
  263. updateChatListEntry();
  264. } else {
  265. owner().removeChatListEntry(this);
  266. }
  267. }
  268. TimeId Entry::adjustedChatListTimeId() const {
  269. return chatListTimeId();
  270. }
  271. void Entry::changedChatListPinHook() {
  272. }
  273. RowsByLetter *Entry::chatListLinks(FilterId filterId) {
  274. const auto i = _chatListLinks.find(filterId);
  275. return (i != end(_chatListLinks)) ? &i->second : nullptr;
  276. }
  277. const RowsByLetter *Entry::chatListLinks(FilterId filterId) const {
  278. const auto i = _chatListLinks.find(filterId);
  279. return (i != end(_chatListLinks)) ? &i->second : nullptr;
  280. }
  281. not_null<Row*> Entry::mainChatListLink(FilterId filterId) const {
  282. const auto links = chatListLinks(filterId);
  283. Assert(links != nullptr);
  284. return links->main;
  285. }
  286. Row *Entry::maybeMainChatListLink(FilterId filterId) const {
  287. const auto links = chatListLinks(filterId);
  288. return links ? links->main.get() : nullptr;
  289. }
  290. PositionChange Entry::adjustByPosInChatList(
  291. FilterId filterId,
  292. not_null<MainList*> list) {
  293. const auto links = chatListLinks(filterId);
  294. Assert(links != nullptr);
  295. const auto from = links->main->top();
  296. list->indexed()->adjustByDate(*links);
  297. const auto to = links->main->top();
  298. return { .from = from, .to = to, .height = links->main->height() };
  299. }
  300. void Entry::setChatListTimeId(TimeId date) {
  301. _timeId = date;
  302. updateChatListSortPosition();
  303. if (const auto folder = this->folder()) {
  304. folder->updateChatListSortPosition();
  305. }
  306. }
  307. int Entry::posInChatList(FilterId filterId) const {
  308. return mainChatListLink(filterId)->index();
  309. }
  310. void Entry::setColorIndexForFilterId(
  311. FilterId filterId,
  312. std::optional<uint8> colorIndex) {
  313. if (!filterId) {
  314. return;
  315. }
  316. if (colorIndex) {
  317. _tagColors[filterId] = *colorIndex;
  318. } else {
  319. _tagColors.remove(filterId);
  320. }
  321. }
  322. not_null<Row*> Entry::addToChatList(
  323. FilterId filterId,
  324. not_null<MainList*> list) {
  325. if (filterId) {
  326. const auto &list = owner().chatsFilters().list();
  327. const auto it = ranges::find(list, filterId, &Data::ChatFilter::id);
  328. if (it != end(list)) {
  329. setColorIndexForFilterId(filterId, it->colorIndex());
  330. }
  331. }
  332. if (const auto main = maybeMainChatListLink(filterId)) {
  333. return main;
  334. }
  335. return _chatListLinks.emplace(
  336. filterId,
  337. list->addEntry(this)
  338. ).first->second.main;
  339. }
  340. void Entry::removeFromChatList(
  341. FilterId filterId,
  342. not_null<MainList*> list) {
  343. if (isPinnedDialog(filterId)) {
  344. owner().setChatPinned(this, filterId, false);
  345. }
  346. if (filterId) {
  347. const auto it = _tagColors.find(filterId);
  348. if (it != end(_tagColors)) {
  349. _tagColors.erase(it);
  350. }
  351. }
  352. const auto i = _chatListLinks.find(filterId);
  353. if (i == end(_chatListLinks)) {
  354. return;
  355. }
  356. _chatListLinks.erase(i);
  357. list->removeEntry(this);
  358. }
  359. void Entry::removeChatListEntryByLetter(FilterId filterId, QChar letter) {
  360. const auto i = _chatListLinks.find(filterId);
  361. if (i != end(_chatListLinks)) {
  362. i->second.letters.remove(letter);
  363. }
  364. }
  365. void Entry::addChatListEntryByLetter(
  366. FilterId filterId,
  367. QChar letter,
  368. not_null<Row*> row) {
  369. const auto i = _chatListLinks.find(filterId);
  370. if (i != end(_chatListLinks)) {
  371. i->second.letters.emplace(letter, row);
  372. }
  373. }
  374. void Entry::updateChatListEntry() {
  375. _flags &= ~Flag::UpdatePostponed;
  376. session().changes().entryUpdated(this, Data::EntryUpdate::Flag::Repaint);
  377. }
  378. void Entry::updateChatListEntryPostponed() {
  379. if (_flags & Flag::UpdatePostponed) {
  380. return;
  381. }
  382. _flags |= Flag::UpdatePostponed;
  383. Ui::PostponeCall(this, [=] {
  384. if (_flags & Flag::UpdatePostponed) {
  385. updateChatListEntry();
  386. }
  387. });
  388. }
  389. void Entry::updateChatListEntryHeight() {
  390. session().changes().entryUpdated(this, Data::EntryUpdate::Flag::Height);
  391. }
  392. [[nodiscard]] bool Entry::hasChatsFilterTags(FilterId exclude) const {
  393. if (!owner().chatsFilters().tagsEnabled()) {
  394. return false;
  395. }
  396. if (exclude) {
  397. if (_tagColors.size() == 1) {
  398. if (_tagColors.begin()->first == exclude) {
  399. return false;
  400. }
  401. }
  402. }
  403. return !_tagColors.empty();
  404. }
  405. } // namespace Dialogs