1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "history/history.h"
9
10 #include "history/view/history_view_element.h"
11 #include "history/history_message.h"
12 #include "history/history_service.h"
13 #include "history/history_item_components.h"
14 #include "history/history_inner_widget.h"
15 #include "dialogs/dialogs_indexed_list.h"
16 #include "data/stickers/data_stickers.h"
17 #include "data/data_drafts.h"
18 #include "data/data_session.h"
19 #include "data/data_media_types.h"
20 #include "data/data_channel_admins.h"
21 #include "data/data_changes.h"
22 #include "data/data_chat_filters.h"
23 #include "data/data_scheduled_messages.h"
24 #include "data/data_send_action.h"
25 #include "data/data_folder.h"
26 #include "data/data_photo.h"
27 #include "data/data_channel.h"
28 #include "data/data_chat.h"
29 #include "data/data_user.h"
30 #include "data/data_document.h"
31 #include "data/data_histories.h"
32 #include "lang/lang_keys.h"
33 #include "apiwrap.h"
34 #include "mainwidget.h"
35 #include "mainwindow.h"
36 #include "main/main_session.h"
37 #include "window/notifications_manager.h"
38 #include "calls/calls_instance.h"
39 #include "storage/localstorage.h"
40 #include "storage/storage_facade.h"
41 #include "storage/storage_shared_media.h"
42 #include "storage/storage_account.h"
43 #include "support/support_helper.h"
44 #include "ui/image/image.h"
45 #include "ui/text/text_options.h"
46 #include "ui/toasts/common_toasts.h"
47 #include "ui/text/text_utilities.h"
48 #include "payments/payments_checkout_process.h"
49 #include "core/crash_reports.h"
50 #include "core/application.h"
51 #include "base/unixtime.h"
52 #include "base/qt_adapters.h"
53 #include "styles/style_dialogs.h"
54
55 namespace {
56
57 constexpr auto kNewBlockEachMessage = 50;
58 constexpr auto kSkipCloudDraftsFor = TimeId(2);
59
60 using UpdateFlag = Data::HistoryUpdate::Flag;
61
62 } // namespace
63
History(not_null<Data::Session * > owner,PeerId peerId)64 History::History(not_null<Data::Session*> owner, PeerId peerId)
65 : Entry(owner, Type::History)
66 , peer(owner->peer(peerId))
67 , cloudDraftTextCache(st::dialogsTextWidthMin)
68 , _mute(owner->notifyIsMuted(peer))
69 , _chatListNameSortKey(owner->nameSortKey(peer->name))
70 , _sendActionPainter(this) {
71 if (const auto user = peer->asUser()) {
72 if (user->isBot()) {
73 _outboxReadBefore = std::numeric_limits<MsgId>::max();
74 }
75 }
76 }
77
clearLastKeyboard()78 void History::clearLastKeyboard() {
79 if (lastKeyboardId) {
80 if (lastKeyboardId == lastKeyboardHiddenId) {
81 lastKeyboardHiddenId = 0;
82 }
83 lastKeyboardId = 0;
84 session().changes().historyUpdated(this, UpdateFlag::BotKeyboard);
85 }
86 lastKeyboardInited = true;
87 lastKeyboardFrom = 0;
88 }
89
height() const90 int History::height() const {
91 return _height;
92 }
93
removeNotification(not_null<HistoryItem * > item)94 void History::removeNotification(not_null<HistoryItem*> item) {
95 _notifications.erase(
96 ranges::remove(_notifications, item),
97 end(_notifications));
98 }
99
currentNotification()100 HistoryItem *History::currentNotification() {
101 return empty(_notifications)
102 ? nullptr
103 : _notifications.front().get();
104 }
105
hasNotification() const106 bool History::hasNotification() const {
107 return !empty(_notifications);
108 }
109
skipNotification()110 void History::skipNotification() {
111 if (!empty(_notifications)) {
112 _notifications.pop_front();
113 }
114 }
115
popNotification(HistoryItem * item)116 void History::popNotification(HistoryItem *item) {
117 if (!empty(_notifications) && (_notifications.back() == item)) {
118 _notifications.pop_back();
119 }
120 }
121
hasPendingResizedItems() const122 bool History::hasPendingResizedItems() const {
123 return _flags & Flag::f_has_pending_resized_items;
124 }
125
setHasPendingResizedItems()126 void History::setHasPendingResizedItems() {
127 _flags |= Flag::f_has_pending_resized_items;
128 }
129
itemRemoved(not_null<HistoryItem * > item)130 void History::itemRemoved(not_null<HistoryItem*> item) {
131 if (item == _joinedMessage) {
132 _joinedMessage = nullptr;
133 }
134 item->removeMainView();
135 if (_lastServerMessage == item) {
136 _lastServerMessage = std::nullopt;
137 }
138 if (lastMessage() == item) {
139 _lastMessage = std::nullopt;
140 if (loadedAtBottom()) {
141 if (const auto last = lastAvailableMessage()) {
142 setLastMessage(last);
143 }
144 }
145 }
146 checkChatListMessageRemoved(item);
147 itemVanished(item);
148 if (IsClientMsgId(item->id)) {
149 unregisterClientSideMessage(item);
150 }
151 if (const auto chat = peer->asChat()) {
152 if (const auto to = chat->getMigrateToChannel()) {
153 if (const auto history = owner().historyLoaded(to)) {
154 history->checkChatListMessageRemoved(item);
155 }
156 }
157 }
158 }
159
checkChatListMessageRemoved(not_null<HistoryItem * > item)160 void History::checkChatListMessageRemoved(not_null<HistoryItem*> item) {
161 if (chatListMessage() != item) {
162 return;
163 }
164 setChatListMessageUnknown();
165 refreshChatListMessage();
166 }
167
itemVanished(not_null<HistoryItem * > item)168 void History::itemVanished(not_null<HistoryItem*> item) {
169 removeNotification(item);
170 if (lastKeyboardId == item->id) {
171 clearLastKeyboard();
172 }
173 if ((!item->out() || item->isPost())
174 && item->unread()
175 && unreadCount() > 0) {
176 setUnreadCount(unreadCount() - 1);
177 }
178 }
179
takeLocalDraft(not_null<History * > from)180 void History::takeLocalDraft(not_null<History*> from) {
181 const auto i = from->_drafts.find(Data::DraftKey::Local());
182 if (i == end(from->_drafts)) {
183 return;
184 }
185 auto &draft = i->second;
186 if (!draft->textWithTags.text.isEmpty()
187 && !_drafts.contains(Data::DraftKey::Local())) {
188 // Edit and reply to drafts can't migrate.
189 // Cloud drafts do not migrate automatically.
190 draft->msgId = 0;
191
192 setLocalDraft(std::move(draft));
193 }
194 from->clearLocalDraft();
195 session().api().saveDraftToCloudDelayed(from);
196 }
197
createLocalDraftFromCloud()198 void History::createLocalDraftFromCloud() {
199 const auto draft = cloudDraft();
200 if (!draft) {
201 clearLocalDraft();
202 return;
203 } else if (Data::draftIsNull(draft) || !draft->date) {
204 return;
205 }
206
207 auto existing = localDraft();
208 if (Data::draftIsNull(existing)
209 || !existing->date
210 || draft->date >= existing->date) {
211 if (!existing) {
212 setLocalDraft(std::make_unique<Data::Draft>(
213 draft->textWithTags,
214 draft->msgId,
215 draft->cursor,
216 draft->previewState));
217 existing = localDraft();
218 } else if (existing != draft) {
219 existing->textWithTags = draft->textWithTags;
220 existing->msgId = draft->msgId;
221 existing->cursor = draft->cursor;
222 existing->previewState = draft->previewState;
223 }
224 existing->date = draft->date;
225 }
226 }
227
draft(Data::DraftKey key) const228 Data::Draft *History::draft(Data::DraftKey key) const {
229 if (!key) {
230 return nullptr;
231 }
232 const auto i = _drafts.find(key);
233 return (i != _drafts.end()) ? i->second.get() : nullptr;
234 }
235
setDraft(Data::DraftKey key,std::unique_ptr<Data::Draft> && draft)236 void History::setDraft(Data::DraftKey key, std::unique_ptr<Data::Draft> &&draft) {
237 if (!key) {
238 return;
239 }
240 const auto changingCloudDraft = (key == Data::DraftKey::Cloud());
241 if (changingCloudDraft) {
242 cloudDraftTextCache.clear();
243 }
244 if (draft) {
245 _drafts[key] = std::move(draft);
246 } else if (_drafts.remove(key) && changingCloudDraft) {
247 updateChatListSortPosition();
248 }
249 }
250
draftsMap() const251 const Data::HistoryDrafts &History::draftsMap() const {
252 return _drafts;
253 }
254
setDraftsMap(Data::HistoryDrafts && map)255 void History::setDraftsMap(Data::HistoryDrafts &&map) {
256 for (auto &[key, draft] : _drafts) {
257 map[key] = std::move(draft);
258 }
259 _drafts = std::move(map);
260 }
261
clearDraft(Data::DraftKey key)262 void History::clearDraft(Data::DraftKey key) {
263 setDraft(key, nullptr);
264 }
265
clearDrafts()266 void History::clearDrafts() {
267 const auto changingCloudDraft = _drafts.contains(Data::DraftKey::Cloud());
268 _drafts.clear();
269 if (changingCloudDraft) {
270 cloudDraftTextCache.clear();
271 updateChatListSortPosition();
272 }
273 }
274
createCloudDraft(const Data::Draft * fromDraft)275 Data::Draft *History::createCloudDraft(const Data::Draft *fromDraft) {
276 if (Data::draftIsNull(fromDraft)) {
277 setCloudDraft(std::make_unique<Data::Draft>(
278 TextWithTags(),
279 0,
280 MessageCursor(),
281 Data::PreviewState::Allowed));
282 cloudDraft()->date = TimeId(0);
283 } else {
284 auto existing = cloudDraft();
285 if (!existing) {
286 setCloudDraft(std::make_unique<Data::Draft>(
287 fromDraft->textWithTags,
288 fromDraft->msgId,
289 fromDraft->cursor,
290 fromDraft->previewState));
291 existing = cloudDraft();
292 } else if (existing != fromDraft) {
293 existing->textWithTags = fromDraft->textWithTags;
294 existing->msgId = fromDraft->msgId;
295 existing->cursor = fromDraft->cursor;
296 existing->previewState = fromDraft->previewState;
297 }
298 existing->date = base::unixtime::now();
299 }
300
301 cloudDraftTextCache.clear();
302 updateChatListSortPosition();
303
304 return cloudDraft();
305 }
306
skipCloudDraftUpdate(TimeId date) const307 bool History::skipCloudDraftUpdate(TimeId date) const {
308 return (_savingCloudDraftRequests > 0)
309 || (date < _acceptCloudDraftsAfter);
310 }
311
startSavingCloudDraft()312 void History::startSavingCloudDraft() {
313 ++_savingCloudDraftRequests;
314 }
315
finishSavingCloudDraft(TimeId savedAt)316 void History::finishSavingCloudDraft(TimeId savedAt) {
317 if (_savingCloudDraftRequests > 0) {
318 --_savingCloudDraftRequests;
319 }
320 const auto acceptAfter = savedAt + kSkipCloudDraftsFor;
321 _acceptCloudDraftsAfter = std::max(_acceptCloudDraftsAfter, acceptAfter);
322 }
323
applyCloudDraft()324 void History::applyCloudDraft() {
325 if (session().supportMode()) {
326 updateChatListEntry();
327 session().supportHelper().cloudDraftChanged(this);
328 } else {
329 createLocalDraftFromCloud();
330 updateChatListSortPosition();
331 session().changes().historyUpdated(this, UpdateFlag::CloudDraft);
332 }
333 }
334
draftSavedToCloud()335 void History::draftSavedToCloud() {
336 updateChatListEntry();
337 session().local().writeDrafts(this);
338 }
339
resolveForwardDraft(const Data::ForwardDraft & draft) const340 Data::ResolvedForwardDraft History::resolveForwardDraft(
341 const Data::ForwardDraft &draft) const {
342 return Data::ResolvedForwardDraft{
343 .items = owner().idsToItems(draft.ids),
344 .options = draft.options,
345 };
346 }
347
resolveForwardDraft()348 Data::ResolvedForwardDraft History::resolveForwardDraft() {
349 auto result = resolveForwardDraft(_forwardDraft);
350 if (result.items.size() != _forwardDraft.ids.size()) {
351 setForwardDraft({
352 .ids = owner().itemsToIds(result.items),
353 .options = result.options,
354 });
355 }
356 return result;
357 }
358
setForwardDraft(Data::ForwardDraft && draft)359 void History::setForwardDraft(Data::ForwardDraft &&draft) {
360 _forwardDraft = std::move(draft);
361 }
362
createItem(MsgId id,const MTPMessage & message,MessageFlags localFlags,bool detachExistingItem)363 not_null<HistoryItem*> History::createItem(
364 MsgId id,
365 const MTPMessage &message,
366 MessageFlags localFlags,
367 bool detachExistingItem) {
368 if (const auto result = owner().message(channelId(), id)) {
369 if (detachExistingItem) {
370 result->removeMainView();
371 }
372 return result;
373 }
374 return HistoryItem::Create(this, id, message, localFlags);
375 }
376
createItems(const QVector<MTPMessage> & data)377 std::vector<not_null<HistoryItem*>> History::createItems(
378 const QVector<MTPMessage> &data) {
379 auto result = std::vector<not_null<HistoryItem*>>();
380 result.reserve(data.size());
381 const auto localFlags = MessageFlags();
382 const auto detachExistingItem = true;
383 for (auto i = data.cend(), e = data.cbegin(); i != e;) {
384 const auto &data = *--i;
385 result.emplace_back(createItem(
386 IdFromMessage(data),
387 data,
388 localFlags,
389 detachExistingItem));
390 }
391 return result;
392 }
393
addNewMessage(MsgId id,const MTPMessage & msg,MessageFlags localFlags,NewMessageType type)394 not_null<HistoryItem*> History::addNewMessage(
395 MsgId id,
396 const MTPMessage &msg,
397 MessageFlags localFlags,
398 NewMessageType type) {
399 const auto detachExistingItem = (type == NewMessageType::Unread);
400 const auto item = createItem(id, msg, localFlags, detachExistingItem);
401 if (type == NewMessageType::Existing || item->mainView()) {
402 return item;
403 }
404 const auto unread = (type == NewMessageType::Unread);
405 if (unread && item->isHistoryEntry()) {
406 applyMessageChanges(item, msg);
407 }
408 return addNewItem(item, unread);
409 }
410
insertItem(std::unique_ptr<HistoryItem> item)411 not_null<HistoryItem*> History::insertItem(
412 std::unique_ptr<HistoryItem> item) {
413 Expects(item != nullptr);
414
415 const auto [i, ok] = _messages.insert(std::move(item));
416
417 const auto result = i->get();
418 owner().registerMessage(result);
419
420 Ensures(ok);
421 return result;
422 }
423
destroyMessage(not_null<HistoryItem * > item)424 void History::destroyMessage(not_null<HistoryItem*> item) {
425 Expects(item->isHistoryEntry() || !item->mainView());
426
427 const auto peerId = peer->id;
428 if (item->isHistoryEntry()) {
429 // All this must be done for all items manually in History::clear()!
430 item->destroyHistoryEntry();
431 if (item->isRegular()) {
432 if (const auto types = item->sharedMediaTypes()) {
433 session().storage().remove(Storage::SharedMediaRemoveOne(
434 peerId,
435 types,
436 item->id));
437 }
438 }
439 itemRemoved(item);
440 }
441 if (item->isSending()) {
442 session().api().cancelLocalItem(item);
443 }
444
445 const auto document = [&] {
446 const auto media = item->media();
447 return media ? media->document() : nullptr;
448 }();
449
450 owner().unregisterMessage(item);
451 Core::App().notifications().clearFromItem(item);
452
453 auto hack = std::unique_ptr<HistoryItem>(item.get());
454 const auto i = _messages.find(hack);
455 hack.release();
456
457 Assert(i != end(_messages));
458 _messages.erase(i);
459
460 if (document) {
461 session().data().documentMessageRemoved(document);
462 }
463 }
464
unpinAllMessages()465 void History::unpinAllMessages() {
466 session().storage().remove(
467 Storage::SharedMediaRemoveAll(
468 peer->id,
469 Storage::SharedMediaType::Pinned));
470 setHasPinnedMessages(false);
471 for (const auto &message : _messages) {
472 if (message->isPinned()) {
473 message->setIsPinned(false);
474 }
475 }
476 }
477
addNewItem(not_null<HistoryItem * > item,bool unread)478 not_null<HistoryItem*> History::addNewItem(
479 not_null<HistoryItem*> item,
480 bool unread) {
481 if (item->isScheduled()) {
482 owner().scheduledMessages().appendSending(item);
483 return item;
484 } else if (!item->isHistoryEntry()) {
485 return item;
486 }
487
488 // In case we've loaded a new 'last' message
489 // and it is not in blocks and we think that
490 // we have all the messages till the bottom
491 // we should unload known history or mark
492 // currently loaded slice as not reaching bottom.
493 const auto shouldMarkBottomNotLoaded = loadedAtBottom()
494 && !unread
495 && !isEmpty();
496 if (shouldMarkBottomNotLoaded) {
497 setNotLoadedAtBottom();
498 }
499
500 if (!loadedAtBottom() || peer->migrateTo()) {
501 setLastMessage(item);
502 if (unread) {
503 newItemAdded(item);
504 }
505 } else {
506 addNewToBack(item, unread);
507 checkForLoadedAtTop(item);
508 if (!unread) {
509 // When we add just one last item, like we do while loading dialogs,
510 // we want to remove a single added grouped media, otherwise it will
511 // jump once we open the message history (first we show only that
512 // media, then we load the rest of the group and show the group).
513 //
514 // That way when we open the message history we show nothing until a
515 // whole history part is loaded, it certainly will contain the group.
516 removeOrphanMediaGroupPart();
517 }
518 }
519 return item;
520 }
521
checkForLoadedAtTop(not_null<HistoryItem * > added)522 void History::checkForLoadedAtTop(not_null<HistoryItem*> added) {
523 if (peer->isChat()) {
524 if (added->isGroupEssential() && !added->isGroupMigrate()) {
525 // We added the first message about group creation.
526 _loadedAtTop = true;
527 addEdgesToSharedMedia();
528 }
529 } else if (peer->isChannel()) {
530 if (added->id == 1) {
531 _loadedAtTop = true;
532 checkLocalMessages();
533 addEdgesToSharedMedia();
534 }
535 }
536 }
537
addNewLocalMessage(MsgId id,MessageFlags flags,UserId viaBotId,MsgId replyTo,TimeId date,PeerId from,const QString & postAuthor,const TextWithEntities & text,const MTPMessageMedia & media,HistoryMessageMarkupData && markup,uint64 groupedId)538 not_null<HistoryItem*> History::addNewLocalMessage(
539 MsgId id,
540 MessageFlags flags,
541 UserId viaBotId,
542 MsgId replyTo,
543 TimeId date,
544 PeerId from,
545 const QString &postAuthor,
546 const TextWithEntities &text,
547 const MTPMessageMedia &media,
548 HistoryMessageMarkupData &&markup,
549 uint64 groupedId) {
550 return addNewItem(
551 makeMessage(
552 id,
553 flags | MessageFlag::Local,
554 replyTo,
555 viaBotId,
556 date,
557 from,
558 postAuthor,
559 text,
560 media,
561 std::move(markup),
562 groupedId),
563 true);
564 }
565
addNewLocalMessage(MsgId id,MessageFlags flags,TimeId date,PeerId from,const QString & postAuthor,not_null<HistoryItem * > forwardOriginal)566 not_null<HistoryItem*> History::addNewLocalMessage(
567 MsgId id,
568 MessageFlags flags,
569 TimeId date,
570 PeerId from,
571 const QString &postAuthor,
572 not_null<HistoryItem*> forwardOriginal) {
573 return addNewItem(
574 makeMessage(
575 id,
576 flags | MessageFlag::Local,
577 date,
578 from,
579 postAuthor,
580 forwardOriginal),
581 true);
582 }
583
addNewLocalMessage(MsgId id,MessageFlags flags,UserId viaBotId,MsgId replyTo,TimeId date,PeerId from,const QString & postAuthor,not_null<DocumentData * > document,const TextWithEntities & caption,HistoryMessageMarkupData && markup)584 not_null<HistoryItem*> History::addNewLocalMessage(
585 MsgId id,
586 MessageFlags flags,
587 UserId viaBotId,
588 MsgId replyTo,
589 TimeId date,
590 PeerId from,
591 const QString &postAuthor,
592 not_null<DocumentData*> document,
593 const TextWithEntities &caption,
594 HistoryMessageMarkupData &&markup) {
595 return addNewItem(
596 makeMessage(
597 id,
598 flags | MessageFlag::Local,
599 replyTo,
600 viaBotId,
601 date,
602 from,
603 postAuthor,
604 document,
605 caption,
606 std::move(markup)),
607 true);
608 }
609
addNewLocalMessage(MsgId id,MessageFlags flags,UserId viaBotId,MsgId replyTo,TimeId date,PeerId from,const QString & postAuthor,not_null<PhotoData * > photo,const TextWithEntities & caption,HistoryMessageMarkupData && markup)610 not_null<HistoryItem*> History::addNewLocalMessage(
611 MsgId id,
612 MessageFlags flags,
613 UserId viaBotId,
614 MsgId replyTo,
615 TimeId date,
616 PeerId from,
617 const QString &postAuthor,
618 not_null<PhotoData*> photo,
619 const TextWithEntities &caption,
620 HistoryMessageMarkupData &&markup) {
621 return addNewItem(
622 makeMessage(
623 id,
624 flags | MessageFlag::Local,
625 replyTo,
626 viaBotId,
627 date,
628 from,
629 postAuthor,
630 photo,
631 caption,
632 std::move(markup)),
633 true);
634 }
635
addNewLocalMessage(MsgId id,MessageFlags flags,UserId viaBotId,MsgId replyTo,TimeId date,PeerId from,const QString & postAuthor,not_null<GameData * > game,HistoryMessageMarkupData && markup)636 not_null<HistoryItem*> History::addNewLocalMessage(
637 MsgId id,
638 MessageFlags flags,
639 UserId viaBotId,
640 MsgId replyTo,
641 TimeId date,
642 PeerId from,
643 const QString &postAuthor,
644 not_null<GameData*> game,
645 HistoryMessageMarkupData &&markup) {
646 return addNewItem(
647 makeMessage(
648 id,
649 flags | MessageFlag::Local,
650 replyTo,
651 viaBotId,
652 date,
653 from,
654 postAuthor,
655 game,
656 std::move(markup)),
657 true);
658 }
659
setUnreadMentionsCount(int count)660 void History::setUnreadMentionsCount(int count) {
661 const auto had = _unreadMentionsCount && (*_unreadMentionsCount > 0);
662 if (_unreadMentions.size() > count) {
663 LOG(("API Warning: real mentions count is greater than received mentions count"));
664 count = _unreadMentions.size();
665 }
666 _unreadMentionsCount = count;
667 const auto has = (count > 0);
668 if (has != had) {
669 owner().chatsFilters().refreshHistory(this);
670 updateChatListEntry();
671 }
672 }
673
addToUnreadMentions(MsgId msgId,UnreadMentionType type)674 bool History::addToUnreadMentions(
675 MsgId msgId,
676 UnreadMentionType type) {
677 if (peer->isChannel() && !peer->isMegagroup()) {
678 return false;
679 }
680 auto allLoaded = _unreadMentionsCount
681 ? (_unreadMentions.size() >= *_unreadMentionsCount)
682 : false;
683 if (allLoaded) {
684 if (type == UnreadMentionType::New) {
685 _unreadMentions.insert(msgId);
686 setUnreadMentionsCount(*_unreadMentionsCount + 1);
687 return true;
688 }
689 } else if (!_unreadMentions.empty() && type != UnreadMentionType::New) {
690 _unreadMentions.insert(msgId);
691 return true;
692 }
693 return false;
694 }
695
eraseFromUnreadMentions(MsgId msgId)696 void History::eraseFromUnreadMentions(MsgId msgId) {
697 _unreadMentions.remove(msgId);
698 if (_unreadMentionsCount && *_unreadMentionsCount > 0) {
699 setUnreadMentionsCount(*_unreadMentionsCount - 1);
700 }
701 session().changes().historyUpdated(this, UpdateFlag::UnreadMentions);
702 }
703
addUnreadMentionsSlice(const MTPmessages_Messages & result)704 void History::addUnreadMentionsSlice(const MTPmessages_Messages &result) {
705 auto count = 0;
706 auto messages = (const QVector<MTPMessage>*)nullptr;
707 auto getMessages = [&](auto &list) {
708 owner().processUsers(list.vusers());
709 owner().processChats(list.vchats());
710 return &list.vmessages().v;
711 };
712 switch (result.type()) {
713 case mtpc_messages_messages: {
714 auto &d = result.c_messages_messages();
715 messages = getMessages(d);
716 count = messages->size();
717 } break;
718
719 case mtpc_messages_messagesSlice: {
720 auto &d = result.c_messages_messagesSlice();
721 messages = getMessages(d);
722 count = d.vcount().v;
723 } break;
724
725 case mtpc_messages_channelMessages: {
726 LOG(("API Error: unexpected messages.channelMessages! (History::addUnreadMentionsSlice)"));
727 auto &d = result.c_messages_channelMessages();
728 messages = getMessages(d);
729 count = d.vcount().v;
730 } break;
731
732 case mtpc_messages_messagesNotModified: {
733 LOG(("API Error: received messages.messagesNotModified! (History::addUnreadMentionsSlice)"));
734 } break;
735
736 default: Unexpected("type in History::addUnreadMentionsSlice");
737 }
738
739 auto added = false;
740 if (messages) {
741 const auto localFlags = MessageFlags();
742 const auto type = NewMessageType::Existing;
743 for (const auto &message : *messages) {
744 const auto item = addNewMessage(
745 IdFromMessage(message),
746 message,
747 localFlags,
748 type);
749 if (item && item->isUnreadMention()) {
750 _unreadMentions.insert(item->id);
751 added = true;
752 }
753 }
754 }
755 if (!added) {
756 count = _unreadMentions.size();
757 }
758 setUnreadMentionsCount(count);
759 session().changes().historyUpdated(this, UpdateFlag::UnreadMentions);
760 }
761
addNewToBack(not_null<HistoryItem * > item,bool unread)762 not_null<HistoryItem*> History::addNewToBack(
763 not_null<HistoryItem*> item,
764 bool unread) {
765 Expects(!isBuildingFrontBlock());
766
767 addItemToBlock(item);
768
769 if (!unread && item->isRegular()) {
770 if (const auto sharedMediaTypes = item->sharedMediaTypes()) {
771 auto from = loadedAtTop() ? 0 : minMsgId();
772 auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
773 session().storage().add(Storage::SharedMediaAddExisting(
774 peer->id,
775 sharedMediaTypes,
776 item->id,
777 { from, till }));
778 if (sharedMediaTypes.test(Storage::SharedMediaType::Pinned)) {
779 setHasPinnedMessages(true);
780 }
781 }
782 }
783 if (item->from()->id) {
784 if (auto user = item->from()->asUser()) {
785 auto getLastAuthors = [this]() -> std::deque<not_null<UserData*>>* {
786 if (auto chat = peer->asChat()) {
787 return &chat->lastAuthors;
788 } else if (auto channel = peer->asMegagroup()) {
789 return &channel->mgInfo->lastParticipants;
790 }
791 return nullptr;
792 };
793 if (auto megagroup = peer->asMegagroup()) {
794 if (user->isBot()) {
795 auto mgInfo = megagroup->mgInfo.get();
796 Assert(mgInfo != nullptr);
797 mgInfo->bots.insert(user);
798 if (mgInfo->botStatus != 0 && mgInfo->botStatus < 2) {
799 mgInfo->botStatus = 2;
800 }
801 }
802 }
803 if (auto lastAuthors = getLastAuthors()) {
804 auto prev = ranges::find(
805 *lastAuthors,
806 user,
807 [](not_null<UserData*> user) { return user.get(); });
808 auto index = (prev != lastAuthors->end())
809 ? (lastAuthors->end() - prev)
810 : -1;
811 if (index > 0) {
812 lastAuthors->erase(prev);
813 } else if (index < 0 && peer->isMegagroup()) { // nothing is outdated if just reordering
814 // admins information outdated
815 }
816 if (index) {
817 lastAuthors->push_front(user);
818 }
819 if (auto megagroup = peer->asMegagroup()) {
820 session().changes().peerUpdated(
821 peer,
822 Data::PeerUpdate::Flag::Members);
823 owner().addNewMegagroupParticipant(megagroup, user);
824 }
825 }
826 }
827 if (item->definesReplyKeyboard()) {
828 auto markupFlags = item->replyKeyboardFlags();
829 if (!(markupFlags & ReplyMarkupFlag::Selective)
830 || item->mentionsMe()) {
831 auto getMarkupSenders = [this]() -> base::flat_set<not_null<PeerData*>>* {
832 if (auto chat = peer->asChat()) {
833 return &chat->markupSenders;
834 } else if (auto channel = peer->asMegagroup()) {
835 return &channel->mgInfo->markupSenders;
836 }
837 return nullptr;
838 };
839 if (auto markupSenders = getMarkupSenders()) {
840 markupSenders->insert(item->from());
841 }
842 if (markupFlags & ReplyMarkupFlag::None) {
843 // None markup means replyKeyboardHide.
844 if (lastKeyboardFrom == item->from()->id
845 || (!lastKeyboardInited
846 && !peer->isChat()
847 && !peer->isMegagroup()
848 && !item->out())) {
849 clearLastKeyboard();
850 }
851 } else {
852 bool botNotInChat = false;
853 if (peer->isChat()) {
854 botNotInChat = item->from()->isUser()
855 && (!peer->asChat()->participants.empty()
856 || !peer->canWrite())
857 && !peer->asChat()->participants.contains(
858 item->from()->asUser());
859 } else if (peer->isMegagroup()) {
860 botNotInChat = item->from()->isUser()
861 && (peer->asChannel()->mgInfo->botStatus != 0
862 || !peer->canWrite())
863 && !peer->asChannel()->mgInfo->bots.contains(
864 item->from()->asUser());
865 }
866 if (botNotInChat) {
867 clearLastKeyboard();
868 } else {
869 lastKeyboardInited = true;
870 lastKeyboardId = item->id;
871 lastKeyboardFrom = item->from()->id;
872 lastKeyboardUsed = false;
873 }
874 }
875 }
876 }
877 }
878
879 setLastMessage(item);
880 if (unread) {
881 newItemAdded(item);
882 }
883
884 owner().notifyHistoryChangeDelayed(this);
885 return item;
886 }
887
applyMessageChanges(not_null<HistoryItem * > item,const MTPMessage & data)888 void History::applyMessageChanges(
889 not_null<HistoryItem*> item,
890 const MTPMessage &data) {
891 if (data.type() == mtpc_messageService) {
892 applyServiceChanges(item, data.c_messageService());
893 }
894 owner().stickers().checkSavedGif(item);
895 session().changes().messageUpdated(
896 item,
897 Data::MessageUpdate::Flag::NewAdded);
898 }
899
applyServiceChanges(not_null<HistoryItem * > item,const MTPDmessageService & data)900 void History::applyServiceChanges(
901 not_null<HistoryItem*> item,
902 const MTPDmessageService &data) {
903 const auto replyTo = data.vreply_to();
904 const auto processJoinedUser = [&](
905 not_null<ChannelData*> megagroup,
906 not_null<MegagroupInfo*> mgInfo,
907 not_null<UserData*> user) {
908 if (!base::contains(mgInfo->lastParticipants, user)) {
909 mgInfo->lastParticipants.push_front(user);
910 session().changes().peerUpdated(
911 peer,
912 Data::PeerUpdate::Flag::Members);
913 owner().addNewMegagroupParticipant(megagroup, user);
914 }
915 if (user->isBot()) {
916 mgInfo->bots.insert(user);
917 if (mgInfo->botStatus != 0 && mgInfo->botStatus < 2) {
918 mgInfo->botStatus = 2;
919 }
920 }
921 };
922 const auto processJoinedPeer = [&](not_null<PeerData*> joined) {
923 if (const auto megagroup = peer->asMegagroup()) {
924 const auto mgInfo = megagroup->mgInfo.get();
925 Assert(mgInfo != nullptr);
926 if (const auto user = joined->asUser()) {
927 processJoinedUser(megagroup, mgInfo, user);
928 }
929 }
930 };
931 data.vaction().match([&](const MTPDmessageActionChatAddUser &data) {
932 if (const auto megagroup = peer->asMegagroup()) {
933 const auto mgInfo = megagroup->mgInfo.get();
934 Assert(mgInfo != nullptr);
935 for (const auto &userId : data.vusers().v) {
936 if (const auto user = owner().userLoaded(userId.v)) {
937 processJoinedUser(megagroup, mgInfo, user);
938 }
939 }
940 }
941 }, [&](const MTPDmessageActionChatJoinedByLink &data) {
942 processJoinedPeer(item->from());
943 }, [&](const MTPDmessageActionChatDeletePhoto &data) {
944 if (const auto chat = peer->asChat()) {
945 chat->setPhoto(MTP_chatPhotoEmpty());
946 }
947 }, [&](const MTPDmessageActionChatDeleteUser &data) {
948 const auto uid = data.vuser_id().v;
949 if (lastKeyboardFrom == peerFromUser(uid)) {
950 clearLastKeyboard();
951 }
952 if (const auto megagroup = peer->asMegagroup()) {
953 if (const auto user = owner().userLoaded(uid)) {
954 const auto mgInfo = megagroup->mgInfo.get();
955 Assert(mgInfo != nullptr);
956 const auto i = ranges::find(
957 mgInfo->lastParticipants,
958 user,
959 [](not_null<UserData*> user) { return user.get(); });
960 if (i != mgInfo->lastParticipants.end()) {
961 mgInfo->lastParticipants.erase(i);
962 session().changes().peerUpdated(
963 peer,
964 Data::PeerUpdate::Flag::Members);
965 }
966 owner().removeMegagroupParticipant(megagroup, user);
967 if (megagroup->membersCount() > 1) {
968 megagroup->setMembersCount(
969 megagroup->membersCount() - 1);
970 } else {
971 mgInfo->lastParticipantsStatus
972 |= MegagroupInfo::LastParticipantsCountOutdated;
973 mgInfo->lastParticipantsCount = 0;
974 }
975 if (mgInfo->lastAdmins.contains(user)) {
976 mgInfo->lastAdmins.remove(user);
977 if (megagroup->adminsCount() > 1) {
978 megagroup->setAdminsCount(
979 megagroup->adminsCount() - 1);
980 }
981 session().changes().peerUpdated(
982 peer,
983 Data::PeerUpdate::Flag::Admins);
984 }
985 mgInfo->bots.remove(user);
986 if (mgInfo->bots.empty() && mgInfo->botStatus > 0) {
987 mgInfo->botStatus = -1;
988 }
989 }
990 Data::ChannelAdminChanges(megagroup).remove(uid);
991 }
992 }, [&](const MTPDmessageActionChatEditPhoto &data) {
993 data.vphoto().match([&](const MTPDphoto &data) {
994 using Flag = MTPDchatPhoto::Flag;
995 const auto photo = owner().processPhoto(data);
996 photo->peer = peer;
997 const auto chatPhoto = MTP_chatPhoto(
998 MTP_flags((photo->hasVideo() ? Flag::f_has_video : Flag(0))
999 | (photo->inlineThumbnailBytes().isEmpty()
1000 ? Flag(0)
1001 : Flag::f_stripped_thumb)),
1002 MTP_long(photo->id),
1003 MTP_bytes(photo->inlineThumbnailBytes()),
1004 data.vdc_id());
1005 if (const auto chat = peer->asChat()) {
1006 chat->setPhoto(chatPhoto);
1007 } else if (const auto channel = peer->asChannel()) {
1008 channel->setPhoto(chatPhoto);
1009 }
1010 peer->loadUserpic();
1011 }, [&](const MTPDphotoEmpty &data) {
1012 if (const auto chat = peer->asChat()) {
1013 chat->setPhoto(MTP_chatPhotoEmpty());
1014 } else if (const auto channel = peer->asChannel()) {
1015 channel->setPhoto(MTP_chatPhotoEmpty());
1016 }
1017 });
1018 }, [&](const MTPDmessageActionChatEditTitle &data) {
1019 if (const auto chat = peer->asChat()) {
1020 chat->setName(qs(data.vtitle()));
1021 }
1022 }, [&](const MTPDmessageActionChatMigrateTo &data) {
1023 if (const auto chat = peer->asChat()) {
1024 chat->addFlags(ChatDataFlag::Deactivated);
1025 if (const auto channel = owner().channelLoaded(
1026 data.vchannel_id().v)) {
1027 Data::ApplyMigration(chat, channel);
1028 }
1029 }
1030 }, [&](const MTPDmessageActionChannelMigrateFrom &data) {
1031 if (const auto channel = peer->asChannel()) {
1032 channel->addFlags(ChannelDataFlag::Megagroup);
1033 if (const auto chat = owner().chatLoaded(data.vchat_id().v)) {
1034 Data::ApplyMigration(chat, channel);
1035 }
1036 }
1037 }, [&](const MTPDmessageActionPinMessage &data) {
1038 if (replyTo) {
1039 replyTo->match([&](const MTPDmessageReplyHeader &data) {
1040 const auto id = data.vreply_to_msg_id().v;
1041 if (item) {
1042 session().storage().add(Storage::SharedMediaAddSlice(
1043 peer->id,
1044 Storage::SharedMediaType::Pinned,
1045 { id },
1046 { id, ServerMaxMsgId }));
1047 setHasPinnedMessages(true);
1048 }
1049 });
1050 }
1051 }, [&](const MTPDmessageActionGroupCall &data) {
1052 if (const auto channel = peer->asChannel()) {
1053 channel->setGroupCall(data.vcall());
1054 } else if (const auto chat = peer->asChat()) {
1055 chat->setGroupCall(data.vcall());
1056 }
1057 }, [&](const MTPDmessageActionGroupCallScheduled &data) {
1058 if (const auto channel = peer->asChannel()) {
1059 channel->setGroupCall(data.vcall(), data.vschedule_date().v);
1060 } else if (const auto chat = peer->asChat()) {
1061 chat->setGroupCall(data.vcall(), data.vschedule_date().v);
1062 }
1063 }, [&](const MTPDmessageActionPaymentSent &data) {
1064 if (const auto payment = item->Get<HistoryServicePayment>()) {
1065 if (const auto message = payment->msg) {
1066 if (const auto media = message->media()) {
1067 if (const auto invoice = media->invoice()) {
1068 using Payments::CheckoutProcess;
1069 if (CheckoutProcess::TakePaymentStarted(message)) {
1070 // Toast on a current active window.
1071 Ui::ShowMultilineToast({
1072 .text = tr::lng_payments_success(
1073 tr::now,
1074 lt_amount,
1075 Ui::Text::Bold(payment->amount),
1076 lt_title,
1077 Ui::Text::Bold(invoice->title),
1078 Ui::Text::WithEntities),
1079 });
1080 }
1081 }
1082 }
1083 }
1084 }
1085 }, [&](const MTPDmessageActionSetChatTheme &data) {
1086 peer->setThemeEmoji(qs(data.vemoticon()));
1087 }, [&](const MTPDmessageActionChatJoinedByRequest &data) {
1088 processJoinedPeer(item->from());
1089 }, [](const auto &) {
1090 });
1091 }
1092
mainViewRemoved(not_null<HistoryBlock * > block,not_null<HistoryView::Element * > view)1093 void History::mainViewRemoved(
1094 not_null<HistoryBlock*> block,
1095 not_null<HistoryView::Element*> view) {
1096 Expects(_joinedMessage != view->data());
1097
1098 if (_firstUnreadView == view) {
1099 getNextFirstUnreadMessage();
1100 }
1101 if (_unreadBarView == view) {
1102 _unreadBarView = nullptr;
1103 }
1104 if (scrollTopItem == view) {
1105 getNextScrollTopItem(block, view->indexInBlock());
1106 }
1107 }
1108
newItemAdded(not_null<HistoryItem * > item)1109 void History::newItemAdded(not_null<HistoryItem*> item) {
1110 item->indexAsNewItem();
1111 if (const auto from = item->from() ? item->from()->asUser() : nullptr) {
1112 if (from == item->author()) {
1113 _sendActionPainter.clear(from);
1114 owner().sendActionManager().repliesPaintersClear(this, from);
1115 }
1116 from->madeAction(item->date());
1117 }
1118 item->contributeToSlowmode();
1119 if (item->showNotification()) {
1120 _notifications.push_back(item);
1121 }
1122 owner().notifyNewItemAdded(item);
1123 const auto stillShow = item->showNotification(); // Could be read already.
1124 if (stillShow) {
1125 Core::App().notifications().schedule(item);
1126 if (!item->out() && item->unread()) {
1127 if (unreadCountKnown()) {
1128 setUnreadCount(unreadCount() + 1);
1129 } else {
1130 owner().histories().requestDialogEntry(this);
1131 }
1132 }
1133 } else if (item->out()) {
1134 destroyUnreadBar();
1135 } else if (!item->unread()) {
1136 inboxRead(item);
1137 }
1138 if (item->out() && !item->unread()) {
1139 outboxRead(item);
1140 }
1141 item->incrementReplyToTopCounter();
1142 if (!folderKnown()) {
1143 owner().histories().requestDialogEntry(this);
1144 }
1145 }
1146
registerClientSideMessage(not_null<HistoryItem * > item)1147 void History::registerClientSideMessage(not_null<HistoryItem*> item) {
1148 Expects(item->isHistoryEntry());
1149 Expects(IsClientMsgId(item->id));
1150
1151 _clientSideMessages.emplace(item);
1152 session().changes().historyUpdated(this, UpdateFlag::ClientSideMessages);
1153 }
1154
unregisterClientSideMessage(not_null<HistoryItem * > item)1155 void History::unregisterClientSideMessage(not_null<HistoryItem*> item) {
1156 const auto removed = _clientSideMessages.remove(item);
1157 Assert(removed);
1158
1159 session().changes().historyUpdated(this, UpdateFlag::ClientSideMessages);
1160 }
1161
clientSideMessages()1162 const base::flat_set<not_null<HistoryItem*>> &History::clientSideMessages() {
1163 return _clientSideMessages;
1164 }
1165
latestSendingMessage() const1166 HistoryItem *History::latestSendingMessage() const {
1167 auto sending = ranges::views::all(
1168 _clientSideMessages
1169 ) | ranges::views::filter([](not_null<HistoryItem*> item) {
1170 return item->isSending();
1171 });
1172 const auto i = ranges::max_element(sending, ranges::less(), [](
1173 not_null<HistoryItem*> item) {
1174 return std::pair(item->date(), item->id.bare);
1175 });
1176 return (i == sending.end()) ? nullptr : i->get();
1177 }
1178
prepareBlockForAddingItem()1179 HistoryBlock *History::prepareBlockForAddingItem() {
1180 if (isBuildingFrontBlock()) {
1181 if (_buildingFrontBlock->block) {
1182 return _buildingFrontBlock->block;
1183 }
1184
1185 blocks.push_front(std::make_unique<HistoryBlock>(this));
1186 for (auto i = 0, l = int(blocks.size()); i != l; ++i) {
1187 blocks[i]->setIndexInHistory(i);
1188 }
1189 _buildingFrontBlock->block = blocks.front().get();
1190 if (_buildingFrontBlock->expectedItemsCount > 0) {
1191 _buildingFrontBlock->block->messages.reserve(
1192 _buildingFrontBlock->expectedItemsCount + 1);
1193 }
1194 return _buildingFrontBlock->block;
1195 }
1196
1197 const auto addNewBlock = blocks.empty()
1198 || (blocks.back()->messages.size() >= kNewBlockEachMessage);
1199 if (addNewBlock) {
1200 blocks.push_back(std::make_unique<HistoryBlock>(this));
1201 blocks.back()->setIndexInHistory(blocks.size() - 1);
1202 blocks.back()->messages.reserve(kNewBlockEachMessage);
1203 }
1204 return blocks.back().get();
1205 }
1206
viewReplaced(not_null<const Element * > was,Element * now)1207 void History::viewReplaced(not_null<const Element*> was, Element *now) {
1208 if (scrollTopItem == was) scrollTopItem = now;
1209 if (_firstUnreadView == was) _firstUnreadView = now;
1210 if (_unreadBarView == was) _unreadBarView = now;
1211 }
1212
addItemToBlock(not_null<HistoryItem * > item)1213 void History::addItemToBlock(not_null<HistoryItem*> item) {
1214 Expects(!item->mainView());
1215
1216 auto block = prepareBlockForAddingItem();
1217
1218 block->messages.push_back(item->createView(
1219 HistoryInner::ElementDelegate()));
1220 const auto view = block->messages.back().get();
1221 view->attachToBlock(block, block->messages.size() - 1);
1222
1223 if (isBuildingFrontBlock() && _buildingFrontBlock->expectedItemsCount > 0) {
1224 --_buildingFrontBlock->expectedItemsCount;
1225 }
1226 }
1227
addEdgesToSharedMedia()1228 void History::addEdgesToSharedMedia() {
1229 auto from = loadedAtTop() ? 0 : minMsgId();
1230 auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
1231 for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
1232 const auto type = static_cast<Storage::SharedMediaType>(i);
1233 session().storage().add(Storage::SharedMediaAddSlice(
1234 peer->id,
1235 type,
1236 {},
1237 { from, till }));
1238 }
1239 }
1240
addOlderSlice(const QVector<MTPMessage> & slice)1241 void History::addOlderSlice(const QVector<MTPMessage> &slice) {
1242 if (slice.isEmpty()) {
1243 _loadedAtTop = true;
1244 checkLocalMessages();
1245 return;
1246 }
1247
1248 if (const auto added = createItems(slice); !added.empty()) {
1249 addCreatedOlderSlice(added);
1250 } else {
1251 // If no items were added it means we've loaded everything old.
1252 _loadedAtTop = true;
1253 addEdgesToSharedMedia();
1254 }
1255 checkLocalMessages();
1256 checkLastMessage();
1257 }
1258
addCreatedOlderSlice(const std::vector<not_null<HistoryItem * >> & items)1259 void History::addCreatedOlderSlice(
1260 const std::vector<not_null<HistoryItem*>> &items) {
1261 startBuildingFrontBlock(items.size());
1262 for (const auto &item : items) {
1263 addItemToBlock(item);
1264 }
1265 finishBuildingFrontBlock();
1266
1267 if (loadedAtBottom()) {
1268 // Add photos to overview and authors to lastAuthors.
1269 addItemsToLists(items);
1270 }
1271 addToSharedMedia(items);
1272 }
1273
addNewerSlice(const QVector<MTPMessage> & slice)1274 void History::addNewerSlice(const QVector<MTPMessage> &slice) {
1275 bool wasLoadedAtBottom = loadedAtBottom();
1276
1277 if (slice.isEmpty()) {
1278 _loadedAtBottom = true;
1279 if (!lastMessage()) {
1280 setLastMessage(lastAvailableMessage());
1281 }
1282 }
1283
1284 if (const auto added = createItems(slice); !added.empty()) {
1285 Assert(!isBuildingFrontBlock());
1286
1287 for (const auto &item : added) {
1288 addItemToBlock(item);
1289 }
1290
1291 addToSharedMedia(added);
1292 } else {
1293 _loadedAtBottom = true;
1294 setLastMessage(lastAvailableMessage());
1295 addEdgesToSharedMedia();
1296 }
1297
1298 if (!wasLoadedAtBottom) {
1299 checkAddAllToUnreadMentions();
1300 }
1301
1302 checkLocalMessages();
1303 checkLastMessage();
1304 }
1305
checkLastMessage()1306 void History::checkLastMessage() {
1307 if (const auto last = lastMessage()) {
1308 if (!_loadedAtBottom && last->mainView()) {
1309 _loadedAtBottom = true;
1310 checkAddAllToUnreadMentions();
1311 }
1312 } else if (_loadedAtBottom) {
1313 setLastMessage(lastAvailableMessage());
1314 }
1315 }
1316
addItemsToLists(const std::vector<not_null<HistoryItem * >> & items)1317 void History::addItemsToLists(
1318 const std::vector<not_null<HistoryItem*>> &items) {
1319 std::deque<not_null<UserData*>> *lastAuthors = nullptr;
1320 base::flat_set<not_null<PeerData*>> *markupSenders = nullptr;
1321 if (peer->isChat()) {
1322 lastAuthors = &peer->asChat()->lastAuthors;
1323 markupSenders = &peer->asChat()->markupSenders;
1324 } else if (peer->isMegagroup()) {
1325 // We don't add users to mgInfo->lastParticipants here.
1326 // We're scrolling back and we see messages from users that
1327 // could be gone from the megagroup already. It is fine for
1328 // chat->lastAuthors, because they're used only for field
1329 // autocomplete, but this is bad for megagroups, because its
1330 // lastParticipants are displayed in Profile as members list.
1331 markupSenders = &peer->asChannel()->mgInfo->markupSenders;
1332 }
1333 for (const auto &item : ranges::views::reverse(items)) {
1334 item->addToUnreadMentions(UnreadMentionType::Existing);
1335 if (item->from()->id) {
1336 if (lastAuthors) { // chats
1337 if (auto user = item->from()->asUser()) {
1338 if (!base::contains(*lastAuthors, user)) {
1339 lastAuthors->push_back(user);
1340 }
1341 }
1342 }
1343 }
1344 if (item->author()->id) {
1345 if (markupSenders) { // chats with bots
1346 if (!lastKeyboardInited && item->definesReplyKeyboard() && !item->out()) {
1347 const auto markupFlags = item->replyKeyboardFlags();
1348 if (!(markupFlags & ReplyMarkupFlag::Selective) || item->mentionsMe()) {
1349 bool wasKeyboardHide = markupSenders->contains(item->author());
1350 if (!wasKeyboardHide) {
1351 markupSenders->insert(item->author());
1352 }
1353 if (!(markupFlags & ReplyMarkupFlag::None)) {
1354 if (!lastKeyboardInited) {
1355 bool botNotInChat = false;
1356 if (peer->isChat()) {
1357 botNotInChat = (!peer->canWrite() || !peer->asChat()->participants.empty()) && item->author()->isUser() && !peer->asChat()->participants.contains(item->author()->asUser());
1358 } else if (peer->isMegagroup()) {
1359 botNotInChat = (!peer->canWrite() || peer->asChannel()->mgInfo->botStatus != 0) && item->author()->isUser() && !peer->asChannel()->mgInfo->bots.contains(item->author()->asUser());
1360 }
1361 if (wasKeyboardHide || botNotInChat) {
1362 clearLastKeyboard();
1363 } else {
1364 lastKeyboardInited = true;
1365 lastKeyboardId = item->id;
1366 lastKeyboardFrom = item->author()->id;
1367 lastKeyboardUsed = false;
1368 }
1369 }
1370 }
1371 }
1372 }
1373 } else if (!lastKeyboardInited && item->definesReplyKeyboard() && !item->out()) { // conversations with bots
1374 const auto markupFlags = item->replyKeyboardFlags();
1375 if (!(markupFlags & ReplyMarkupFlag::Selective) || item->mentionsMe()) {
1376 if (markupFlags & ReplyMarkupFlag::None) {
1377 clearLastKeyboard();
1378 } else {
1379 lastKeyboardInited = true;
1380 lastKeyboardId = item->id;
1381 lastKeyboardFrom = item->author()->id;
1382 lastKeyboardUsed = false;
1383 }
1384 }
1385 }
1386 }
1387 }
1388
1389 }
1390
checkAddAllToUnreadMentions()1391 void History::checkAddAllToUnreadMentions() {
1392 if (!loadedAtBottom()) {
1393 return;
1394 }
1395
1396 for (const auto &block : blocks) {
1397 for (const auto &message : block->messages) {
1398 const auto item = message->data();
1399 item->addToUnreadMentions(UnreadMentionType::Existing);
1400 }
1401 }
1402 }
1403
addToSharedMedia(const std::vector<not_null<HistoryItem * >> & items)1404 void History::addToSharedMedia(
1405 const std::vector<not_null<HistoryItem*>> &items) {
1406 std::vector<MsgId> medias[Storage::kSharedMediaTypeCount];
1407 for (const auto &item : items) {
1408 if (const auto types = item->sharedMediaTypes()) {
1409 for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
1410 const auto type = static_cast<Storage::SharedMediaType>(i);
1411 if (types.test(type)) {
1412 if (medias[i].empty()) {
1413 medias[i].reserve(items.size());
1414 }
1415 medias[i].push_back(item->id);
1416 }
1417 }
1418 }
1419 }
1420 const auto from = loadedAtTop() ? 0 : minMsgId();
1421 const auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
1422 for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
1423 if (!medias[i].empty()) {
1424 const auto type = static_cast<Storage::SharedMediaType>(i);
1425 session().storage().add(Storage::SharedMediaAddSlice(
1426 peer->id,
1427 type,
1428 std::move(medias[i]),
1429 { from, till }));
1430 if (type == Storage::SharedMediaType::Pinned) {
1431 setHasPinnedMessages(true);
1432 }
1433 }
1434 }
1435 }
1436
calculateFirstUnreadMessage()1437 void History::calculateFirstUnreadMessage() {
1438 if (!_inboxReadBefore) {
1439 return;
1440 }
1441
1442 _firstUnreadView = nullptr;
1443 if (!unreadCount() || !trackUnreadMessages()) {
1444 return;
1445 }
1446 for (const auto &block : ranges::views::reverse(blocks)) {
1447 for (const auto &message : ranges::views::reverse(block->messages)) {
1448 const auto item = message->data();
1449 if (!item->isRegular()) {
1450 continue;
1451 } else if (!item->out()) {
1452 if (item->id >= *_inboxReadBefore) {
1453 _firstUnreadView = message.get();
1454 } else {
1455 return;
1456 }
1457 }
1458 }
1459 }
1460 }
1461
readInboxTillNeedsRequest(MsgId tillId)1462 bool History::readInboxTillNeedsRequest(MsgId tillId) {
1463 Expects(!tillId || IsServerMsgId(tillId));
1464
1465 readClientSideMessages();
1466 if (unreadMark()) {
1467 owner().histories().changeDialogUnreadMark(this, false);
1468 }
1469 DEBUG_LOG(("Reading: readInboxTillNeedsRequest is_server %1, before %2."
1470 ).arg(Logs::b(IsServerMsgId(tillId))
1471 ).arg(_inboxReadBefore.value_or(-666).bare));
1472 return IsServerMsgId(tillId) && (_inboxReadBefore.value_or(1) <= tillId);
1473 }
1474
readClientSideMessages()1475 void History::readClientSideMessages() {
1476 auto &histories = owner().histories();
1477 for (const auto &item : _clientSideMessages) {
1478 histories.readClientSideMessage(item);
1479 }
1480 }
1481
unreadCountRefreshNeeded(MsgId readTillId) const1482 bool History::unreadCountRefreshNeeded(MsgId readTillId) const {
1483 return !unreadCountKnown()
1484 || ((readTillId + 1) > _inboxReadBefore.value_or(0));
1485 }
1486
countStillUnreadLocal(MsgId readTillId) const1487 std::optional<int> History::countStillUnreadLocal(MsgId readTillId) const {
1488 if (isEmpty() || !folderKnown()) {
1489 DEBUG_LOG(("Reading: countStillUnreadLocal unknown %1 and %2.").arg(
1490 Logs::b(isEmpty()),
1491 Logs::b(folderKnown())));
1492 return std::nullopt;
1493 }
1494 if (_inboxReadBefore) {
1495 const auto before = *_inboxReadBefore;
1496 DEBUG_LOG(("Reading: check before %1 with min %2 and max %3."
1497 ).arg(before.bare
1498 ).arg(minMsgId().bare
1499 ).arg(maxMsgId().bare));
1500 if (minMsgId() <= before && maxMsgId() >= readTillId) {
1501 auto result = 0;
1502 for (const auto &block : blocks) {
1503 for (const auto &message : block->messages) {
1504 const auto item = message->data();
1505 if (!item->isRegular()
1506 || (item->out() && !item->isFromScheduled())) {
1507 continue;
1508 } else if (item->id > readTillId) {
1509 break;
1510 } else if (item->id >= before) {
1511 ++result;
1512 }
1513 }
1514 }
1515 DEBUG_LOG(("Reading: check before result %1 with existing %2"
1516 ).arg(result
1517 ).arg(_unreadCount.value_or(-666)));
1518 if (_unreadCount) {
1519 return std::max(*_unreadCount - result, 0);
1520 }
1521 }
1522 }
1523 const auto minimalServerId = minMsgId();
1524 DEBUG_LOG(("Reading: check at end loaded from %1 loaded %2 - %3").arg(
1525 QString::number(minimalServerId.bare),
1526 Logs::b(loadedAtBottom()),
1527 Logs::b(loadedAtTop())));
1528 if (!loadedAtBottom()
1529 || (!loadedAtTop() && !minimalServerId)
1530 || minimalServerId > readTillId) {
1531 return std::nullopt;
1532 }
1533 auto result = 0;
1534 for (const auto &block : ranges::views::reverse(blocks)) {
1535 for (const auto &message : ranges::views::reverse(block->messages)) {
1536 const auto item = message->data();
1537 if (item->isRegular()) {
1538 if (item->id <= readTillId) {
1539 return result;
1540 } else if (!item->out()) {
1541 ++result;
1542 }
1543 }
1544 }
1545 }
1546 DEBUG_LOG(("Reading: check at end counted %1").arg(result));
1547 return result;
1548 }
1549
applyInboxReadUpdate(FolderId folderId,MsgId upTo,int stillUnread,int32 channelPts)1550 void History::applyInboxReadUpdate(
1551 FolderId folderId,
1552 MsgId upTo,
1553 int stillUnread,
1554 int32 channelPts) {
1555 const auto folder = folderId ? owner().folderLoaded(folderId) : nullptr;
1556 if (folder && this->folder() != folder) {
1557 // If history folder is unknown or not synced, request both.
1558 owner().histories().requestDialogEntry(this);
1559 owner().histories().requestDialogEntry(folder);
1560 }
1561 if (_inboxReadBefore.value_or(1) <= upTo) {
1562 if (!peer->isChannel() || peer->asChannel()->pts() == channelPts) {
1563 inboxRead(upTo, stillUnread);
1564 } else {
1565 inboxRead(upTo);
1566 }
1567 }
1568 }
1569
inboxRead(MsgId upTo,std::optional<int> stillUnread)1570 void History::inboxRead(MsgId upTo, std::optional<int> stillUnread) {
1571 if (stillUnread.has_value() && folderKnown()) {
1572 setUnreadCount(*stillUnread);
1573 } else if (const auto still = countStillUnreadLocal(upTo)) {
1574 setUnreadCount(*still);
1575 } else {
1576 owner().histories().requestDialogEntry(this);
1577 }
1578 setInboxReadTill(upTo);
1579 updateChatListEntry();
1580 if (const auto to = peer->migrateTo()) {
1581 if (const auto migrated = peer->owner().historyLoaded(to->id)) {
1582 migrated->updateChatListEntry();
1583 }
1584 }
1585
1586 _firstUnreadView = nullptr;
1587 Core::App().notifications().clearIncomingFromHistory(this);
1588 }
1589
inboxRead(not_null<const HistoryItem * > wasRead)1590 void History::inboxRead(not_null<const HistoryItem*> wasRead) {
1591 if (wasRead->isRegular()) {
1592 inboxRead(wasRead->id);
1593 }
1594 }
1595
outboxRead(MsgId upTo)1596 void History::outboxRead(MsgId upTo) {
1597 setOutboxReadTill(upTo);
1598 if (const auto last = chatListMessage()) {
1599 if (last->out() && last->isRegular() && last->id <= upTo) {
1600 session().changes().messageUpdated(
1601 last,
1602 Data::MessageUpdate::Flag::DialogRowRepaint);
1603 }
1604 }
1605 updateChatListEntry();
1606 session().changes().historyUpdated(this, UpdateFlag::OutboxRead);
1607 }
1608
outboxRead(not_null<const HistoryItem * > wasRead)1609 void History::outboxRead(not_null<const HistoryItem*> wasRead) {
1610 if (wasRead->isRegular()) {
1611 outboxRead(wasRead->id);
1612 }
1613 }
1614
loadAroundId() const1615 MsgId History::loadAroundId() const {
1616 if (_unreadCount && *_unreadCount > 0 && _inboxReadBefore) {
1617 return *_inboxReadBefore;
1618 }
1619 return MsgId(0);
1620 }
1621
inboxReadTillId() const1622 MsgId History::inboxReadTillId() const {
1623 return _inboxReadBefore.value_or(1) - 1;
1624 }
1625
outboxReadTillId() const1626 MsgId History::outboxReadTillId() const {
1627 return _outboxReadBefore.value_or(1) - 1;
1628 }
1629
lastAvailableMessage() const1630 HistoryItem *History::lastAvailableMessage() const {
1631 return isEmpty() ? nullptr : blocks.back()->messages.back()->data().get();
1632 }
1633
unreadCount() const1634 int History::unreadCount() const {
1635 return _unreadCount ? *_unreadCount : 0;
1636 }
1637
unreadCountForBadge() const1638 int History::unreadCountForBadge() const {
1639 const auto result = unreadCount();
1640 return (!result && unreadMark()) ? 1 : result;
1641 }
1642
unreadCountKnown() const1643 bool History::unreadCountKnown() const {
1644 return _unreadCount.has_value();
1645 }
1646
setUnreadCount(int newUnreadCount)1647 void History::setUnreadCount(int newUnreadCount) {
1648 Expects(folderKnown());
1649
1650 if (_unreadCount == newUnreadCount) {
1651 return;
1652 }
1653 const auto wasForBadge = (unreadCountForBadge() > 0);
1654 const auto refresher = gsl::finally([&] {
1655 if (wasForBadge != (unreadCountForBadge() > 0)) {
1656 owner().chatsFilters().refreshHistory(this);
1657 }
1658 session().changes().historyUpdated(this, UpdateFlag::UnreadView);
1659 });
1660 const auto notifier = unreadStateChangeNotifier(true);
1661 _unreadCount = newUnreadCount;
1662
1663 const auto lastOutgoing = [&] {
1664 const auto last = lastMessage();
1665 return last
1666 && last->isRegular()
1667 && loadedAtBottom()
1668 && !isEmpty()
1669 && blocks.back()->messages.back()->data() == last
1670 && last->out();
1671 }();
1672 if (newUnreadCount == 1 && !lastOutgoing) {
1673 if (loadedAtBottom()) {
1674 _firstUnreadView = !isEmpty()
1675 ? blocks.back()->messages.back().get()
1676 : nullptr;
1677 }
1678 if (const auto last = msgIdForRead()) {
1679 setInboxReadTill(last - 1);
1680 }
1681 } else if (!newUnreadCount) {
1682 _firstUnreadView = nullptr;
1683 if (const auto last = msgIdForRead()) {
1684 setInboxReadTill(last);
1685 }
1686 } else if (!_firstUnreadView && !_unreadBarView && loadedAtBottom()) {
1687 calculateFirstUnreadMessage();
1688 }
1689 }
1690
setUnreadMark(bool unread)1691 void History::setUnreadMark(bool unread) {
1692 if (clearUnreadOnClientSide()) {
1693 unread = false;
1694 }
1695 if (_unreadMark == unread) {
1696 return;
1697 }
1698 const auto noUnreadMessages = !unreadCount();
1699 const auto refresher = gsl::finally([&] {
1700 if (inChatList() && noUnreadMessages) {
1701 owner().chatsFilters().refreshHistory(this);
1702 updateChatListEntry();
1703 }
1704 session().changes().historyUpdated(this, UpdateFlag::UnreadView);
1705 });
1706 const auto notifier = unreadStateChangeNotifier(noUnreadMessages);
1707 _unreadMark = unread;
1708 }
1709
unreadMark() const1710 bool History::unreadMark() const {
1711 return _unreadMark;
1712 }
1713
setFakeUnreadWhileOpened(bool enabled)1714 void History::setFakeUnreadWhileOpened(bool enabled) {
1715 if (_fakeUnreadWhileOpened == enabled
1716 || (enabled
1717 && (!inChatList()
1718 || (!unreadCount()
1719 && !unreadMark()
1720 && !hasUnreadMentions())))) {
1721 return;
1722 }
1723 _fakeUnreadWhileOpened = enabled;
1724 owner().chatsFilters().refreshHistory(this);
1725 }
1726
fakeUnreadWhileOpened() const1727 [[nodiscard]] bool History::fakeUnreadWhileOpened() const {
1728 return _fakeUnreadWhileOpened;
1729 }
1730
mute() const1731 bool History::mute() const {
1732 return _mute;
1733 }
1734
changeMute(bool newMute)1735 bool History::changeMute(bool newMute) {
1736 if (_mute == newMute) {
1737 return false;
1738 }
1739 const auto refresher = gsl::finally([&] {
1740 if (inChatList()) {
1741 owner().chatsFilters().refreshHistory(this);
1742 updateChatListEntry();
1743 }
1744 session().changes().peerUpdated(
1745 peer,
1746 Data::PeerUpdate::Flag::Notifications);
1747 });
1748 const auto notify = (unreadCountForBadge() > 0);
1749 const auto notifier = unreadStateChangeNotifier(notify);
1750 _mute = newMute;
1751 return true;
1752 }
1753
getNextFirstUnreadMessage()1754 void History::getNextFirstUnreadMessage() {
1755 Expects(_firstUnreadView != nullptr);
1756
1757 const auto block = _firstUnreadView->block();
1758 const auto index = _firstUnreadView->indexInBlock();
1759 const auto setFromMessage = [&](const auto &view) {
1760 if (view->data()->isRegular()) {
1761 _firstUnreadView = view.get();
1762 return true;
1763 }
1764 return false;
1765 };
1766 if (index >= 0) {
1767 const auto count = int(block->messages.size());
1768 for (auto i = index + 1; i != count; ++i) {
1769 const auto &message = block->messages[i];
1770 if (setFromMessage(message)) {
1771 return;
1772 }
1773 }
1774 }
1775
1776 const auto count = int(blocks.size());
1777 for (auto j = block->indexInHistory() + 1; j != count; ++j) {
1778 for (const auto &message : blocks[j]->messages) {
1779 if (setFromMessage(message)) {
1780 return;
1781 }
1782 }
1783 }
1784 _firstUnreadView = nullptr;
1785 }
1786
nextNonHistoryEntryId()1787 MsgId History::nextNonHistoryEntryId() {
1788 return owner().nextNonHistoryEntryId();
1789 }
1790
folderKnown() const1791 bool History::folderKnown() const {
1792 return _folder.has_value();
1793 }
1794
folder() const1795 Data::Folder *History::folder() const {
1796 return _folder.value_or(nullptr);
1797 }
1798
setFolder(not_null<Data::Folder * > folder,HistoryItem * folderDialogItem)1799 void History::setFolder(
1800 not_null<Data::Folder*> folder,
1801 HistoryItem *folderDialogItem) {
1802 setFolderPointer(folder);
1803 if (folderDialogItem) {
1804 setLastServerMessage(folderDialogItem);
1805 }
1806 }
1807
clearFolder()1808 void History::clearFolder() {
1809 setFolderPointer(nullptr);
1810 }
1811
setFolderPointer(Data::Folder * folder)1812 void History::setFolderPointer(Data::Folder *folder) {
1813 if (_folder == folder) {
1814 return;
1815 }
1816 if (isPinnedDialog(FilterId())) {
1817 owner().setChatPinned(this, FilterId(), false);
1818 }
1819 const auto wasKnown = folderKnown();
1820 const auto wasInList = inChatList();
1821 if (wasInList) {
1822 removeFromChatList(0, owner().chatsList(this->folder()));
1823 }
1824 const auto was = _folder.value_or(nullptr);
1825 _folder = folder;
1826 if (was) {
1827 was->unregisterOne(this);
1828 }
1829 if (wasInList) {
1830 addToChatList(0, owner().chatsList(folder));
1831
1832 owner().chatsFilters().refreshHistory(this);
1833 updateChatListEntry();
1834
1835 owner().chatsListChanged(was);
1836 owner().chatsListChanged(folder);
1837 } else if (!wasKnown) {
1838 updateChatListSortPosition();
1839 }
1840 if (folder) {
1841 folder->registerOne(this);
1842 }
1843 session().changes().historyUpdated(this, UpdateFlag::Folder);
1844 }
1845
applyPinnedUpdate(const MTPDupdateDialogPinned & data)1846 void History::applyPinnedUpdate(const MTPDupdateDialogPinned &data) {
1847 const auto folderId = data.vfolder_id().value_or_empty();
1848 if (!folderKnown()) {
1849 if (folderId) {
1850 setFolder(owner().folder(folderId));
1851 } else {
1852 clearFolder();
1853 }
1854 }
1855 owner().setChatPinned(this, FilterId(), data.is_pinned());
1856 }
1857
adjustedChatListTimeId() const1858 TimeId History::adjustedChatListTimeId() const {
1859 const auto result = chatListTimeId();
1860 if (const auto draft = cloudDraft()) {
1861 if (!Data::draftIsNull(draft) && !session().supportMode()) {
1862 return std::max(result, draft->date);
1863 }
1864 }
1865 return result;
1866 }
1867
countScrollState(int top)1868 void History::countScrollState(int top) {
1869 std::tie(scrollTopItem, scrollTopOffset) = findItemAndOffset(top);
1870 }
1871
findItemAndOffset(int top) const1872 auto History::findItemAndOffset(int top) const -> std::pair<Element*, int> {
1873 if (const auto element = findScrollTopItem(top)) {
1874 return { element, (top - element->block()->y() - element->y()) };
1875 }
1876 return {};
1877 }
1878
findScrollTopItem(int top) const1879 auto History::findScrollTopItem(int top) const -> Element* {
1880 if (isEmpty()) {
1881 return nullptr;
1882 }
1883
1884 auto itemIndex = 0;
1885 auto blockIndex = 0;
1886 auto itemTop = 0;
1887 if (scrollTopItem) {
1888 itemIndex = scrollTopItem->indexInBlock();
1889 blockIndex = scrollTopItem->block()->indexInHistory();
1890 itemTop = blocks[blockIndex]->y() + scrollTopItem->y();
1891 }
1892 if (itemTop > top) {
1893 // go backward through history while we don't find an item that starts above
1894 do {
1895 const auto &block = blocks[blockIndex];
1896 for (--itemIndex; itemIndex >= 0; --itemIndex) {
1897 const auto view = block->messages[itemIndex].get();
1898 itemTop = block->y() + view->y();
1899 if (itemTop <= top) {
1900 return view;
1901 }
1902 }
1903 if (--blockIndex >= 0) {
1904 itemIndex = blocks[blockIndex]->messages.size();
1905 } else {
1906 break;
1907 }
1908 } while (true);
1909
1910 return blocks.front()->messages.front().get();
1911 }
1912 // go forward through history while we don't find the last item that starts above
1913 for (auto blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) {
1914 const auto &block = blocks[blockIndex];
1915 for (auto itemsCount = int(block->messages.size()); itemIndex < itemsCount; ++itemIndex) {
1916 itemTop = block->y() + block->messages[itemIndex]->y();
1917 if (itemTop > top) {
1918 Assert(itemIndex > 0 || blockIndex > 0);
1919 if (itemIndex > 0) {
1920 return block->messages[itemIndex - 1].get();
1921 }
1922 return blocks[blockIndex - 1]->messages.back().get();
1923 }
1924 }
1925 itemIndex = 0;
1926 }
1927 return blocks.back()->messages.back().get();
1928 }
1929
getNextScrollTopItem(HistoryBlock * block,int32 i)1930 void History::getNextScrollTopItem(HistoryBlock *block, int32 i) {
1931 ++i;
1932 if (i > 0 && i < block->messages.size()) {
1933 scrollTopItem = block->messages[i].get();
1934 return;
1935 }
1936 int j = block->indexInHistory() + 1;
1937 if (j > 0 && j < blocks.size()) {
1938 scrollTopItem = blocks[j]->messages.front().get();
1939 return;
1940 }
1941 scrollTopItem = nullptr;
1942 }
1943
addUnreadBar()1944 void History::addUnreadBar() {
1945 if (_unreadBarView || !_firstUnreadView || !unreadCount()) {
1946 return;
1947 }
1948 if (const auto count = chatListUnreadCount()) {
1949 _unreadBarView = _firstUnreadView;
1950 _unreadBarView->createUnreadBar(tr::lng_unread_bar_some());
1951 }
1952 }
1953
destroyUnreadBar()1954 void History::destroyUnreadBar() {
1955 if (const auto view = base::take(_unreadBarView)) {
1956 view->destroyUnreadBar();
1957 }
1958 }
1959
unsetFirstUnreadMessage()1960 void History::unsetFirstUnreadMessage() {
1961 _firstUnreadView = nullptr;
1962 }
1963
unreadBar() const1964 HistoryView::Element *History::unreadBar() const {
1965 return _unreadBarView;
1966 }
1967
firstUnreadMessage() const1968 HistoryView::Element *History::firstUnreadMessage() const {
1969 return _firstUnreadView;
1970 }
1971
addNewInTheMiddle(not_null<HistoryItem * > item,int blockIndex,int itemIndex)1972 not_null<HistoryItem*> History::addNewInTheMiddle(
1973 not_null<HistoryItem*> item,
1974 int blockIndex,
1975 int itemIndex) {
1976 Expects(blockIndex >= 0);
1977 Expects(blockIndex < blocks.size());
1978 Expects(itemIndex >= 0);
1979 Expects(itemIndex <= blocks[blockIndex]->messages.size());
1980
1981 const auto &block = blocks[blockIndex];
1982
1983 const auto it = block->messages.insert(
1984 block->messages.begin() + itemIndex,
1985 item->createView(
1986 HistoryInner::ElementDelegate()));
1987 (*it)->attachToBlock(block.get(), itemIndex);
1988 if (itemIndex + 1 < block->messages.size()) {
1989 for (auto i = itemIndex + 1, l = int(block->messages.size()); i != l; ++i) {
1990 block->messages[i]->setIndexInBlock(i);
1991 }
1992 block->messages[itemIndex + 1]->previousInBlocksChanged();
1993 } else if (blockIndex + 1 < blocks.size() && !blocks[blockIndex + 1]->messages.empty()) {
1994 blocks[blockIndex + 1]->messages.front()->previousInBlocksChanged();
1995 } else {
1996 (*it)->nextInBlocksRemoved();
1997 }
1998
1999 return item;
2000 }
2001
migrateSibling() const2002 History *History::migrateSibling() const {
2003 const auto addFromId = [&] {
2004 if (const auto from = peer->migrateFrom()) {
2005 return from->id;
2006 } else if (const auto to = peer->migrateTo()) {
2007 return to->id;
2008 }
2009 return PeerId(0);
2010 }();
2011 return owner().historyLoaded(addFromId);
2012 }
2013
chatListUnreadCount() const2014 int History::chatListUnreadCount() const {
2015 const auto result = unreadCount();
2016 if (const auto migrated = migrateSibling()) {
2017 return result + migrated->unreadCount();
2018 }
2019 return result;
2020 }
2021
chatListUnreadMark() const2022 bool History::chatListUnreadMark() const {
2023 if (unreadMark()) {
2024 return true;
2025 } else if (const auto migrated = migrateSibling()) {
2026 return migrated->unreadMark();
2027 }
2028 return false;
2029 }
2030
chatListMutedBadge() const2031 bool History::chatListMutedBadge() const {
2032 return mute();
2033 }
2034
chatListUnreadState() const2035 Dialogs::UnreadState History::chatListUnreadState() const {
2036 auto result = Dialogs::UnreadState();
2037 const auto count = _unreadCount.value_or(0);
2038 const auto mark = !count && _unreadMark;
2039 result.messages = count;
2040 result.messagesMuted = mute() ? count : 0;
2041 result.chats = count ? 1 : 0;
2042 result.chatsMuted = (count && mute()) ? 1 : 0;
2043 result.marks = mark ? 1 : 0;
2044 result.marksMuted = (mark && mute()) ? 1 : 0;
2045 result.known = _unreadCount.has_value();
2046 return result;
2047 }
2048
chatListMessage() const2049 HistoryItem *History::chatListMessage() const {
2050 return _chatListMessage.value_or(nullptr);
2051 }
2052
chatListMessageKnown() const2053 bool History::chatListMessageKnown() const {
2054 return _chatListMessage.has_value();
2055 }
2056
chatListName() const2057 const QString &History::chatListName() const {
2058 return peer->name;
2059 }
2060
chatListNameSortKey() const2061 const QString &History::chatListNameSortKey() const {
2062 return _chatListNameSortKey;
2063 }
2064
refreshChatListNameSortKey()2065 void History::refreshChatListNameSortKey() {
2066 _chatListNameSortKey = owner().nameSortKey(peer->name);
2067 }
2068
chatListNameWords() const2069 const base::flat_set<QString> &History::chatListNameWords() const {
2070 return peer->nameWords();
2071 }
2072
chatListFirstLetters() const2073 const base::flat_set<QChar> &History::chatListFirstLetters() const {
2074 return peer->nameFirstLetters();
2075 }
2076
loadUserpic()2077 void History::loadUserpic() {
2078 peer->loadUserpic();
2079 }
2080
paintUserpic(Painter & p,std::shared_ptr<Data::CloudImageView> & view,int x,int y,int size) const2081 void History::paintUserpic(
2082 Painter &p,
2083 std::shared_ptr<Data::CloudImageView> &view,
2084 int x,
2085 int y,
2086 int size) const {
2087 peer->paintUserpic(p, view, x, y, size);
2088 }
2089
startBuildingFrontBlock(int expectedItemsCount)2090 void History::startBuildingFrontBlock(int expectedItemsCount) {
2091 Assert(!isBuildingFrontBlock());
2092 Assert(expectedItemsCount > 0);
2093
2094 _buildingFrontBlock = std::make_unique<BuildingBlock>();
2095 _buildingFrontBlock->expectedItemsCount = expectedItemsCount;
2096 }
2097
finishBuildingFrontBlock()2098 void History::finishBuildingFrontBlock() {
2099 Expects(isBuildingFrontBlock());
2100
2101 // Some checks if there was some message history already
2102 if (const auto block = base::take(_buildingFrontBlock)->block) {
2103 if (blocks.size() > 1) {
2104 // ... item, item, item, last ], [ first, item, item ...
2105 const auto first = blocks[1]->messages.front().get();
2106
2107 // we've added a new front block, so previous item for
2108 // the old first item of a first block was changed
2109 first->previousInBlocksChanged();
2110 } else {
2111 block->messages.back()->nextInBlocksRemoved();
2112 }
2113 }
2114 }
2115
clearNotifications()2116 void History::clearNotifications() {
2117 _notifications.clear();
2118 }
2119
clearIncomingNotifications()2120 void History::clearIncomingNotifications() {
2121 if (!peer->isSelf()) {
2122 _notifications.erase(
2123 ranges::remove(_notifications, false, &HistoryItem::out),
2124 end(_notifications));
2125 }
2126 }
2127
loadedAtBottom() const2128 bool History::loadedAtBottom() const {
2129 return _loadedAtBottom;
2130 }
2131
loadedAtTop() const2132 bool History::loadedAtTop() const {
2133 return _loadedAtTop;
2134 }
2135
isReadyFor(MsgId msgId)2136 bool History::isReadyFor(MsgId msgId) {
2137 if (msgId < 0 && -msgId < ServerMaxMsgId && peer->migrateFrom()) {
2138 // Old group history.
2139 return owner().history(peer->migrateFrom()->id)->isReadyFor(-msgId);
2140 }
2141
2142 if (msgId == ShowAtTheEndMsgId) {
2143 return loadedAtBottom();
2144 }
2145 if (msgId == ShowAtUnreadMsgId) {
2146 if (const auto migratePeer = peer->migrateFrom()) {
2147 if (const auto migrated = owner().historyLoaded(migratePeer)) {
2148 if (migrated->unreadCount()) {
2149 return migrated->isReadyFor(msgId);
2150 }
2151 }
2152 }
2153 if (unreadCount() && _inboxReadBefore) {
2154 if (!isEmpty()) {
2155 return (loadedAtTop() || minMsgId() <= *_inboxReadBefore)
2156 && (loadedAtBottom() || maxMsgId() >= *_inboxReadBefore);
2157 }
2158 return false;
2159 }
2160 return loadedAtBottom();
2161 }
2162 const auto item = owner().message(channelId(), msgId);
2163 return item && (item->history() == this) && item->mainView();
2164 }
2165
getReadyFor(MsgId msgId)2166 void History::getReadyFor(MsgId msgId) {
2167 if (msgId < 0 && -msgId < ServerMaxMsgId && peer->migrateFrom()) {
2168 const auto migrated = owner().history(peer->migrateFrom()->id);
2169 migrated->getReadyFor(-msgId);
2170 if (migrated->isEmpty()) {
2171 clear(ClearType::Unload);
2172 }
2173 return;
2174 }
2175 if (msgId == ShowAtUnreadMsgId) {
2176 if (const auto migratePeer = peer->migrateFrom()) {
2177 if (const auto migrated = owner().historyLoaded(migratePeer)) {
2178 if (migrated->unreadCount()) {
2179 clear(ClearType::Unload);
2180 migrated->getReadyFor(msgId);
2181 return;
2182 }
2183 }
2184 }
2185 }
2186 if (!isReadyFor(msgId)) {
2187 clear(ClearType::Unload);
2188 if (const auto migratePeer = peer->migrateFrom()) {
2189 if (const auto migrated = owner().historyLoaded(migratePeer)) {
2190 migrated->clear(ClearType::Unload);
2191 }
2192 }
2193 if ((msgId == ShowAtTheEndMsgId)
2194 || (msgId == ShowAtUnreadMsgId && !unreadCount())) {
2195 _loadedAtBottom = true;
2196 }
2197 }
2198 }
2199
setNotLoadedAtBottom()2200 void History::setNotLoadedAtBottom() {
2201 _loadedAtBottom = false;
2202
2203 session().storage().invalidate(
2204 Storage::SharedMediaInvalidateBottom(peer->id));
2205 }
2206
clearSharedMedia()2207 void History::clearSharedMedia() {
2208 session().storage().remove(
2209 Storage::SharedMediaRemoveAll(peer->id));
2210 }
2211
setLastServerMessage(HistoryItem * item)2212 void History::setLastServerMessage(HistoryItem *item) {
2213 _lastServerMessage = item;
2214 if (_lastMessage
2215 && *_lastMessage
2216 && !(*_lastMessage)->isRegular()
2217 && (!item || (*_lastMessage)->date() > item->date())) {
2218 return;
2219 }
2220 setLastMessage(item);
2221 }
2222
setLastMessage(HistoryItem * item)2223 void History::setLastMessage(HistoryItem *item) {
2224 if (_lastMessage && *_lastMessage == item) {
2225 return;
2226 }
2227 _lastMessage = item;
2228 if (!item || item->isRegular()) {
2229 _lastServerMessage = item;
2230 }
2231 if (peer->migrateTo()) {
2232 // We don't want to request last message for all deactivated chats.
2233 // This is a heavy request for them, because we need to get last
2234 // two items by messages.getHistory to skip the migration message.
2235 setChatListMessageUnknown();
2236 } else {
2237 setChatListMessageFromLast();
2238 if (!chatListMessageKnown()) {
2239 setFakeChatListMessage();
2240 }
2241 }
2242 }
2243
refreshChatListMessage()2244 void History::refreshChatListMessage() {
2245 const auto known = chatListMessageKnown();
2246 setChatListMessageFromLast();
2247 if (known && !_chatListMessage) {
2248 requestChatListMessage();
2249 }
2250 }
2251
setChatListMessage(HistoryItem * item)2252 void History::setChatListMessage(HistoryItem *item) {
2253 if (_chatListMessage && *_chatListMessage == item) {
2254 return;
2255 }
2256 const auto was = _chatListMessage.value_or(nullptr);
2257 if (item) {
2258 if (item->isSponsored()) {
2259 return;
2260 }
2261 if (_chatListMessage
2262 && *_chatListMessage
2263 && !(*_chatListMessage)->isRegular()
2264 && (*_chatListMessage)->date() > item->date()) {
2265 return;
2266 }
2267 _chatListMessage = item;
2268 setChatListTimeId(item->date());
2269
2270 // If we have a single message from a group, request the full album.
2271 if (hasOrphanMediaGroupPart()
2272 && !item->toPreview({
2273 .hideSender = true,
2274 .hideCaption = true }).images.empty()) {
2275 owner().histories().requestGroupAround(item);
2276 }
2277 } else if (!_chatListMessage || *_chatListMessage) {
2278 _chatListMessage = nullptr;
2279 updateChatListEntry();
2280 }
2281 if (const auto folder = this->folder()) {
2282 folder->oneListMessageChanged(was, item);
2283 }
2284 if (const auto to = peer->migrateTo()) {
2285 if (const auto history = owner().historyLoaded(to)) {
2286 if (!history->chatListMessageKnown()) {
2287 history->requestChatListMessage();
2288 }
2289 }
2290 }
2291 }
2292
computeChatListMessageFromLast() const2293 auto History::computeChatListMessageFromLast() const
2294 -> std::optional<HistoryItem*> {
2295 if (!_lastMessage) {
2296 return _lastMessage;
2297 }
2298
2299 // In migrated groups we want to skip essential message
2300 // about migration in the chats list and display the last
2301 // non-migration message from the original legacy group.
2302 const auto last = lastMessage();
2303 if (!last || !last->isGroupMigrate()) {
2304 return _lastMessage;
2305 }
2306 if (const auto chat = peer->asChat()) {
2307 // In chats we try to take the item before the 'last', which
2308 // is the empty-displayed migration message.
2309 if (!loadedAtBottom()) {
2310 // We don't know the tail of the history.
2311 return std::nullopt;
2312 }
2313 const auto before = [&]() -> HistoryItem* {
2314 for (const auto &block : ranges::views::reverse(blocks)) {
2315 const auto &messages = block->messages;
2316 for (const auto &item : ranges::views::reverse(messages)) {
2317 if (item->data() != last) {
2318 return item->data();
2319 }
2320 }
2321 }
2322 return nullptr;
2323 }();
2324 if (before) {
2325 // We found a message that is not the migration one.
2326 return before;
2327 } else if (loadedAtTop()) {
2328 // No other messages in this history.
2329 return _lastMessage;
2330 }
2331 return std::nullopt;
2332 } else if (const auto from = migrateFrom()) {
2333 // In megagroups we just try to use
2334 // the message from the original group.
2335 return from->chatListMessageKnown()
2336 ? std::make_optional(from->chatListMessage())
2337 : std::nullopt;
2338 }
2339 return _lastMessage;
2340 }
2341
setChatListMessageFromLast()2342 void History::setChatListMessageFromLast() {
2343 if (const auto good = computeChatListMessageFromLast()) {
2344 setChatListMessage(*good);
2345 } else {
2346 setChatListMessageUnknown();
2347 }
2348 }
2349
setChatListMessageUnknown()2350 void History::setChatListMessageUnknown() {
2351 if (!_chatListMessage.has_value()) {
2352 return;
2353 }
2354 const auto was = *_chatListMessage;
2355 _chatListMessage = std::nullopt;
2356 if (const auto folder = this->folder()) {
2357 folder->oneListMessageChanged(was, nullptr);
2358 }
2359 }
2360
requestChatListMessage()2361 void History::requestChatListMessage() {
2362 if (!lastMessageKnown()) {
2363 owner().histories().requestDialogEntry(this, [=] {
2364 requestChatListMessage();
2365 });
2366 return;
2367 } else if (chatListMessageKnown()) {
2368 return;
2369 }
2370 setChatListMessageFromLast();
2371 if (!chatListMessageKnown()) {
2372 setFakeChatListMessage();
2373 }
2374 }
2375
setFakeChatListMessage()2376 void History::setFakeChatListMessage() {
2377 if (const auto chat = peer->asChat()) {
2378 // In chats we try to take the item before the 'last', which
2379 // is the empty-displayed migration message.
2380 owner().histories().requestFakeChatListMessage(this);
2381 } else if (const auto from = migrateFrom()) {
2382 // In megagroups we just try to use
2383 // the message from the original group.
2384 from->requestChatListMessage();
2385 }
2386 }
2387
setFakeChatListMessageFrom(const MTPmessages_Messages & data)2388 void History::setFakeChatListMessageFrom(const MTPmessages_Messages &data) {
2389 if (!lastMessageKnown()) {
2390 requestChatListMessage();
2391 return;
2392 }
2393 const auto finalize = gsl::finally([&] {
2394 // Make sure that we have chatListMessage when we get out of here.
2395 if (!chatListMessageKnown()) {
2396 setChatListMessage(lastMessage());
2397 }
2398 });
2399 const auto last = lastMessage();
2400 if (!last || !last->isGroupMigrate()) {
2401 // Last message is good enough.
2402 return;
2403 }
2404 const auto other = data.match([&](
2405 const MTPDmessages_messagesNotModified &) {
2406 return static_cast<const MTPMessage*>(nullptr);
2407 }, [&](const auto &data) {
2408 for (const auto &message : data.vmessages().v) {
2409 const auto id = message.match([](const auto &data) {
2410 return data.vid().v;
2411 });
2412 if (id != last->id) {
2413 return &message;
2414 }
2415 }
2416 return static_cast<const MTPMessage*>(nullptr);
2417 });
2418 if (!other) {
2419 // Other (non equal to the last one) message not found.
2420 return;
2421 }
2422 const auto item = owner().addNewMessage(
2423 *other,
2424 MessageFlags(),
2425 NewMessageType::Existing);
2426 if (!item || item->isGroupMigrate()) {
2427 // Not better than the last one.
2428 return;
2429 }
2430 setChatListMessage(item);
2431 }
2432
applyChatListGroup(ChannelId channelId,const MTPmessages_Messages & data)2433 void History::applyChatListGroup(
2434 ChannelId channelId,
2435 const MTPmessages_Messages &data) {
2436 if (!isEmpty()
2437 || !_chatListMessage
2438 || !*_chatListMessage
2439 || (*_chatListMessage)->history()->channelId() != channelId
2440 || (*_chatListMessage)->history() != this
2441 || !_lastMessage
2442 || !*_lastMessage) {
2443 return;
2444 }
2445 // Apply loaded album as a last slice.
2446 const auto processMessages = [&](const MTPVector<MTPMessage> &messages) {
2447 auto items = std::vector<not_null<HistoryItem*>>();
2448 items.reserve(messages.v.size());
2449 for (const auto &message : messages.v) {
2450 const auto id = IdFromMessage(message);
2451 if (const auto message = owner().message(channelId, id)) {
2452 items.push_back(message);
2453 }
2454 }
2455 if (!ranges::contains(items, not_null(*_lastMessage))
2456 || !ranges::contains(items, not_null(*_chatListMessage))) {
2457 return;
2458 }
2459 _loadedAtBottom = true;
2460 ranges::sort(items, ranges::less{}, &HistoryItem::id);
2461 addCreatedOlderSlice(items);
2462 checkLocalMessages();
2463 checkLastMessage();
2464 };
2465 data.match([&](const MTPDmessages_messagesNotModified &) {
2466 }, [&](const auto &data) {
2467 processMessages(data.vmessages());
2468 });
2469 }
2470
lastMessage() const2471 HistoryItem *History::lastMessage() const {
2472 return _lastMessage.value_or(nullptr);
2473 }
2474
lastMessageKnown() const2475 bool History::lastMessageKnown() const {
2476 return _lastMessage.has_value();
2477 }
2478
lastServerMessage() const2479 HistoryItem *History::lastServerMessage() const {
2480 return _lastServerMessage.value_or(nullptr);
2481 }
2482
lastServerMessageKnown() const2483 bool History::lastServerMessageKnown() const {
2484 return _lastServerMessage.has_value();
2485 }
2486
updateChatListExistence()2487 void History::updateChatListExistence() {
2488 Entry::updateChatListExistence();
2489 }
2490
useTopPromotion() const2491 bool History::useTopPromotion() const {
2492 if (!isTopPromoted()) {
2493 return false;
2494 } else if (const auto channel = peer->asChannel()) {
2495 return !isPinnedDialog(FilterId()) && !channel->amIn();
2496 } else if (const auto user = peer->asUser()) {
2497 return !isPinnedDialog(FilterId()) && user->isBot() && isEmpty();
2498 }
2499 return false;
2500 }
2501
fixedOnTopIndex() const2502 int History::fixedOnTopIndex() const {
2503 return useTopPromotion() ? kTopPromotionFixOnTopIndex : 0;
2504 }
2505
trackUnreadMessages() const2506 bool History::trackUnreadMessages() const {
2507 if (const auto channel = peer->asChannel()) {
2508 return channel->amIn();
2509 }
2510 return true;
2511 }
2512
shouldBeInChatList() const2513 bool History::shouldBeInChatList() const {
2514 if (peer->migrateTo() || !folderKnown()) {
2515 return false;
2516 } else if (isPinnedDialog(FilterId())) {
2517 return true;
2518 } else if (const auto channel = peer->asChannel()) {
2519 if (!channel->amIn()) {
2520 return isTopPromoted();
2521 }
2522 } else if (const auto chat = peer->asChat()) {
2523 return chat->amIn()
2524 || !lastMessageKnown()
2525 || (lastMessage() != nullptr);
2526 } else if (const auto user = peer->asUser()) {
2527 if (user->isBot() && isTopPromoted()) {
2528 return true;
2529 }
2530 }
2531 return !lastMessageKnown()
2532 || (lastMessage() != nullptr);
2533 }
2534
unknownMessageDeleted(MsgId messageId)2535 void History::unknownMessageDeleted(MsgId messageId) {
2536 if (_inboxReadBefore && messageId >= *_inboxReadBefore) {
2537 owner().histories().requestDialogEntry(this);
2538 }
2539 }
2540
isServerSideUnread(not_null<const HistoryItem * > item) const2541 bool History::isServerSideUnread(not_null<const HistoryItem*> item) const {
2542 Expects(item->isRegular());
2543
2544 return item->out()
2545 ? (!_outboxReadBefore || (item->id >= *_outboxReadBefore))
2546 : (!_inboxReadBefore || (item->id >= *_inboxReadBefore));
2547 }
2548
applyDialog(Data::Folder * requestFolder,const MTPDdialog & data)2549 void History::applyDialog(
2550 Data::Folder *requestFolder,
2551 const MTPDdialog &data) {
2552 const auto folderId = data.vfolder_id();
2553 const auto folder = !folderId
2554 ? requestFolder
2555 : folderId->v
2556 ? owner().folder(folderId->v).get()
2557 : nullptr;
2558 applyDialogFields(
2559 folder,
2560 data.vunread_count().v,
2561 data.vread_inbox_max_id().v,
2562 data.vread_outbox_max_id().v);
2563 applyDialogTopMessage(data.vtop_message().v);
2564 setUnreadMark(data.is_unread_mark());
2565 setUnreadMentionsCount(data.vunread_mentions_count().v);
2566 if (const auto channel = peer->asChannel()) {
2567 if (const auto pts = data.vpts()) {
2568 channel->ptsReceived(pts->v);
2569 }
2570 if (!channel->amCreator()) {
2571 const auto topMessageId = FullMsgId(
2572 peerToChannel(channel->id),
2573 data.vtop_message().v);
2574 if (const auto item = owner().message(topMessageId)) {
2575 if (item->date() <= channel->date) {
2576 session().api().requestSelfParticipant(channel);
2577 }
2578 }
2579 }
2580 }
2581 owner().applyNotifySetting(
2582 MTP_notifyPeer(data.vpeer()),
2583 data.vnotify_settings());
2584
2585 const auto draft = data.vdraft();
2586 if (draft && draft->type() == mtpc_draftMessage) {
2587 Data::ApplyPeerCloudDraft(
2588 &session(),
2589 peer->id,
2590 draft->c_draftMessage());
2591 }
2592 owner().histories().dialogEntryApplied(this);
2593 }
2594
dialogEntryApplied()2595 void History::dialogEntryApplied() {
2596 if (!lastServerMessageKnown()) {
2597 setLastServerMessage(nullptr);
2598 } else if (!lastMessageKnown()) {
2599 setLastMessage(nullptr);
2600 }
2601 if (peer->migrateTo()) {
2602 return;
2603 } else if (!chatListMessageKnown()) {
2604 requestChatListMessage();
2605 return;
2606 }
2607 if (!chatListMessage()) {
2608 clear(ClearType::Unload);
2609 addNewerSlice(QVector<MTPMessage>());
2610 addOlderSlice(QVector<MTPMessage>());
2611 if (const auto channel = peer->asChannel()) {
2612 const auto inviter = channel->inviter;
2613 if (inviter && channel->amIn()) {
2614 if (const auto from = owner().userLoaded(inviter)) {
2615 insertJoinedMessage();
2616 }
2617 }
2618 }
2619 return;
2620 }
2621
2622 if (chatListTimeId() != 0 && loadedAtBottom()) {
2623 if (const auto channel = peer->asChannel()) {
2624 const auto inviter = channel->inviter;
2625 if (inviter
2626 && chatListTimeId() <= channel->inviteDate
2627 && channel->amIn()) {
2628 if (const auto from = owner().userLoaded(inviter)) {
2629 insertJoinedMessage();
2630 }
2631 }
2632 }
2633 }
2634 }
2635
cacheTopPromotion(bool promoted,const QString & type,const QString & message)2636 void History::cacheTopPromotion(
2637 bool promoted,
2638 const QString &type,
2639 const QString &message) {
2640 const auto changed = (isTopPromoted() != promoted);
2641 cacheTopPromoted(promoted);
2642 if (topPromotionType() != type || _topPromotedMessage != message) {
2643 _topPromotedType = type;
2644 _topPromotedMessage = message;
2645 cloudDraftTextCache.clear();
2646 } else if (changed) {
2647 cloudDraftTextCache.clear();
2648 }
2649 }
2650
topPromotionType() const2651 QStringView History::topPromotionType() const {
2652 return topPromotionAboutShown()
2653 ? base::StringViewMid(_topPromotedType, 5)
2654 : QStringView(_topPromotedType);
2655 }
2656
topPromotionAboutShown() const2657 bool History::topPromotionAboutShown() const {
2658 return _topPromotedType.startsWith("seen^");
2659 }
2660
markTopPromotionAboutShown()2661 void History::markTopPromotionAboutShown() {
2662 if (!topPromotionAboutShown()) {
2663 _topPromotedType = "seen^" + _topPromotedType;
2664 }
2665 }
2666
topPromotionMessage() const2667 QString History::topPromotionMessage() const {
2668 return _topPromotedMessage;
2669 }
2670
clearUnreadOnClientSide() const2671 bool History::clearUnreadOnClientSide() const {
2672 if (!session().supportMode()) {
2673 return false;
2674 }
2675 if (const auto user = peer->asUser()) {
2676 if (user->isInaccessible()) {
2677 return true;
2678 }
2679 }
2680 return false;
2681 }
2682
skipUnreadUpdate() const2683 bool History::skipUnreadUpdate() const {
2684 return clearUnreadOnClientSide();
2685 }
2686
applyDialogFields(Data::Folder * folder,int unreadCount,MsgId maxInboxRead,MsgId maxOutboxRead)2687 void History::applyDialogFields(
2688 Data::Folder *folder,
2689 int unreadCount,
2690 MsgId maxInboxRead,
2691 MsgId maxOutboxRead) {
2692 if (folder) {
2693 setFolder(folder);
2694 } else {
2695 clearFolder();
2696 }
2697 if (!skipUnreadUpdate()
2698 && maxInboxRead + 1 >= _inboxReadBefore.value_or(1)) {
2699 setUnreadCount(unreadCount);
2700 setInboxReadTill(maxInboxRead);
2701 }
2702 setOutboxReadTill(maxOutboxRead);
2703 }
2704
applyDialogTopMessage(MsgId topMessageId)2705 void History::applyDialogTopMessage(MsgId topMessageId) {
2706 if (topMessageId) {
2707 const auto itemId = FullMsgId(
2708 channelId(),
2709 topMessageId);
2710 if (const auto item = owner().message(itemId)) {
2711 setLastServerMessage(item);
2712 } else {
2713 setLastServerMessage(nullptr);
2714 }
2715 } else {
2716 setLastServerMessage(nullptr);
2717 }
2718 if (clearUnreadOnClientSide()) {
2719 setUnreadCount(0);
2720 if (const auto last = lastMessage()) {
2721 setInboxReadTill(last->id);
2722 }
2723 }
2724 }
2725
setInboxReadTill(MsgId upTo)2726 void History::setInboxReadTill(MsgId upTo) {
2727 if (_inboxReadBefore) {
2728 accumulate_max(*_inboxReadBefore, upTo + 1);
2729 } else {
2730 _inboxReadBefore = upTo + 1;
2731 }
2732 }
2733
setOutboxReadTill(MsgId upTo)2734 void History::setOutboxReadTill(MsgId upTo) {
2735 if (_outboxReadBefore) {
2736 accumulate_max(*_outboxReadBefore, upTo + 1);
2737 } else {
2738 _outboxReadBefore = upTo + 1;
2739 }
2740 }
2741
minMsgId() const2742 MsgId History::minMsgId() const {
2743 for (const auto &block : blocks) {
2744 for (const auto &message : block->messages) {
2745 const auto item = message->data();
2746 if (item->isRegular()) {
2747 return item->id;
2748 }
2749 }
2750 }
2751 return 0;
2752 }
2753
maxMsgId() const2754 MsgId History::maxMsgId() const {
2755 for (const auto &block : ranges::views::reverse(blocks)) {
2756 for (const auto &message : ranges::views::reverse(block->messages)) {
2757 const auto item = message->data();
2758 if (item->isRegular()) {
2759 return item->id;
2760 }
2761 }
2762 }
2763 return 0;
2764 }
2765
msgIdForRead() const2766 MsgId History::msgIdForRead() const {
2767 const auto last = lastMessage();
2768 const auto result = (last && last->isRegular())
2769 ? last->id
2770 : MsgId(0);
2771 return loadedAtBottom()
2772 ? std::max(result, maxMsgId())
2773 : result;
2774 }
2775
lastEditableMessage() const2776 HistoryItem *History::lastEditableMessage() const {
2777 if (!loadedAtBottom()) {
2778 return nullptr;
2779 }
2780 const auto now = base::unixtime::now();
2781 for (const auto &block : ranges::views::reverse(blocks)) {
2782 for (const auto &message : ranges::views::reverse(block->messages)) {
2783 const auto item = message->data();
2784 if (item->allowsEdit(now)) {
2785 return owner().groups().findItemToEdit(item);
2786 }
2787 }
2788 }
2789 return nullptr;
2790 }
2791
resizeToWidth(int newWidth)2792 void History::resizeToWidth(int newWidth) {
2793 const auto resizeAllItems = (_width != newWidth);
2794
2795 if (!resizeAllItems && !hasPendingResizedItems()) {
2796 return;
2797 }
2798 _flags &= ~(Flag::f_has_pending_resized_items);
2799
2800 _width = newWidth;
2801 int y = 0;
2802 for (const auto &block : blocks) {
2803 block->setY(y);
2804 y += block->resizeGetHeight(newWidth, resizeAllItems);
2805 }
2806 _height = y;
2807 }
2808
forceFullResize()2809 void History::forceFullResize() {
2810 _width = 0;
2811 _flags |= Flag::f_has_pending_resized_items;
2812 }
2813
channelId() const2814 ChannelId History::channelId() const {
2815 return peerToChannel(peer->id);
2816 }
2817
isChannel() const2818 bool History::isChannel() const {
2819 return peerIsChannel(peer->id);
2820 }
2821
isMegagroup() const2822 bool History::isMegagroup() const {
2823 return peer->isMegagroup();
2824 }
2825
migrateToOrMe() const2826 not_null<History*> History::migrateToOrMe() const {
2827 if (const auto to = peer->migrateTo()) {
2828 return owner().history(to);
2829 }
2830 // We could get it by owner().history(peer), but we optimize.
2831 return const_cast<History*>(this);
2832 }
2833
migrateFrom() const2834 History *History::migrateFrom() const {
2835 if (const auto from = peer->migrateFrom()) {
2836 return owner().history(from);
2837 }
2838 return nullptr;
2839 }
2840
rangeForDifferenceRequest() const2841 MsgRange History::rangeForDifferenceRequest() const {
2842 auto fromId = MsgId(0);
2843 auto toId = MsgId(0);
2844 for (const auto &block : blocks) {
2845 for (const auto &item : block->messages) {
2846 const auto id = item->data()->id;
2847 if (id > 0) {
2848 fromId = id;
2849 break;
2850 }
2851 }
2852 if (fromId) break;
2853 }
2854 if (fromId) {
2855 for (auto blockIndex = blocks.size(); blockIndex > 0;) {
2856 const auto &block = blocks[--blockIndex];
2857 for (auto itemIndex = block->messages.size(); itemIndex > 0;) {
2858 const auto id = block->messages[--itemIndex]->data()->id;
2859 if (id > 0) {
2860 toId = id;
2861 break;
2862 }
2863 }
2864 if (toId) break;
2865 }
2866 return { fromId, toId + 1 };
2867 }
2868 return MsgRange();
2869 }
2870
insertJoinedMessage()2871 HistoryService *History::insertJoinedMessage() {
2872 const auto channel = peer->asChannel();
2873 if (!channel
2874 || _joinedMessage
2875 || !channel->amIn()
2876 || (peer->isMegagroup()
2877 && channel->mgInfo->joinedMessageFound)) {
2878 return _joinedMessage;
2879 }
2880
2881 const auto inviter = (channel->inviter.bare > 0)
2882 ? owner().userLoaded(channel->inviter)
2883 : nullptr;
2884 if (!inviter) {
2885 return nullptr;
2886 }
2887
2888 if (peer->isMegagroup()
2889 && peer->migrateFrom()
2890 && !blocks.empty()
2891 && blocks.front()->messages.front()->data()->id == 1) {
2892 channel->mgInfo->joinedMessageFound = true;
2893 return nullptr;
2894 }
2895
2896 _joinedMessage = GenerateJoinedMessage(
2897 this,
2898 channel->inviteDate,
2899 inviter,
2900 channel->inviteViaRequest);
2901 insertMessageToBlocks(_joinedMessage);
2902 return _joinedMessage;
2903 }
2904
insertMessageToBlocks(not_null<HistoryItem * > item)2905 void History::insertMessageToBlocks(not_null<HistoryItem*> item) {
2906 Expects(item->mainView() == nullptr);
2907
2908 if (isEmpty()) {
2909 addNewToBack(item, false);
2910 return;
2911 }
2912
2913 const auto itemDate = item->date();
2914 for (auto blockIndex = blocks.size(); blockIndex > 0;) {
2915 const auto &block = blocks[--blockIndex];
2916 for (auto itemIndex = block->messages.size(); itemIndex > 0;) {
2917 if (block->messages[--itemIndex]->data()->date() <= itemDate) {
2918 ++itemIndex;
2919 addNewInTheMiddle(item, blockIndex, itemIndex);
2920 const auto lastDate = chatListTimeId();
2921 if (!lastDate || itemDate >= lastDate) {
2922 setLastMessage(item);
2923 }
2924 return;
2925 }
2926 }
2927 }
2928
2929 startBuildingFrontBlock();
2930 addItemToBlock(item);
2931 finishBuildingFrontBlock();
2932 }
2933
checkLocalMessages()2934 void History::checkLocalMessages() {
2935 if (isEmpty() && (!loadedAtTop() || !loadedAtBottom())) {
2936 return;
2937 }
2938 const auto firstDate = loadedAtTop()
2939 ? 0
2940 : blocks.front()->messages.front()->data()->date();
2941 const auto lastDate = loadedAtBottom()
2942 ? std::numeric_limits<TimeId>::max()
2943 : blocks.back()->messages.back()->data()->date();
2944 const auto goodDate = [&](TimeId date) {
2945 return (date >= firstDate && date < lastDate);
2946 };
2947 for (const auto &item : _clientSideMessages) {
2948 if (!item->mainView() && goodDate(item->date())) {
2949 insertMessageToBlocks(item);
2950 }
2951 }
2952 if (isChannel()
2953 && !_joinedMessage
2954 && peer->asChannel()->inviter
2955 && goodDate(peer->asChannel()->inviteDate)) {
2956 insertJoinedMessage();
2957 }
2958 }
2959
removeJoinedMessage()2960 void History::removeJoinedMessage() {
2961 if (_joinedMessage) {
2962 _joinedMessage->destroy();
2963 }
2964 }
2965
isEmpty() const2966 bool History::isEmpty() const {
2967 return blocks.empty();
2968 }
2969
isDisplayedEmpty() const2970 bool History::isDisplayedEmpty() const {
2971 if (!loadedAtTop() || !loadedAtBottom()) {
2972 return false;
2973 }
2974 const auto first = findFirstNonEmpty();
2975 if (!first) {
2976 return true;
2977 }
2978 const auto chat = peer->asChat();
2979 if (!chat || !chat->amCreator()) {
2980 return false;
2981 }
2982
2983 // For legacy chats we want to show the chat with only
2984 // messages about you creating the group and maybe about you
2985 // changing the group photo as an empty chat with
2986 // a nice information about the group features.
2987 if (nonEmptyCountMoreThan(2)) {
2988 return false;
2989 }
2990 const auto isChangePhoto = [](not_null<HistoryItem*> item) {
2991 if (const auto media = item->media()) {
2992 return (media->photo() != nullptr) && item->isService();
2993 }
2994 return false;
2995 };
2996 const auto last = findLastNonEmpty();
2997 if (first == last) {
2998 return first->data()->isGroupEssential()
2999 || isChangePhoto(first->data());
3000 }
3001 return first->data()->isGroupEssential() && isChangePhoto(last->data());
3002 }
3003
findFirstNonEmpty() const3004 auto History::findFirstNonEmpty() const -> Element* {
3005 for (const auto &block : blocks) {
3006 for (const auto &element : block->messages) {
3007 if (!element->data()->isEmpty()) {
3008 return element.get();
3009 }
3010 }
3011 }
3012 return nullptr;
3013 }
3014
findFirstDisplayed() const3015 auto History::findFirstDisplayed() const -> Element* {
3016 for (const auto &block : blocks) {
3017 for (const auto &element : block->messages) {
3018 if (!element->data()->isEmpty() && !element->isHidden()) {
3019 return element.get();
3020 }
3021 }
3022 }
3023 return nullptr;
3024 }
3025
findLastNonEmpty() const3026 auto History::findLastNonEmpty() const -> Element* {
3027 for (const auto &block : ranges::views::reverse(blocks)) {
3028 for (const auto &element : ranges::views::reverse(block->messages)) {
3029 if (!element->data()->isEmpty()) {
3030 return element.get();
3031 }
3032 }
3033 }
3034 return nullptr;
3035 }
3036
findLastDisplayed() const3037 auto History::findLastDisplayed() const -> Element* {
3038 for (const auto &block : ranges::views::reverse(blocks)) {
3039 for (const auto &element : ranges::views::reverse(block->messages)) {
3040 if (!element->data()->isEmpty() && !element->isHidden()) {
3041 return element.get();
3042 }
3043 }
3044 }
3045 return nullptr;
3046 }
3047
nonEmptyCountMoreThan(int count) const3048 bool History::nonEmptyCountMoreThan(int count) const {
3049 Expects(count >= 0);
3050
3051 for (const auto &block : blocks) {
3052 for (const auto &element : block->messages) {
3053 if (!element->data()->isEmpty()) {
3054 if (!count--) {
3055 return true;
3056 }
3057 }
3058 }
3059 }
3060 return false;
3061 }
3062
hasOrphanMediaGroupPart() const3063 bool History::hasOrphanMediaGroupPart() const {
3064 if (loadedAtTop() || !loadedAtBottom()) {
3065 return false;
3066 } else if (blocks.size() != 1) {
3067 return false;
3068 } else if (blocks.front()->messages.size() != 1) {
3069 return false;
3070 }
3071 const auto last = blocks.front()->messages.front()->data();
3072 return last->groupId() != MessageGroupId();
3073 }
3074
removeOrphanMediaGroupPart()3075 bool History::removeOrphanMediaGroupPart() {
3076 if (hasOrphanMediaGroupPart()) {
3077 clear(ClearType::Unload);
3078 return true;
3079 }
3080 return false;
3081 }
3082
collectMessagesFromUserToDelete(not_null<UserData * > user) const3083 QVector<MsgId> History::collectMessagesFromUserToDelete(
3084 not_null<UserData*> user) const {
3085 auto result = QVector<MsgId>();
3086 for (const auto &block : blocks) {
3087 for (const auto &message : block->messages) {
3088 const auto item = message->data();
3089 if (item->from() == user && item->canDelete()) {
3090 result.push_back(item->id);
3091 }
3092 }
3093 }
3094 return result;
3095 }
3096
clear(ClearType type)3097 void History::clear(ClearType type) {
3098 _unreadBarView = nullptr;
3099 _firstUnreadView = nullptr;
3100 removeJoinedMessage();
3101
3102 forgetScrollState();
3103 blocks.clear();
3104 owner().notifyHistoryUnloaded(this);
3105 lastKeyboardInited = false;
3106 if (type == ClearType::Unload) {
3107 _loadedAtTop = _loadedAtBottom = false;
3108 } else {
3109 // Leave the 'sending' messages in local messages.
3110 auto local = base::flat_set<not_null<HistoryItem*>>();
3111 for (const auto &item : _clientSideMessages) {
3112 if (!item->isSending()) {
3113 local.emplace(item);
3114 }
3115 }
3116 for (const auto &item : local) {
3117 item->destroy();
3118 }
3119 _notifications.clear();
3120 owner().notifyHistoryCleared(this);
3121 if (unreadCountKnown()) {
3122 setUnreadCount(0);
3123 }
3124 if (type == ClearType::DeleteChat) {
3125 setLastServerMessage(nullptr);
3126 } else if (_lastMessage && *_lastMessage) {
3127 if ((*_lastMessage)->isRegular()) {
3128 (*_lastMessage)->applyEditionToHistoryCleared();
3129 } else {
3130 _lastMessage = std::nullopt;
3131 }
3132 }
3133 const auto tillId = (_lastMessage && *_lastMessage)
3134 ? (*_lastMessage)->id
3135 : std::numeric_limits<MsgId>::max();
3136 clearUpTill(tillId);
3137 if (blocks.empty() && _lastMessage && *_lastMessage) {
3138 addItemToBlock(*_lastMessage);
3139 }
3140 _loadedAtTop = _loadedAtBottom = _lastMessage.has_value();
3141 clearSharedMedia();
3142 clearLastKeyboard();
3143 }
3144
3145 if (const auto chat = peer->asChat()) {
3146 chat->lastAuthors.clear();
3147 chat->markupSenders.clear();
3148 } else if (const auto channel = peer->asMegagroup()) {
3149 channel->mgInfo->markupSenders.clear();
3150 }
3151
3152 owner().notifyHistoryChangeDelayed(this);
3153 owner().sendHistoryChangeNotifications();
3154 }
3155
clearUpTill(MsgId availableMinId)3156 void History::clearUpTill(MsgId availableMinId) {
3157 auto remove = std::vector<not_null<HistoryItem*>>();
3158 remove.reserve(_messages.size());
3159 for (const auto &item : _messages) {
3160 const auto itemId = item->id;
3161 if (!item->isRegular()) {
3162 continue;
3163 } else if (itemId == availableMinId) {
3164 item->applyEditionToHistoryCleared();
3165 } else if (itemId < availableMinId) {
3166 remove.push_back(item.get());
3167 }
3168 }
3169 for (const auto item : remove) {
3170 item->destroy();
3171 }
3172 requestChatListMessage();
3173 }
3174
applyGroupAdminChanges(const base::flat_set<UserId> & changes)3175 void History::applyGroupAdminChanges(const base::flat_set<UserId> &changes) {
3176 for (const auto &block : blocks) {
3177 for (const auto &message : block->messages) {
3178 message->applyGroupAdminChanges(changes);
3179 }
3180 }
3181 }
3182
changedChatListPinHook()3183 void History::changedChatListPinHook() {
3184 session().changes().historyUpdated(this, UpdateFlag::IsPinned);
3185 }
3186
removeBlock(not_null<HistoryBlock * > block)3187 void History::removeBlock(not_null<HistoryBlock*> block) {
3188 Expects(block->messages.empty());
3189
3190 if (_buildingFrontBlock && block == _buildingFrontBlock->block) {
3191 _buildingFrontBlock->block = nullptr;
3192 }
3193
3194 int index = block->indexInHistory();
3195 blocks.erase(blocks.begin() + index);
3196 if (index < blocks.size()) {
3197 for (int i = index, l = blocks.size(); i < l; ++i) {
3198 blocks[i]->setIndexInHistory(i);
3199 }
3200 blocks[index]->messages.front()->previousInBlocksChanged();
3201 } else if (!blocks.empty() && !blocks.back()->messages.empty()) {
3202 blocks.back()->messages.back()->nextInBlocksRemoved();
3203 }
3204 }
3205
hasPinnedMessages() const3206 bool History::hasPinnedMessages() const {
3207 return _hasPinnedMessages;
3208 }
3209
setHasPinnedMessages(bool has)3210 void History::setHasPinnedMessages(bool has) {
3211 _hasPinnedMessages = has;
3212 session().changes().historyUpdated(this, UpdateFlag::PinnedMessages);
3213 }
3214
3215 History::~History() = default;
3216
HistoryBlock(not_null<History * > history)3217 HistoryBlock::HistoryBlock(not_null<History*> history)
3218 : _history(history) {
3219 }
3220
resizeGetHeight(int newWidth,bool resizeAllItems)3221 int HistoryBlock::resizeGetHeight(int newWidth, bool resizeAllItems) {
3222 auto y = 0;
3223 for (const auto &message : messages) {
3224 message->setY(y);
3225 if (resizeAllItems || message->pendingResize()) {
3226 y += message->resizeGetHeight(newWidth);
3227 } else {
3228 y += message->height();
3229 }
3230 }
3231 _height = y;
3232 return _height;
3233 }
3234
remove(not_null<Element * > view)3235 void HistoryBlock::remove(not_null<Element*> view) {
3236 Expects(view->block() == this);
3237
3238 _history->mainViewRemoved(this, view);
3239
3240 const auto blockIndex = indexInHistory();
3241 const auto itemIndex = view->indexInBlock();
3242 const auto item = view->data();
3243 item->clearMainView();
3244 messages.erase(messages.begin() + itemIndex);
3245 for (auto i = itemIndex, l = int(messages.size()); i < l; ++i) {
3246 messages[i]->setIndexInBlock(i);
3247 }
3248 if (messages.empty()) {
3249 // Deletes this.
3250 _history->removeBlock(this);
3251 } else if (itemIndex < messages.size()) {
3252 messages[itemIndex]->previousInBlocksChanged();
3253 } else if (blockIndex + 1 < _history->blocks.size()) {
3254 _history->blocks[blockIndex + 1]->messages.front()->previousInBlocksChanged();
3255 } else if (!_history->blocks.empty() && !_history->blocks.back()->messages.empty()) {
3256 _history->blocks.back()->messages.back()->nextInBlocksRemoved();
3257 }
3258 }
3259
refreshView(not_null<Element * > view)3260 void HistoryBlock::refreshView(not_null<Element*> view) {
3261 Expects(view->block() == this);
3262
3263 const auto item = view->data();
3264 auto refreshed = item->createView(
3265 HistoryInner::ElementDelegate(),
3266 view);
3267
3268 auto blockIndex = indexInHistory();
3269 auto itemIndex = view->indexInBlock();
3270 _history->viewReplaced(view, refreshed.get());
3271
3272 messages[itemIndex] = std::move(refreshed);
3273 messages[itemIndex]->attachToBlock(this, itemIndex);
3274 if (itemIndex + 1 < messages.size()) {
3275 messages[itemIndex + 1]->previousInBlocksChanged();
3276 } else if (blockIndex + 1 < _history->blocks.size()) {
3277 _history->blocks[blockIndex + 1]->messages.front()->previousInBlocksChanged();
3278 } else if (!_history->blocks.empty() && !_history->blocks.back()->messages.empty()) {
3279 _history->blocks.back()->messages.back()->nextInBlocksRemoved();
3280 }
3281 }
3282
3283 HistoryBlock::~HistoryBlock() = default;
3284