data_folder.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  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 "data/data_folder.h"
  8. #include "data/data_session.h"
  9. #include "data/data_channel.h"
  10. #include "data/data_histories.h"
  11. #include "data/data_changes.h"
  12. #include "dialogs/dialogs_key.h"
  13. #include "dialogs/ui/dialogs_layout.h"
  14. #include "history/history.h"
  15. #include "history/history_item.h"
  16. #include "ui/painter.h"
  17. #include "ui/text/text_options.h"
  18. #include "ui/text/text_utilities.h"
  19. #include "lang/lang_keys.h"
  20. #include "storage/storage_facade.h"
  21. #include "core/application.h"
  22. #include "core/core_settings.h"
  23. #include "main/main_account.h"
  24. #include "main/main_session.h"
  25. #include "mtproto/mtproto_config.h"
  26. #include "apiwrap.h"
  27. #include "mainwidget.h"
  28. #include "styles/style_dialogs.h"
  29. namespace Data {
  30. namespace {
  31. constexpr auto kLoadedChatsMinCount = 20;
  32. constexpr auto kShowChatNamesCount = 8;
  33. [[nodiscard]] TextWithEntities ComposeFolderListEntryText(
  34. not_null<Folder*> folder) {
  35. const auto &list = folder->lastHistories();
  36. if (list.empty()) {
  37. if (const auto storiesUnread = folder->storiesUnreadCount()) {
  38. return {
  39. tr::lng_contacts_stories_status_new(
  40. tr::now,
  41. lt_count,
  42. storiesUnread),
  43. };
  44. } else if (const auto storiesCount = folder->storiesCount()) {
  45. return {
  46. tr::lng_contacts_stories_status(
  47. tr::now,
  48. lt_count,
  49. storiesCount),
  50. };
  51. }
  52. return {};
  53. }
  54. const auto count = std::max(
  55. int(list.size()),
  56. folder->chatsList()->fullSize().current());
  57. const auto throwAwayLastName = (list.size() > 1)
  58. && (count == list.size() + 1);
  59. auto &&peers = ranges::views::all(
  60. list
  61. ) | ranges::views::take(
  62. list.size() - (throwAwayLastName ? 1 : 0)
  63. );
  64. const auto wrapName = [](not_null<History*> history) {
  65. const auto name = history->peer->name();
  66. return st::wrap_rtl(TextWithEntities{
  67. .text = name,
  68. .entities = (history->chatListBadgesState().unread
  69. ? EntitiesInText{
  70. { EntityType::Semibold, 0, int(name.size()), QString() },
  71. { EntityType::Colorized, 0, int(name.size()), QString() },
  72. }
  73. : EntitiesInText{}),
  74. });
  75. };
  76. const auto shown = int(peers.size());
  77. const auto accumulated = [&] {
  78. Expects(shown > 0);
  79. auto i = peers.begin();
  80. auto result = wrapName(*i);
  81. for (++i; i != peers.end(); ++i) {
  82. result = tr::lng_archived_last_list(
  83. tr::now,
  84. lt_accumulated,
  85. result,
  86. lt_chat,
  87. wrapName(*i),
  88. Ui::Text::WithEntities);
  89. }
  90. return result;
  91. }();
  92. return (shown < count)
  93. ? tr::lng_archived_last(
  94. tr::now,
  95. lt_count,
  96. (count - shown),
  97. lt_chats,
  98. accumulated,
  99. Ui::Text::WithEntities)
  100. : accumulated;
  101. }
  102. } // namespace
  103. Folder::Folder(not_null<Session*> owner, FolderId id)
  104. : Entry(owner, Type::Folder)
  105. , _id(id)
  106. , _chatsList(
  107. &owner->session(),
  108. FilterId(),
  109. owner->maxPinnedChatsLimitValue(this))
  110. , _name(tr::lng_archived_name(tr::now)) {
  111. indexNameParts();
  112. session().changes().peerUpdates(
  113. PeerUpdate::Flag::Name
  114. ) | rpl::filter([=](const PeerUpdate &update) {
  115. return ranges::contains(_lastHistories, update.peer, &History::peer);
  116. }) | rpl::start_with_next([=] {
  117. ++_chatListViewVersion;
  118. updateChatListEntryPostponed();
  119. }, _lifetime);
  120. _chatsList.setAllAreMuted(true);
  121. _chatsList.unreadStateChanges(
  122. ) | rpl::filter([=] {
  123. return inChatList();
  124. }) | rpl::start_with_next([=](const Dialogs::UnreadState &old) {
  125. ++_chatListViewVersion;
  126. notifyUnreadStateChange(old);
  127. }, _lifetime);
  128. _chatsList.fullSize().changes(
  129. ) | rpl::start_with_next([=] {
  130. updateChatListEntryPostponed();
  131. }, _lifetime);
  132. }
  133. FolderId Folder::id() const {
  134. return _id;
  135. }
  136. void Folder::indexNameParts() {
  137. // We don't want archive to be filtered in the chats list.
  138. }
  139. void Folder::registerOne(not_null<History*> history) {
  140. if (_chatsList.indexed()->size() == 1) {
  141. updateChatListSortPosition();
  142. if (!_chatsList.cloudUnreadKnown()) {
  143. owner().histories().requestDialogEntry(this);
  144. }
  145. } else {
  146. updateChatListEntry();
  147. }
  148. reorderLastHistories();
  149. }
  150. void Folder::unregisterOne(not_null<History*> history) {
  151. if (_chatsList.empty()) {
  152. updateChatListExistence();
  153. }
  154. reorderLastHistories();
  155. }
  156. int Folder::chatListNameVersion() const {
  157. return 1;
  158. }
  159. void Folder::oneListMessageChanged(HistoryItem *from, HistoryItem *to) {
  160. if (from || to) {
  161. reorderLastHistories();
  162. }
  163. }
  164. void Folder::reorderLastHistories() {
  165. // We want first kShowChatNamesCount histories, by last message date.
  166. const auto pred = [](not_null<History*> a, not_null<History*> b) {
  167. const auto aItem = a->chatListMessage();
  168. const auto bItem = b->chatListMessage();
  169. const auto aDate = aItem ? aItem->date() : TimeId(0);
  170. const auto bDate = bItem ? bItem->date() : TimeId(0);
  171. return aDate > bDate;
  172. };
  173. _lastHistories.clear();
  174. _lastHistories.reserve(kShowChatNamesCount + 1);
  175. auto &&histories = ranges::views::all(
  176. *_chatsList.indexed()
  177. ) | ranges::views::transform([](not_null<Dialogs::Row*> row) {
  178. return row->history();
  179. }) | ranges::views::filter([](History *history) {
  180. return (history != nullptr);
  181. });
  182. auto nonPinnedChecked = 0;
  183. for (const auto history : histories) {
  184. const auto i = ranges::upper_bound(
  185. _lastHistories,
  186. not_null(history),
  187. pred);
  188. if (size(_lastHistories) < kShowChatNamesCount
  189. || i != end(_lastHistories)) {
  190. _lastHistories.insert(i, history);
  191. }
  192. if (size(_lastHistories) > kShowChatNamesCount) {
  193. _lastHistories.pop_back();
  194. }
  195. if (!history->isPinnedDialog(FilterId())
  196. && ++nonPinnedChecked >= kShowChatNamesCount) {
  197. break;
  198. }
  199. }
  200. ++_chatListViewVersion;
  201. updateChatListEntry();
  202. }
  203. not_null<Dialogs::MainList*> Folder::chatsList() {
  204. return &_chatsList;
  205. }
  206. void Folder::clearChatsList() {
  207. _chatsList.clear();
  208. }
  209. void Folder::chatListPreloadData() {
  210. }
  211. void Folder::paintUserpic(
  212. Painter &p,
  213. Ui::PeerUserpicView &view,
  214. const Dialogs::Ui::PaintContext &context) const {
  215. paintUserpic(
  216. p,
  217. context.st->padding.left(),
  218. context.st->padding.top(),
  219. context.st->photoSize);
  220. }
  221. void Folder::paintUserpic(Painter &p, int x, int y, int size) const {
  222. paintUserpic(p, x, y, size, nullptr, nullptr);
  223. }
  224. void Folder::paintUserpic(
  225. Painter &p,
  226. int x,
  227. int y,
  228. int size,
  229. const style::color &bg,
  230. const style::color &fg) const {
  231. paintUserpic(p, x, y, size, &bg, &fg);
  232. }
  233. void Folder::paintUserpic(
  234. Painter &p,
  235. int x,
  236. int y,
  237. int size,
  238. const style::color *overrideBg,
  239. const style::color *overrideFg) const {
  240. p.setPen(Qt::NoPen);
  241. p.setBrush(overrideBg ? *overrideBg : st::historyPeerArchiveUserpicBg);
  242. {
  243. PainterHighQualityEnabler hq(p);
  244. p.drawEllipse(x, y, size, size);
  245. }
  246. if (size == st::defaultDialogRow.photoSize) {
  247. const auto rect = QRect{ x, y, size, size };
  248. if (overrideFg) {
  249. st::dialogsArchiveUserpic.paintInCenter(
  250. p,
  251. rect,
  252. (*overrideFg)->c);
  253. } else {
  254. st::dialogsArchiveUserpic.paintInCenter(p, rect);
  255. }
  256. } else {
  257. p.save();
  258. const auto ratio = size / float64(st::defaultDialogRow.photoSize);
  259. p.translate(x + size / 2., y + size / 2.);
  260. p.scale(ratio, ratio);
  261. const auto skip = st::defaultDialogRow.photoSize;
  262. const auto rect = QRect{ -skip, -skip, 2 * skip, 2 * skip };
  263. if (overrideFg) {
  264. st::dialogsArchiveUserpic.paintInCenter(
  265. p,
  266. rect,
  267. (*overrideFg)->c);
  268. } else {
  269. st::dialogsArchiveUserpic.paintInCenter(p, rect);
  270. }
  271. p.restore();
  272. }
  273. }
  274. const std::vector<not_null<History*>> &Folder::lastHistories() const {
  275. return _lastHistories;
  276. }
  277. void Folder::validateListEntryCache() {
  278. if (_listEntryCacheVersion == _chatListViewVersion) {
  279. return;
  280. }
  281. _listEntryCacheVersion = _chatListViewVersion;
  282. _listEntryCache.setMarkedText(
  283. st::dialogsTextStyle,
  284. ComposeFolderListEntryText(this),
  285. // Use rich options as long as the entry text does not have user text.
  286. Ui::ItemTextDefaultOptions());
  287. }
  288. void Folder::updateStoriesCount(int count, int unread) {
  289. if (_storiesCount == count && _storiesUnreadCount == unread) {
  290. return;
  291. }
  292. const auto limit = (1 << 16) - 1;
  293. const auto was = (_storiesCount > 0);
  294. _storiesCount = std::min(count, limit);
  295. _storiesUnreadCount = std::min(unread, limit);
  296. const auto now = (_storiesCount > 0);
  297. if (was == now) {
  298. updateChatListEntryPostponed();
  299. } else if (now) {
  300. updateChatListSortPosition();
  301. } else {
  302. updateChatListExistence();
  303. }
  304. ++_chatListViewVersion;
  305. }
  306. int Folder::storiesCount() const {
  307. return _storiesCount;
  308. }
  309. int Folder::storiesUnreadCount() const {
  310. return _storiesUnreadCount;
  311. }
  312. TimeId Folder::adjustedChatListTimeId() const {
  313. return chatListTimeId();
  314. }
  315. void Folder::applyDialog(const MTPDdialogFolder &data) {
  316. _chatsList.updateCloudUnread(data);
  317. if (const auto peerId = peerFromMTP(data.vpeer())) {
  318. const auto history = owner().history(peerId);
  319. const auto fullId = FullMsgId(peerId, data.vtop_message().v);
  320. history->setFolder(this, owner().message(fullId));
  321. } else {
  322. _chatsList.clear();
  323. updateChatListExistence();
  324. }
  325. if (_chatsList.indexed()->size() < kLoadedChatsMinCount) {
  326. session().api().requestDialogs(this);
  327. }
  328. }
  329. void Folder::applyPinnedUpdate(const MTPDupdateDialogPinned &data) {
  330. const auto folderId = data.vfolder_id().value_or_empty();
  331. if (folderId != 0) {
  332. LOG(("API Error: Nested folders detected."));
  333. }
  334. owner().setChatPinned(this, FilterId(), data.is_pinned());
  335. }
  336. int Folder::fixedOnTopIndex() const {
  337. return kArchiveFixOnTopIndex;
  338. }
  339. bool Folder::shouldBeInChatList() const {
  340. return !_chatsList.empty() || (_storiesCount > 0);
  341. }
  342. Dialogs::UnreadState Folder::chatListUnreadState() const {
  343. return _chatsList.unreadState();
  344. }
  345. Dialogs::BadgesState Folder::chatListBadgesState() const {
  346. auto result = Dialogs::BadgesForUnread(
  347. chatListUnreadState(),
  348. Dialogs::CountInBadge::Chats,
  349. Dialogs::IncludeInBadge::All);
  350. result.unreadMuted = result.mentionMuted = result.reactionMuted = true;
  351. if (result.unread && !result.unreadCounter) {
  352. result.unreadCounter = 1;
  353. }
  354. return result;
  355. }
  356. HistoryItem *Folder::chatListMessage() const {
  357. return nullptr;
  358. }
  359. bool Folder::chatListMessageKnown() const {
  360. return true;
  361. }
  362. const QString &Folder::chatListName() const {
  363. return _name;
  364. }
  365. const base::flat_set<QString> &Folder::chatListNameWords() const {
  366. return _nameWords;
  367. }
  368. const base::flat_set<QChar> &Folder::chatListFirstLetters() const {
  369. return _nameFirstLetters;
  370. }
  371. const QString &Folder::chatListNameSortKey() const {
  372. static const auto empty = QString();
  373. return empty;
  374. }
  375. } // namespace Data