diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7d15f61ad..f88ce9414 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1420,6 +1420,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_feed_group" = "Group in feed"; "lng_feed_ungroup" = "Ungroup from feed"; +"lng_feed_channel_added" = "Channel added to your feed."; +"lng_feed_channel_removed" = "Channel removed from your feed."; "lng_info_feed_title" = "Feed Info"; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 534bcd97b..a0e575f85 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -172,9 +172,10 @@ void ApiWrap::savePinnedOrder() { void ApiWrap::toggleChannelGrouping( not_null channel, - bool group) { + bool group, + base::lambda callback) { if (const auto already = _channelGroupingRequests.take(channel)) { - request(*already).cancel(); + request(already->first).cancel(); } const auto feedId = Data::Feed::kId; const auto flags = group @@ -185,15 +186,18 @@ void ApiWrap::toggleChannelGrouping( channel->inputChannel, MTP_int(feedId) )).done([=](const MTPBool &result) { - _channelGroupingRequests.remove(channel); if (group) { channel->setFeed(Auth().data().feed(feedId)); } else { channel->clearFeed(); } + if (const auto data = _channelGroupingRequests.take(channel)) { + data->second(); + } }).fail([=](const RPCError &error) { _channelGroupingRequests.remove(channel); }).send(); + _channelGroupingRequests.emplace(channel, requestId, callback); } void ApiWrap::sendMessageFail(const RPCError &error) { @@ -362,6 +366,124 @@ void ApiWrap::requestContacts() { }).send(); } +void ApiWrap::requestDialogEntry(not_null feed) { + if (_dialogFeedRequests.contains(feed)) { + return; + } + _dialogFeedRequests.emplace(feed); + + auto peers = QVector( + 1, + MTP_inputDialogPeerFeed(MTP_int(feed->id()))); + if (feed->channelsLoaded()) { + const auto &channels = feed->channels(); + peers.reserve(channels.size() + 1); + for (const auto history : feed->channels()) { + peers.push_back(MTP_inputDialogPeer(history->peer->input)); + } + } else { + request(MTPmessages_GetDialogs( + MTP_flags(MTPmessages_GetDialogs::Flag::f_feed_id), + MTP_int(feed->id()), + MTP_int(0), // offset_date + MTP_int(0), // offset_id + MTP_inputPeerEmpty(), // offset_peer + MTP_int(Data::Feed::kChannelsLimit) + )).done([=](const MTPmessages_Dialogs &result) { + // applyFeedChannels(result); + }).send(); + } + request(MTPmessages_GetPeerDialogs( + MTP_vector(std::move(peers)) + )).done([=](const MTPmessages_PeerDialogs &result) { + applyPeerDialogs(result); + _dialogFeedRequests.remove(feed); + }).fail([=](const RPCError &error) { + _dialogFeedRequests.remove(feed); + }).send(); +} + +void ApiWrap::requestDialogEntry(not_null history) { + if (_dialogRequests.contains(history)) { + return; + } + _dialogRequests.emplace(history); + auto peers = QVector( + 1, + MTP_inputDialogPeer(history->peer->input)); + request(MTPmessages_GetPeerDialogs( + MTP_vector(std::move(peers)) + )).done([=](const MTPmessages_PeerDialogs &result) { + applyPeerDialogs(result); + + if (history->lastMessage()) { + if (!history->chatsListDate().isNull() + && history->loadedAtBottom()) { + if (const auto channel = history->peer->asChannel()) { + const auto inviter = channel->inviter; + if (inviter != 0 + && history->chatsListDate() <= channel->inviteDate + && channel->amIn()) { + if (const auto from = App::userLoaded(inviter)) { + history->insertJoinedMessage(true); + } + } + } + } + history->updateChatListExistence(); + } else { + if (const auto chat = history->peer->asChat()) { + if (!chat->haveLeft()) { + Local::addSavedPeer( + history->peer, + history->chatsListDate()); + } + } else if (const auto channel = history->peer->asChannel()) { + const auto inviter = channel->inviter; + if (inviter != 0 && channel->amIn()) { + if (const auto from = App::userLoaded(inviter)) { + history->unloadBlocks(); + history->addNewerSlice(QVector()); + history->insertJoinedMessage( + true); + } + } + } else { + App::main()->deleteConversation(history->peer, false); + } + } + _dialogRequests.remove(history); + }).fail([=](const RPCError &error) { + _dialogRequests.remove(history); + }).send(); +} + +void ApiWrap::applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs) { + Expects(dialogs.type() == mtpc_messages_peerDialogs); + + const auto &data = dialogs.c_messages_peerDialogs(); + App::feedUsers(data.vusers); + App::feedChats(data.vchats); + App::feedMsgs(data.vmessages, NewMessageLast); + for (const auto &dialog : data.vdialogs.v) { + switch (dialog.type()) { + case mtpc_dialog: { + const auto &fields = dialog.c_dialog(); + if (const auto peerId = peerFromMTP(fields.vpeer)) { + App::history(peerId)->applyDialog(fields); + } + } break; + + case mtpc_dialogFeed: { + const auto &fields = dialog.c_dialogFeed(); + const auto feed = Auth().data().feed(fields.vfeed_id.v); + feed->applyDialog(fields); + } break; + } + } + _session->data().sendHistoryChangeNotifications(); +} + void ApiWrap::requestFullPeer(PeerData *peer) { if (!peer || _fullPeerRequests.contains(peer)) return; @@ -464,7 +586,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt if (auto h = App::historyLoaded(cfrom->id)) { if (auto hto = App::historyLoaded(channel->id)) { if (!h->isEmpty()) { - h->clear(true); + h->unloadBlocks(); } if (hto->inChatList(Dialogs::Mode::All) && h->inChatList(Dialogs::Mode::All)) { App::main()->removeDialog(h); @@ -495,14 +617,12 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt channel->setRestrictedCount(f.has_banned_count() ? f.vbanned_count.v : 0); channel->setKickedCount(f.has_kicked_count() ? f.vkicked_count.v : 0); channel->setInviteLink((f.vexported_invite.type() == mtpc_chatInviteExported) ? qs(f.vexported_invite.c_chatInviteExported().vlink) : QString()); - if (auto h = App::historyLoaded(channel->id)) { - if (h->inboxReadBefore < f.vread_inbox_max_id.v + 1) { - h->setUnreadCount(f.vunread_count.v); - h->inboxReadBefore = f.vread_inbox_max_id.v + 1; - } - accumulate_max(h->outboxReadBefore, f.vread_outbox_max_id.v + 1); + if (const auto history = App::historyLoaded(channel->id)) { + history->applyDialogFields( + f.vunread_count.v, + f.vread_inbox_max_id.v, + f.vread_outbox_max_id.v); } - ranges::overload([] {}, [](int a) {}); if (f.has_pinned_msg_id()) { channel->setPinnedMessageId(f.vpinned_msg_id.v); } else { @@ -1063,7 +1183,9 @@ void ApiWrap::unblockParticipant( not_null channel, not_null user) { const auto kick = KickRequest(channel, user); - if (_kickRequests.contains(kick)) return; + if (_kickRequests.contains(kick)) { + return; + } const auto requestId = request(MTPchannels_EditBanned( channel->inputChannel, @@ -1085,6 +1207,43 @@ void ApiWrap::unblockParticipant( _kickRequests.emplace(kick, requestId); } +void ApiWrap::deleteAllFromUser( + not_null channel, + not_null from) { + const auto history = App::historyLoaded(channel->id); + const auto ids = history + ? history->collectMessagesFromUserToDelete(from) + : QVector(); + const auto channelId = peerToChannel(channel->id); + for (const auto msgId : ids) { + if (const auto item = App::histItemById(channelId, msgId)) { + item->destroy(); + } + } + + Auth().data().sendHistoryChangeNotifications(); + + deleteAllFromUserSend(channel, from); +} + +void ApiWrap::deleteAllFromUserSend( + not_null channel, + not_null from) { + request(MTPchannels_DeleteUserHistory( + channel->inputChannel, + from->inputUser + )).done([=](const MTPmessages_AffectedHistory &result) { + const auto offset = applyAffectedHistory(channel, result); + if (offset > 0) { + deleteAllFromUserSend(channel, from); + } else if (const auto history = App::historyLoaded(channel)) { + if (!history->lastMessageKnown()) { + Auth().api().requestDialogEntry(history); + } + } + }).send(); +} + void ApiWrap::requestChannelMembersForAdd( not_null channel, base::lambda callback) { @@ -1505,23 +1664,23 @@ int ApiWrap::onlineTillFromStatus(const MTPUserStatus &status, int currentOnline } void ApiWrap::clearHistory(not_null peer) { - auto lastMsgId = MsgId(0); + auto deleteTillId = MsgId(0); if (auto history = App::historyLoaded(peer->id)) { - if (history->lastMsg) { - lastMsgId = history->lastMsg->id; - Local::addSavedPeer(history->peer, history->lastMsg->date); + if (const auto last = history->lastMessage()) { + deleteTillId = last->id; + Local::addSavedPeer(history->peer, last->date); } history->clear(); history->newLoaded = history->oldLoaded = true; } - if (auto channel = peer->asChannel()) { - if (auto migrated = peer->migrateFrom()) { + if (const auto channel = peer->asChannel()) { + if (const auto migrated = peer->migrateFrom()) { clearHistory(migrated); } - if (IsServerMsgId(lastMsgId)) { + if (IsServerMsgId(deleteTillId)) { request(MTPchannels_DeleteHistory( channel->inputChannel, - MTP_int(lastMsgId) + MTP_int(deleteTillId) )).send(); } } else { @@ -1530,7 +1689,7 @@ void ApiWrap::clearHistory(not_null peer) { peer->input, MTP_int(0) )).done([=](const MTPmessages_AffectedHistory &result) { - auto offset = applyAffectedHistory(peer, result); + const auto offset = applyAffectedHistory(peer, result); if (offset > 0) { clearHistory(peer); } @@ -1775,6 +1934,94 @@ void ApiWrap::requestParticipantsCountDelayed( [this, channel] { channel->updateFullForced(); }); } +void ApiWrap::requestChannelRangeDifference(not_null history) { + Expects(history->isChannel()); + + const auto channel = history->peer->asChannel(); + if (const auto requestId = _rangeDifferenceRequests.take(channel)) { + request(*requestId).cancel(); + } + const auto range = history->rangeForDifferenceRequest(); + if (!(range.from < range.till) || !channel->pts()) { + return; + } + + MTP_LOG(0, ("getChannelDifference { good - " + "after channelDifferenceTooLong was received, " + "validating history part }%1").arg(cTestMode() ? " TESTMODE" : "")); + channelRangeDifferenceSend(channel, range, channel->pts()); +} + +void ApiWrap::channelRangeDifferenceSend( + not_null channel, + MsgRange range, + int32 pts) { + Expects(range.from < range.till); + + const auto limit = range.till - range.from; + const auto filter = MTP_channelMessagesFilter( + MTP_flags(0), + MTP_vector(1, MTP_messageRange( + MTP_int(range.from), + MTP_int(range.till - 1)))); + const auto requestId = request(MTPupdates_GetChannelDifference( + MTP_flags(MTPupdates_GetChannelDifference::Flag::f_force), + channel->inputChannel, + filter, + MTP_int(pts), + MTP_int(limit) + )).done([=](const MTPupdates_ChannelDifference &result) { + _rangeDifferenceRequests.remove(channel); + channelRangeDifferenceDone(channel, range, result); + }).fail([=](const RPCError &error) { + _rangeDifferenceRequests.remove(channel); + }).send(); + _rangeDifferenceRequests.emplace(channel, requestId); +} + +void ApiWrap::channelRangeDifferenceDone( + not_null channel, + MsgRange range, + const MTPupdates_ChannelDifference &result) { + auto nextRequestPts = int32(0); + auto isFinal = true; + + switch (result.type()) { + case mtpc_updates_channelDifferenceEmpty: { + const auto &d = result.c_updates_channelDifferenceEmpty(); + nextRequestPts = d.vpts.v; + isFinal = d.is_final(); + } break; + + case mtpc_updates_channelDifferenceTooLong: { + const auto &d = result.c_updates_channelDifferenceTooLong(); + + App::feedUsers(d.vusers); + App::feedChats(d.vchats); + + nextRequestPts = d.vpts.v; + isFinal = d.is_final(); + } break; + + case mtpc_updates_channelDifference: { + const auto &d = result.c_updates_channelDifference(); + + App::main()->feedChannelDifference(d); + + nextRequestPts = d.vpts.v; + isFinal = d.is_final(); + } break; + } + + if (!isFinal) { + MTP_LOG(0, ("getChannelDifference { " + "good - after not final channelDifference was received, " + "validating history part }%1" + ).arg(cTestMode() ? " TESTMODE" : "")); + channelRangeDifferenceSend(channel, range, nextRequestPts); + } +} + void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId req) { const QVector *v = 0; switch (msgs.type()) { @@ -3438,7 +3685,11 @@ void ApiWrap::readServerHistory(not_null history) { void ApiWrap::readServerHistoryForce(not_null history) { const auto peer = history->peer; - const auto upTo = history->inboxRead(0); + const auto upTo = history->readInbox(); + if (!upTo) { + return; + } + if (const auto channel = peer->asChannel()) { if (!channel->amIn()) { return; // no read request for channels that I didn't join diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 5e3c46298..bbc32a0ce 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -56,7 +56,10 @@ public: void applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId = 0); void savePinnedOrder(); - void toggleChannelGrouping(not_null channel, bool group); + void toggleChannelGrouping( + not_null channel, + bool group, + base::lambda callback); using RequestMessageDataCallback = base::lambda; void requestMessageData( @@ -65,6 +68,8 @@ public: RequestMessageDataCallback callback); void requestContacts(); + void requestDialogEntry(not_null feed); + void requestDialogEntry(not_null history); void requestFullPeer(PeerData *peer); void requestPeer(PeerData *peer); @@ -73,6 +78,7 @@ public: void requestBots(not_null channel); void requestAdmins(not_null channel); void requestParticipantsCountDelayed(not_null channel); + void requestChannelRangeDifference(not_null history); void requestChangelog( const QString &sinceVersion, @@ -96,6 +102,9 @@ public: void unblockParticipant( not_null channel, not_null user); + void deleteAllFromUser( + not_null channel, + not_null from); void requestWebPageDelayed(WebPageData *page); void clearWebPageRequest(WebPageData *page); @@ -269,6 +278,7 @@ private: QVector collectMessageIds(const MessageDataRequests &requests); MessageDataRequests *messageDataRequests(ChannelData *channel, bool onlyExisting = false); + void applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs); void gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mtpRequestId req); void gotUserFull(UserData *user, const MTPUserFull &result, mtpRequestId req); @@ -285,10 +295,24 @@ private: int availableCount, const QVector &list); void resolveWebPages(); - void gotWebPages(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req); + void gotWebPages( + ChannelData *channel, + const MTPmessages_Messages &result, + mtpRequestId req); void gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result); - PeerData *notifySettingReceived(MTPInputNotifyPeer peer, const MTPPeerNotifySettings &settings); + void channelRangeDifferenceSend( + not_null channel, + MsgRange range, + int32 pts); + void channelRangeDifferenceDone( + not_null channel, + MsgRange range, + const MTPupdates_ChannelDifference &result); + + PeerData *notifySettingReceived( + MTPInputNotifyPeer peer, + const MTPPeerNotifySettings &settings); void stickerSetDisenabled(mtpRequestId requestId); void stickersSaveOrder(); @@ -347,6 +371,11 @@ private: void applyAffectedMessages( not_null peer, const MTPmessages_AffectedMessages &result); + + void deleteAllFromUserSend( + not_null channel, + not_null from); + void sendMessageFail(const RPCError &error); void uploadAlbumMedia( not_null item, @@ -391,7 +420,7 @@ private: const MTPchannels_ChannelParticipants&)> _channelMembersForAddCallback; base::flat_map< not_null, - mtpRequestId> _channelGroupingRequests; + std::pair>> _channelGroupingRequests; using KickRequest = std::pair< not_null, @@ -400,6 +429,10 @@ private: QMap _selfParticipantRequests; + base::flat_map< + not_null, + mtpRequestId> _rangeDifferenceRequests; + QMap _webPagesPending; base::Timer _webPagesTimer; @@ -432,6 +465,8 @@ private: mtpRequestId _contactsRequestId = 0; mtpRequestId _contactsStatusesRequestId = 0; + base::flat_set> _dialogFeedRequests; + base::flat_set> _dialogRequests; base::flat_map, mtpRequestId> _unreadMentionsRequests; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index dd915f091..7858994d2 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -459,7 +459,7 @@ namespace { if (auto h = App::historyLoaded(cdata->id)) { if (auto hto = App::historyLoaded(channel->id)) { if (!h->isEmpty()) { - h->clear(true); + h->unloadBlocks(); } if (hto->inChatList(Dialogs::Mode::All) && h->inChatList(Dialogs::Mode::All)) { App::main()->removeDialog(h); @@ -1019,7 +1019,7 @@ namespace { } void feedInboxRead(const PeerId &peer, MsgId upTo) { - if (auto history = App::historyLoaded(peer)) { + if (const auto history = App::historyLoaded(peer)) { history->inboxRead(upTo); } } @@ -1027,17 +1027,8 @@ namespace { void feedOutboxRead(const PeerId &peer, MsgId upTo, TimeId when) { if (auto history = App::historyLoaded(peer)) { history->outboxRead(upTo); - if (history->lastMsg - && history->lastMsg->out() - && history->lastMsg->id <= upTo) { - if (const auto main = App::main()) { - main->repaintDialogRow(history, history->lastMsg->id); - } - } - history->updateChatListEntry(); - - if (history->peer->isUser()) { - history->peer->asUser()->madeAction(when); + if (const auto user = history->peer->asUser()) { + user->madeAction(when); } } } @@ -1055,37 +1046,31 @@ namespace { return &(*i); } - void feedWereDeleted(ChannelId channelId, const QVector &msgsIds) { + void feedWereDeleted( + ChannelId channelId, + const QVector &msgsIds) { const auto data = fetchMsgsData(channelId, false); if (!data) return; - const auto channelHistory = (channelId != NoChannel) - ? App::history(peerFromChannel(channelId))->asChannelHistory() + const auto affectedHistory = (channelId != NoChannel) + ? App::history(peerFromChannel(channelId)).get() : nullptr; - base::flat_set> historiesToCheck; + auto historiesToCheck = base::flat_set>(); for (const auto msgId : msgsIds) { auto j = data->constFind(msgId.v); if (j != data->cend()) { - const auto h = (*j)->history(); + const auto history = (*j)->history(); (*j)->destroy(); - if (!h->lastMsg) { - historiesToCheck.emplace(h); - } - } else { - if (channelHistory) { - if (channelHistory->unreadCount() > 0 - && msgId.v >= channelHistory->inboxReadBefore) { - channelHistory->setUnreadCount( - channelHistory->unreadCount() - 1); - } + if (!history->lastMessageKnown()) { + historiesToCheck.emplace(history); } + } else if (affectedHistory) { + affectedHistory->unknownMessageDeleted(msgId.v); } } - if (main()) { - for (const auto history : historiesToCheck) { - main()->checkPeerHistory(history->peer); - } + for (const auto history : historiesToCheck) { + Auth().api().requestDialogEntry(history); } } @@ -1222,10 +1207,6 @@ namespace { return ::histories.findOrInsert(peer); } - not_null historyFromDialog(const PeerId &peer, int32 unreadCnt, int32 maxInboxRead, int32 maxOutboxRead) { - return ::histories.findOrInsert(peer, unreadCnt, maxInboxRead, maxOutboxRead); - } - History *historyLoaded(const PeerId &peer) { if (!peer) { return nullptr; diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 3bc8c2cc8..879327c77 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -148,7 +148,6 @@ namespace App { Histories &histories(); not_null history(const PeerId &peer); - not_null historyFromDialog(const PeerId &peer, int32 unreadCnt, int32 maxInboxRead, int32 maxOutboxRead); History *historyLoaded(const PeerId &peer); HistoryItem *histItemById(ChannelId channelId, MsgId itemId); inline not_null history(const PeerData *peer) { diff --git a/Telegram/SourceFiles/boxes/confirm_box.cpp b/Telegram/SourceFiles/boxes/confirm_box.cpp index 6ddeffd91..cb67cbd8b 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.cpp +++ b/Telegram/SourceFiles/boxes/confirm_box.cpp @@ -568,7 +568,7 @@ void DeleteMessagesBox::deleteAndClear() { MTP_vector(1, MTP_int(_ids[0].msg)))); } if (_deleteAll && _deleteAll->checked()) { - App::main()->deleteAllFromUser( + Auth().api().deleteAllFromUser( _moderateInChannel, _moderateFrom); } @@ -583,13 +583,13 @@ void DeleteMessagesBox::deleteAndClear() { if (auto item = App::histItemById(itemId)) { auto history = item->history(); auto wasOnServer = (item->id > 0); - auto wasLast = (history->lastMsg == item); + auto wasLast = (history->lastMessage() == item); item->destroy(); if (wasOnServer) { idsByPeer[history->peer].push_back(MTP_int(itemId.msg)); } else if (wasLast) { - App::main()->checkPeerHistory(history->peer); + Auth().api().requestDialogEntry(history); } } } diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index 0680b053c..fc0da2280 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -372,6 +372,7 @@ std::unique_ptr ContactsBoxController::createSearchRow(not_null row) { + Auth().api().requestDialogEntry(App::history(row->peer())); Ui::showPeerHistory(row->peer(), ShowAtUnreadMsgId); } diff --git a/Telegram/SourceFiles/data/data_feed.cpp b/Telegram/SourceFiles/data/data_feed.cpp index d99e0e760..07d927ce2 100644 --- a/Telegram/SourceFiles/data/data_feed.cpp +++ b/Telegram/SourceFiles/data/data_feed.cpp @@ -72,8 +72,10 @@ void Feed::registerOne(not_null channel) { if (!base::contains(_channels, history)) { const auto invisible = (_channels.size() < 2); _channels.push_back(history); - if (history->lastMsg) { - updateLastMessage(history->lastMsg); + if (history->lastMessageKnown()) { + recountLastMessage(); + } else if (_channelsLoaded) { + _parent->session().api().requestDialogEntry(history); } _parent->session().storage().remove( Storage::FeedMessagesInvalidate(_id)); @@ -96,8 +98,10 @@ void Feed::unregisterOne(not_null channel) { if (i != end(_channels)) { const auto visible = (_channels.size() > 1); _channels.erase(i, end(_channels)); - if (_lastMessage && _lastMessage->history() == history) { - messageRemoved(_lastMessage); + if (const auto last = lastMessage()) { + if (last->history() == history) { + recountLastMessage(); + } } _parent->session().storage().remove( Storage::FeedMessagesRemoveAll(_id, channel->bareId())); @@ -115,8 +119,10 @@ void Feed::unregisterOne(not_null channel) { } void Feed::updateLastMessage(not_null item) { - if (justSetLastMessage(item)) { - setChatsListDate(_lastMessage->date); + if (justUpdateLastMessage(item)) { + if (_lastMessage && *_lastMessage) { + setChatsListDate((*_lastMessage)->date); + } } } @@ -207,8 +213,11 @@ void Feed::setChannels(std::vector> channels) { _parent->notifyFeedUpdated(this, FeedUpdateFlag::Channels); } -bool Feed::justSetLastMessage(not_null item) { - if (_lastMessage && item->position() <= _lastMessage->position()) { +bool Feed::justUpdateLastMessage(not_null item) { + if (!_lastMessage) { + return false; + } else if (*_lastMessage + && item->position() <= (*_lastMessage)->position()) { return false; } _lastMessage = item; @@ -216,36 +225,106 @@ bool Feed::justSetLastMessage(not_null item) { } void Feed::messageRemoved(not_null item) { - if (_lastMessage == item) { + if (lastMessage() == item) { + _lastMessage = base::none; recountLastMessage(); } } void Feed::historyCleared(not_null history) { - if (_lastMessage->history() == history) { - recountLastMessage(); + if (const auto last = lastMessage()) { + if (last->history() == history) { + messageRemoved(last); + } } } void Feed::recountLastMessage() { - _lastMessage = nullptr; for (const auto history : _channels) { - if (const auto last = history->lastMsg) { - justSetLastMessage(last); + if (!history->lastMessageKnown()) { + return; } } - if (_lastMessage) { - setChatsListDate(_lastMessage->date); + _lastMessage = nullptr; + for (const auto history : _channels) { + if (const auto last = history->lastMessage()) { + justUpdateLastMessage(last); + } + } + updateChatsListDate(); +} + +void Feed::updateChatsListDate() { + if (_lastMessage && *_lastMessage) { + setChatsListDate((*_lastMessage)->date); } } -void Feed::setUnreadCounts(int unreadCount, int unreadMutedCount) { - _unreadCount = unreadCount; +HistoryItem *Feed::lastMessage() const { + return _lastMessage ? *_lastMessage : nullptr; +} + +bool Feed::lastMessageKnown() const { + return !!_lastMessage; +} + +void Feed::applyDialog(const MTPDdialogFeed &data) { + const auto addChannel = [&](ChannelId channelId) { + if (const auto channel = App::channelLoaded(channelId)) { + channel->setFeed(this); + } + }; + for (const auto &channelId : data.vfeed_other_channels.v) { + addChannel(channelId.v); + } + + _lastMessage = nullptr; + if (const auto peerId = peerFromMTP(data.vpeer)) { + if (const auto channelId = peerToChannel(peerId)) { + addChannel(channelId); + const auto fullId = FullMsgId(channelId, data.vtop_message.v); + if (const auto item = App::histItemById(fullId)) { + justUpdateLastMessage(item); + } + } + } + updateChatsListDate(); + + setUnreadCounts( + data.vunread_count.v, + data.vunread_muted_count.v); + if (data.has_read_max_position()) { + setUnreadPosition(FeedPositionFromMTP(data.vread_max_position)); + } +} + +void Feed::setUnreadCounts(int unreadNonMutedCount, int unreadMutedCount) { + _unreadCount = unreadNonMutedCount + unreadMutedCount; _unreadMutedCount = unreadMutedCount; } void Feed::setUnreadPosition(const MessagePosition &position) { - _unreadPosition = position; + if (_unreadPosition.current() < position) { + _unreadPosition = position; + } +} + +void Feed::unreadCountChanged( + base::optional unreadCountDelta, + int mutedCountDelta) { + if (!_unreadCount) { + return; + } + if (unreadCountDelta) { + *_unreadCount = std::max(*_unreadCount + *unreadCountDelta, 0); + _unreadMutedCount = snap( + _unreadMutedCount + mutedCountDelta, + 0, + *_unreadCount); + updateChatListEntry(); + } else { + _parent->session().api().requestDialogEntry(this); + } } MessagePosition Feed::unreadPosition() const { @@ -265,15 +344,15 @@ bool Feed::shouldBeInChatList() const { } int Feed::chatListUnreadCount() const { - return _unreadCount; + return _unreadCount ? *_unreadCount : 0; } bool Feed::chatListMutedBadge() const { - return _unreadCount <= _unreadMutedCount; + return _unreadCount ? (*_unreadCount <= _unreadMutedCount) : false; } HistoryItem *Feed::chatsListItem() const { - return _lastMessage; + return lastMessage(); } const QString &Feed::chatsListName() const { diff --git a/Telegram/SourceFiles/data/data_feed.h b/Telegram/SourceFiles/data/data_feed.h index 2ff450778..708001e87 100644 --- a/Telegram/SourceFiles/data/data_feed.h +++ b/Telegram/SourceFiles/data/data_feed.h @@ -32,6 +32,7 @@ MessagePosition FeedPositionFromMTP(const MTPFeedPosition &position); class Feed : public Dialogs::Entry { public: static constexpr auto kId = 1; + static constexpr auto kChannelsLimit = 1000; Feed(FeedId id, not_null parent); @@ -43,11 +44,18 @@ public: void messageRemoved(not_null item); void historyCleared(not_null history); - void setUnreadCounts(int unreadCount, int unreadMutedCount); + void applyDialog(const MTPDdialogFeed &data); + void setUnreadCounts(int unreadNonMutedCount, int unreadMutedCount); void setUnreadPosition(const MessagePosition &position); + void unreadCountChanged( + base::optional unreadCountDelta, + int mutedCountDelta); MessagePosition unreadPosition() const; rpl::producer unreadPositionChanges() const; + HistoryItem *lastMessage() const; + bool lastMessageKnown() const; + bool toImportant() const override; bool shouldBeInChatList() const override; int chatListUnreadCount() const override; @@ -73,7 +81,8 @@ public: private: void indexNameParts(); void recountLastMessage(); - bool justSetLastMessage(not_null item); + bool justUpdateLastMessage(not_null item); + void updateChatsListDate(); FeedId _id = 0; not_null _parent; @@ -83,10 +92,10 @@ private: QString _name; base::flat_set _nameWords; base::flat_set _nameFirstLetters; - HistoryItem *_lastMessage = nullptr; + base::optional _lastMessage; rpl::variable _unreadPosition; - int _unreadCount = 0; + base::optional _unreadCount; int _unreadMutedCount = 0; }; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index c7495dc84..2531d7f96 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -849,13 +849,22 @@ void ChannelData::setAvailableMinId(MsgId availableMinId) { if (auto history = App::historyLoaded(this)) { history->clearUpTill(availableMinId); } + if (_pinnedMessageId <= _availableMinId) { + _pinnedMessageId = MsgId(0); + Notify::peerUpdatedDelayed( + this, + Notify::PeerUpdate::Flag::ChannelPinnedChanged); + } } } void ChannelData::setPinnedMessageId(MsgId messageId) { + messageId = (messageId > _availableMinId) ? messageId : MsgId(0); if (_pinnedMessageId != messageId) { _pinnedMessageId = messageId; - Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::ChannelPinnedChanged); + Notify::peerUpdatedDelayed( + this, + Notify::PeerUpdate::Flag::ChannelPinnedChanged); } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index f9f0d3905..9793f21d8 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1726,76 +1726,32 @@ void DialogsInner::applyDialog(const MTPDdialog &dialog) { return; } - const auto history = App::historyFromDialog( - peerId, - dialog.vunread_count.v, - dialog.vread_inbox_max_id.v, - dialog.vread_outbox_max_id.v); - history->setUnreadMentionsCount(dialog.vunread_mentions_count.v); - const auto peer = history->peer; - if (const auto channel = peer->asChannel()) { - if (dialog.has_pts()) { - channel->ptsReceived(dialog.vpts.v); - } - if (!channel->amCreator()) { - const auto topMsgId = FullMsgId( - peerToChannel(channel->id), - dialog.vtop_message.v); - if (const auto topMsg = App::histItemById(topMsgId)) { - if (topMsg->date <= date(channel->date)) { - Auth().api().requestSelfParticipant(channel); - } - } - } - } - App::main()->applyNotifySetting( - MTP_notifyPeer(dialog.vpeer), - dialog.vnotify_settings, - history); + const auto history = App::history(peerId); + history->applyDialog(dialog); if (!history->isPinnedDialog() && !history->chatsListDate().isNull()) { addSavedPeersAfter(history->chatsListDate()); } _contactsNoDialogs->del(history); - if (peer->migrateFrom()) { - if (const auto historyFrom = App::historyLoaded(peer->migrateFrom())) { + if (const auto from = history->peer->migrateFrom()) { + if (const auto historyFrom = App::historyLoaded(from)) { removeDialog(historyFrom); } - } else if (peer->migrateTo() && peer->migrateTo()->amIn()) { - removeDialog(history); - } - - if (dialog.has_draft() && dialog.vdraft.type() == mtpc_draftMessage) { - const auto &draft = dialog.vdraft.c_draftMessage(); - Data::applyPeerCloudDraft(peerId, draft); + } else if (const auto to = history->peer->migrateTo()) { + if (to->amIn()) { + removeDialog(history); + } } } void DialogsInner::applyFeedDialog(const MTPDdialogFeed &dialog) { - const auto peerId = peerFromMTP(dialog.vpeer); const auto feedId = dialog.vfeed_id.v; const auto feed = Auth().data().feed(feedId); - const auto addChannel = [&](ChannelId channelId) { - if (const auto channel = App::channelLoaded(channelId)) { - channel->setFeed(feed); - } - }; - if (peerId && peerIsChannel(peerId)) { - addChannel(peerToChannel(peerId)); - } - for (const auto &channelId : dialog.vfeed_other_channels.v) { - addChannel(channelId.v); - } - feed->setUnreadCounts( - dialog.vunread_count.v, - dialog.vunread_muted_count.v); + feed->applyDialog(dialog); + if (!feed->isPinnedDialog() && !feed->chatsListDate().isNull()) { addSavedPeersAfter(feed->chatsListDate()); } - if (dialog.has_read_max_position()) { - feed->setUnreadPosition( - Data::FeedPositionFromMTP(dialog.vread_max_position)); - } } void DialogsInner::addSavedPeersAfter(const QDateTime &date) { diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 4aced1bd1..cbfdf31f7 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -52,8 +52,151 @@ constexpr auto kStatusShowClientsidePlayGame = 10000; constexpr auto kSetMyActionForMs = 10000; constexpr auto kNewBlockEachMessage = 50; +void checkForSwitchInlineButton(HistoryItem *item) { + if (item->out() || !item->hasSwitchInlineButton()) { + return; + } + if (const auto user = item->history()->peer->asUser()) { + if (!user->botInfo || !user->botInfo->inlineReturnPeerId) { + return; + } + if (const auto markup = item->Get()) { + for_const (auto &row, markup->rows) { + for_const (auto &button, row) { + if (button.type == HistoryMessageMarkupButton::Type::SwitchInline) { + Notify::switchInlineBotButtonReceived(QString::fromUtf8(button.data)); + return; + } + } + } + } + } +} + } // namespace +Histories::Histories() +: _a_typings(animation(this, &Histories::step_typings)) +, _selfDestructTimer([this] { checkSelfDestructItems(); }) { +} + +History *Histories::find(PeerId peerId) const { + if (const auto i = _map.find(peerId); i != _map.end()) { + return i->second.get(); + } + return nullptr; +} + +not_null Histories::findOrInsert(PeerId peerId) { + if (const auto result = find(peerId)) { + return result; + } + const auto [i, ok] = _map.emplace( + peerId, + std::make_unique(peerId)); + return i->second.get(); +} + +void Histories::clear() { + App::historyClearMsgs(); + + _map.clear(); + + _unreadFull = _unreadMuted = 0; + Notify::unreadCounterUpdated(); + App::historyClearItems(); + typing.clear(); +} + +void Histories::registerSendAction( + not_null history, + not_null user, + const MTPSendMessageAction &action, + TimeId when) { + if (history->updateSendActionNeedsAnimating(user, action)) { + user->madeAction(when); + + auto i = typing.find(history); + if (i == typing.cend()) { + typing.insert(history, getms()); + _a_typings.start(); + } + } +} + +void Histories::step_typings(TimeMs ms, bool timer) { + for (auto i = typing.begin(), e = typing.end(); i != e;) { + if (i.key()->updateSendActionNeedsAnimating(ms)) { + ++i; + } else { + i = typing.erase(i); + } + } + if (typing.isEmpty()) { + _a_typings.stop(); + } +} + +void Histories::remove(const PeerId &peer) { + const auto i = _map.find(peer); + if (i != _map.cend()) { + typing.remove(i->second.get()); + _map.erase(i); + } +} + +HistoryItem *Histories::addNewMessage( + const MTPMessage &msg, + NewMessageType type) { + auto peer = peerFromMessage(msg); + if (!peer) return nullptr; + + auto result = App::history(peer)->addNewMessage(msg, type); + if (result && type == NewMessageUnread) { + checkForSwitchInlineButton(result); + } + return result; +} + +int Histories::unreadBadge() const { + return _unreadFull - (Global::IncludeMuted() ? 0 : _unreadMuted); +} + +bool Histories::unreadOnlyMuted() const { + return Global::IncludeMuted() ? (_unreadMuted >= _unreadFull) : false; +} + +void Histories::selfDestructIn(not_null item, TimeMs delay) { + _selfDestructItems.push_back(item->fullId()); + if (!_selfDestructTimer.isActive() || _selfDestructTimer.remainingTime() > delay) { + _selfDestructTimer.callOnce(delay); + } +} + +void Histories::checkSelfDestructItems() { + auto now = getms(true); + auto nextDestructIn = TimeMs(0); + for (auto i = _selfDestructItems.begin(); i != _selfDestructItems.cend();) { + if (auto item = App::histItemById(*i)) { + if (auto destructIn = item->getSelfDestructIn(now)) { + if (nextDestructIn > 0) { + accumulate_min(nextDestructIn, destructIn); + } else { + nextDestructIn = destructIn; + } + ++i; + } else { + i = _selfDestructItems.erase(i); + } + } else { + i = _selfDestructItems.erase(i); + } + } + if (nextDestructIn > 0) { + _selfDestructTimer.callOnce(nextDestructIn); + } +} + History::History(const PeerId &peerId) : Entry(this) , peer(App::peer(peerId)) @@ -62,7 +205,7 @@ History::History(const PeerId &peerId) , _sendActionText(st::dialogsTextWidthMin) { if (const auto user = peer->asUser()) { if (user->botInfo) { - outboxReadBefore = std::numeric_limits::max(); + _outboxReadBefore = std::numeric_limits::max(); } } } @@ -85,10 +228,80 @@ int History::height() const { return _height; } +void History::removeNotification(not_null item) { + if (!notifies.isEmpty()) { + for (auto i = notifies.begin(), e = notifies.end(); i != e; ++i) { + if ((*i) == item) { + notifies.erase(i); + break; + } + } + } +} + +HistoryItem *History::currentNotification() { + return notifies.isEmpty() ? 0 : notifies.front(); +} + +bool History::hasNotification() const { + return !notifies.isEmpty(); +} + +void History::skipNotification() { + if (!notifies.isEmpty()) { + notifies.pop_front(); + } +} + +void History::popNotification(HistoryItem *item) { + if (!notifies.isEmpty() && notifies.back() == item) notifies.pop_back(); +} + +bool History::hasPendingResizedItems() const { + return _flags & Flag::f_has_pending_resized_items; +} + void History::setHasPendingResizedItems() { _flags |= Flag::f_has_pending_resized_items; } +void History::itemRemoved(not_null item) { + item->removeMainView(); + if (lastMessage() == item) { + _lastMessage = base::none; + if (loadedAtBottom()) { + if (const auto last = lastAvailableMessage()) { + setLastMessage(last); + } + } + if (const auto channel = peer->asChannel()) { + if (const auto feed = channel->feed()) { + // Must be after history->lastMessage() is updated. + // Otherwise feed last message will be this value again. + feed->messageRemoved(item); + } + } + } + itemVanished(item); +} + +void History::itemVanished(not_null item) { + removeNotification(item); + if (lastKeyboardId == item->id) { + clearLastKeyboard(); + } + if ((!item->out() || item->isPost()) + && item->unread() + && unreadCount() > 0) { + setUnreadCount(unreadCount() - 1); + } + if (const auto channel = peer->asChannel()) { + if (channel->pinnedMessageId() == item->id) { + channel->clearPinnedMessage(); + } + } +} + void History::setLocalDraft(std::unique_ptr &&draft) { _localDraft = std::move(draft); } @@ -374,378 +587,6 @@ bool History::updateSendActionNeedsAnimating(TimeMs ms, bool force) { return result; } -void ChannelHistory::getRangeDifference() { - auto fromId = MsgId(0); - auto toId = MsgId(0); - for (auto blockIndex = 0, blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) { - const auto &block = blocks[blockIndex]; - for (auto itemIndex = 0, itemsCount = int(block->messages.size()); itemIndex < itemsCount; ++itemIndex) { - const auto id = block->messages[itemIndex]->data()->id; - if (id > 0) { - fromId = id; - break; - } - } - if (fromId) break; - } - if (!fromId) return; - - for (auto blockIndex = blocks.size(); blockIndex > 0;) { - const auto &block = blocks[--blockIndex]; - for (auto itemIndex = block->messages.size(); itemIndex > 0;) { - const auto id = block->messages[--itemIndex]->data()->id; - if (id > 0) { - toId = id; - break; - } - } - if (toId) break; - } - if (fromId > 0 && peer->asChannel()->pts() > 0) { - if (_rangeDifferenceRequestId) { - MTP::cancel(_rangeDifferenceRequestId); - } - _rangeDifferenceFromId = fromId; - _rangeDifferenceToId = toId; - - MTP_LOG(0, ("getChannelDifference { good - after channelDifferenceTooLong was received, validating history part }%1").arg(cTestMode() ? " TESTMODE" : "")); - getRangeDifferenceNext(peer->asChannel()->pts()); - } -} - -void ChannelHistory::getRangeDifferenceNext(int32 pts) { - if (!App::main() || _rangeDifferenceToId < _rangeDifferenceFromId) return; - - int limit = _rangeDifferenceToId + 1 - _rangeDifferenceFromId; - - auto filter = MTP_channelMessagesFilter(MTP_flags(0), MTP_vector(1, MTP_messageRange(MTP_int(_rangeDifferenceFromId), MTP_int(_rangeDifferenceToId)))); - auto flags = MTPupdates_GetChannelDifference::Flag::f_force; - _rangeDifferenceRequestId = MTP::send(MTPupdates_GetChannelDifference(MTP_flags(flags), peer->asChannel()->inputChannel, filter, MTP_int(pts), MTP_int(limit)), App::main()->rpcDone(&MainWidget::gotRangeDifference, peer->asChannel())); -} - -HistoryService *ChannelHistory::insertJoinedMessage(bool unread) { - if (_joinedMessage - || !peer->asChannel()->amIn() - || (peer->isMegagroup() - && peer->asChannel()->mgInfo->joinedMessageFound)) { - return _joinedMessage; - } - - const auto inviter = (peer->asChannel()->inviter > 0) - ? App::userLoaded(peer->asChannel()->inviter) - : nullptr; - if (!inviter) { - return nullptr; - } - - MTPDmessage::Flags flags = 0; - if (inviter->id == Auth().userPeerId()) { - unread = false; - //} else if (unread) { - // flags |= MTPDmessage::Flag::f_unread; - } - - auto inviteDate = peer->asChannel()->inviteDate; - if (unread) _maxReadMessageDate = inviteDate; - if (isEmpty()) { - _joinedMessage = GenerateJoinedMessage( - this, - inviteDate, - inviter, - flags); - addNewItem(_joinedMessage, unread); - return _joinedMessage; - } - - for (auto blockIndex = blocks.size(); blockIndex > 0;) { - const auto &block = blocks[--blockIndex]; - for (auto itemIndex = block->messages.size(); itemIndex > 0;) { - const auto item = block->messages[--itemIndex]->data(); - - // Due to a server bug sometimes inviteDate is less (before) than the - // first message in the megagroup (message about migration), let us - // ignore that and think, that the inviteDate is always greater-or-equal. - if (item->isGroupMigrate() - && peer->isMegagroup() - && peer->migrateFrom()) { - peer->asChannel()->mgInfo->joinedMessageFound = true; - return nullptr; - } - if (item->date <= inviteDate) { - ++itemIndex; - _joinedMessage = GenerateJoinedMessage( - this, - inviteDate, - inviter, - flags); - addNewInTheMiddle(_joinedMessage, blockIndex, itemIndex); - const auto lastDate = chatsListDate(); - if (lastDate.isNull() || inviteDate >= lastDate) { - setLastMessage(_joinedMessage); - if (unread) { - newItemAdded(_joinedMessage); - } - } - return _joinedMessage; - } - } - } - - startBuildingFrontBlock(); - _joinedMessage = GenerateJoinedMessage( - this, - inviteDate, - inviter, - flags); - addItemToBlock(_joinedMessage); - finishBuildingFrontBlock(); - - return _joinedMessage; -} - -void ChannelHistory::checkJoinedMessage(bool createUnread) { - if (_joinedMessage || peer->asChannel()->inviter <= 0) { - return; - } - if (isEmpty()) { - if (loadedAtTop() && loadedAtBottom()) { - if (insertJoinedMessage(createUnread)) { - if (_joinedMessage->mainView()) { - setLastMessage(_joinedMessage); - } - } - return; - } - } - - QDateTime inviteDate = peer->asChannel()->inviteDate; - QDateTime firstDate, lastDate; - if (!blocks.empty()) { - firstDate = blocks.front()->messages.front()->data()->date; - lastDate = blocks.back()->messages.back()->data()->date; - } - if (!firstDate.isNull() && !lastDate.isNull() && (firstDate <= inviteDate || loadedAtTop()) && (lastDate > inviteDate || loadedAtBottom())) { - bool willBeLastMsg = (inviteDate >= lastDate); - if (insertJoinedMessage(createUnread && willBeLastMsg) && willBeLastMsg) { - if (_joinedMessage->mainView()) { - setLastMessage(_joinedMessage); - } - } - } -} - -void ChannelHistory::checkMaxReadMessageDate() { - if (_maxReadMessageDate.isValid()) return; - - for (auto blockIndex = blocks.size(); blockIndex > 0;) { - const auto &block = blocks[--blockIndex]; - for (auto itemIndex = block->messages.size(); itemIndex > 0;) { - const auto item = block->messages[--itemIndex]->data(); - if (!item->unread()) { - _maxReadMessageDate = item->date; - if (item->isGroupMigrate() && isMegagroup() && peer->migrateFrom()) { - // No report spam panel. - _maxReadMessageDate = date(MTP_int(peer->asChannel()->date + 1)); - } - return; - } - } - } - if (loadedAtTop() && (!isMegagroup() || !isEmpty())) { - _maxReadMessageDate = date(MTP_int(peer->asChannel()->date)); - } -} - -const QDateTime &ChannelHistory::maxReadMessageDate() { - return _maxReadMessageDate; -} - -void ChannelHistory::cleared(bool leaveItems) { - _joinedMessage = nullptr; -} - -void ChannelHistory::messageDetached(not_null message) { - if (_joinedMessage == message) { - _joinedMessage = nullptr; - } -} - -ChannelHistory::~ChannelHistory() { - // all items must be destroyed before ChannelHistory is destroyed - // or they will call history()->asChannelHistory() -> undefined behaviour - clearOnDestroy(); -} - -History *Histories::find(const PeerId &peerId) { - Map::const_iterator i = map.constFind(peerId); - return (i == map.cend()) ? 0 : i.value(); -} - -not_null Histories::findOrInsert(const PeerId &peerId) { - auto i = map.constFind(peerId); - if (i == map.cend()) { - auto history = peerIsChannel(peerId) - ? static_cast(new ChannelHistory(peerId)) - : (new History(peerId)); - i = map.insert(peerId, history); - } - return i.value(); -} - -not_null Histories::findOrInsert( - const PeerId &peerId, - int unreadCount, - MsgId maxInboxRead, - MsgId maxOutboxRead) { - auto i = map.constFind(peerId); - if (i == map.cend()) { - auto history = peerIsChannel(peerId) - ? static_cast(new ChannelHistory(peerId)) - : (new History(peerId)); - i = map.insert(peerId, history); - history->setUnreadCount(unreadCount); - history->inboxReadBefore = maxInboxRead + 1; - history->outboxReadBefore = maxOutboxRead + 1; - } else { - auto history = i.value(); - if (unreadCount > history->unreadCount()) { - history->setUnreadCount(unreadCount); - } - accumulate_max(history->inboxReadBefore, maxInboxRead + 1); - accumulate_max(history->outboxReadBefore, maxOutboxRead + 1); - } - return i.value(); -} - -void Histories::clear() { - App::historyClearMsgs(); - - auto temp = base::take(map); - for_const (auto history, temp) { - delete history; - } - - _unreadFull = _unreadMuted = 0; - Notify::unreadCounterUpdated(); - App::historyClearItems(); - typing.clear(); -} - -void Histories::registerSendAction( - not_null history, - not_null user, - const MTPSendMessageAction &action, - TimeId when) { - if (history->updateSendActionNeedsAnimating(user, action)) { - user->madeAction(when); - - auto i = typing.find(history); - if (i == typing.cend()) { - typing.insert(history, getms()); - _a_typings.start(); - } - } -} - -void Histories::step_typings(TimeMs ms, bool timer) { - for (auto i = typing.begin(), e = typing.end(); i != e;) { - if (i.key()->updateSendActionNeedsAnimating(ms)) { - ++i; - } else { - i = typing.erase(i); - } - } - if (typing.isEmpty()) { - _a_typings.stop(); - } -} - -void Histories::remove(const PeerId &peer) { - const auto i = map.find(peer); - if (i != map.cend()) { - typing.remove(i.value()); - delete i.value(); - map.erase(i); - } -} - -namespace { - -void checkForSwitchInlineButton(HistoryItem *item) { - if (item->out() || !item->hasSwitchInlineButton()) { - return; - } - if (UserData *user = item->history()->peer->asUser()) { - if (!user->botInfo || !user->botInfo->inlineReturnPeerId) { - return; - } - if (auto markup = item->Get()) { - for_const (auto &row, markup->rows) { - for_const (auto &button, row) { - if (button.type == HistoryMessageMarkupButton::Type::SwitchInline) { - Notify::switchInlineBotButtonReceived(QString::fromUtf8(button.data)); - return; - } - } - } - } - } -} - -} // namespace - -HistoryItem *Histories::addNewMessage( - const MTPMessage &msg, - NewMessageType type) { - auto peer = peerFromMessage(msg); - if (!peer) return nullptr; - - auto result = App::history(peer)->addNewMessage(msg, type); - if (result && type == NewMessageUnread) { - checkForSwitchInlineButton(result); - } - return result; -} - -int Histories::unreadBadge() const { - return _unreadFull - (Global::IncludeMuted() ? 0 : _unreadMuted); -} - -bool Histories::unreadOnlyMuted() const { - return Global::IncludeMuted() ? (_unreadMuted >= _unreadFull) : false; -} - -void Histories::selfDestructIn(not_null item, TimeMs delay) { - _selfDestructItems.push_back(item->fullId()); - if (!_selfDestructTimer.isActive() || _selfDestructTimer.remainingTime() > delay) { - _selfDestructTimer.callOnce(delay); - } -} - -void Histories::checkSelfDestructItems() { - auto now = getms(true); - auto nextDestructIn = TimeMs(0); - for (auto i = _selfDestructItems.begin(); i != _selfDestructItems.cend();) { - if (auto item = App::histItemById(*i)) { - if (auto destructIn = item->getSelfDestructIn(now)) { - if (nextDestructIn > 0) { - accumulate_min(nextDestructIn, destructIn); - } else { - nextDestructIn = destructIn; - } - ++i; - } else { - i = _selfDestructItems.erase(i); - } - } else { - i = _selfDestructItems.erase(i); - } - } - if (nextDestructIn > 0) { - _selfDestructTimer.callOnce(nextDestructIn); - } -} - HistoryItem *History::createItem( const MTPMessage &message, bool detachExistingItem) { @@ -884,7 +725,9 @@ not_null History::addNewService( unread); } -HistoryItem *History::addNewMessage(const MTPMessage &msg, NewMessageType type) { +HistoryItem *History::addNewMessage( + const MTPMessage &msg, + NewMessageType type) { if (type == NewMessageExisting) { return addToHistory(msg); } @@ -1427,6 +1270,9 @@ void History::mainViewRemoved( if (lastSentMsg == view->data()) { lastSentMsg = nullptr; } + if (_joinedMessage == view->data()) { + _joinedMessage = nullptr; + } if (_firstUnreadView == view) { getNextFirstUnreadMessage(); } @@ -1528,10 +1374,7 @@ void History::addEdgesToSharedMedia() { void History::addOlderSlice(const QVector &slice) { if (slice.isEmpty()) { oldLoaded = true; - if (isChannel()) { - asChannelHistory()->checkJoinedMessage(); - asChannelHistory()->checkMaxReadMessageDate(); - } + checkJoinedMessage(); return; } @@ -1553,11 +1396,8 @@ void History::addOlderSlice(const QVector &slice) { addEdgesToSharedMedia(); } - if (isChannel()) { - asChannelHistory()->checkJoinedMessage(); - asChannelHistory()->checkMaxReadMessageDate(); - } - checkLastMsg(); + checkJoinedMessage(); + checkLastMessage(); } void History::addNewerSlice(const QVector &slice) { @@ -1565,7 +1405,7 @@ void History::addNewerSlice(const QVector &slice) { if (slice.isEmpty()) { newLoaded = true; - if (!lastMsg) { + if (!lastMessage()) { setLastMessage(lastAvailableMessage()); } } @@ -1588,13 +1428,13 @@ void History::addNewerSlice(const QVector &slice) { checkAddAllToUnreadMentions(); } - if (isChannel()) asChannelHistory()->checkJoinedMessage(); - checkLastMsg(); + checkJoinedMessage(); + checkLastMessage(); } -void History::checkLastMsg() { - if (lastMsg) { - if (!newLoaded && lastMsg->mainView()) { +void History::checkLastMessage() { + if (const auto last = lastMessage()) { + if (!newLoaded && last->mainView()) { newLoaded = true; checkAddAllToUnreadMentions(); } @@ -1739,7 +1579,7 @@ int History::countUnread(MsgId upTo) { } void History::calculateFirstUnreadMessage() { - if (_firstUnreadView) { + if (_firstUnreadView || !_inboxReadBefore) { return; } @@ -1753,7 +1593,7 @@ void History::calculateFirstUnreadMessage() { if (!IsServerMsgId(item->id)) { continue; } else if (!item->out() || !_firstUnreadView) { - if (item->id >= inboxReadBefore) { + if (item->id >= *_inboxReadBefore) { _firstUnreadView = view; } else { return; @@ -1763,16 +1603,23 @@ void History::calculateFirstUnreadMessage() { } } -MsgId History::inboxRead(MsgId upTo) { - if (upTo < 0) return upTo; - if (unreadCount()) { - if (upTo && loadedAtBottom()) App::main()->historyToDown(this); - setUnreadCount(upTo ? countUnread(upTo) : 0); +MsgId History::readInbox() { + const auto upTo = msgIdForRead(); + setUnreadCount(0); + if (upTo) { + inboxRead(upTo); } + return upTo; +} - if (!upTo) upTo = msgIdForRead(); - accumulate_max(inboxReadBefore, upTo + 1); - +void History::inboxRead(MsgId upTo) { + if (unreadCount()) { + if (loadedAtBottom()) { + App::main()->historyToDown(this); + } + setUnreadCount(countUnread(upTo)); + } + setInboxReadTill(upTo); updateChatListEntry(); if (peer->migrateTo()) { if (auto migrateTo = App::historyLoaded(peer->migrateTo()->id)) { @@ -1782,54 +1629,73 @@ MsgId History::inboxRead(MsgId upTo) { _firstUnreadView = nullptr; Auth().notifications().clearFromHistory(this); - - return upTo; } -MsgId History::inboxRead(HistoryItem *wasRead) { - return inboxRead(wasRead ? wasRead->id : 0); +void History::inboxRead(not_null wasRead) { + if (IsServerMsgId(wasRead->id)) { + inboxRead(wasRead->id); + } } -MsgId History::outboxRead(int32 upTo) { - if (upTo < 0) return upTo; - if (!upTo) upTo = msgIdForRead(); - accumulate_max(outboxReadBefore, upTo + 1); - - return upTo; +void History::outboxRead(MsgId upTo) { + setOutboxReadTill(upTo); + if (const auto last = lastMessage()) { + if (last->out() && IsServerMsgId(last->id) && last->id <= upTo) { + if (const auto main = App::main()) { + main->repaintDialogRow(this, last->id); + } + } + } + updateChatListEntry(); } -MsgId History::outboxRead(HistoryItem *wasRead) { - return outboxRead(wasRead ? wasRead->id : 0); +void History::outboxRead(not_null wasRead) { + if (IsServerMsgId(wasRead->id)) { + outboxRead(wasRead->id); + } +} + +MsgId History::loadAroundId() const { + if (_unreadCount && *_unreadCount > 0 && _inboxReadBefore) { + return *_inboxReadBefore; + } + return MsgId(0); } HistoryItem *History::lastAvailableMessage() const { return isEmpty() ? nullptr : blocks.back()->messages.back()->data().get(); } +int History::unreadCount() const { + return _unreadCount ? *_unreadCount : 0; +} + void History::setUnreadCount(int newUnreadCount) { - if (_unreadCount != newUnreadCount) { + if (!_unreadCount || *_unreadCount != newUnreadCount) { + const auto unreadCountDelta = _unreadCount | [&](int count) { + return newUnreadCount - count; + }; if (newUnreadCount == 1) { if (loadedAtBottom()) { _firstUnreadView = !isEmpty() ? blocks.back()->messages.back().get() : nullptr; } - inboxReadBefore = qMax(inboxReadBefore, msgIdForRead()); + if (const auto last = msgIdForRead()) { + setInboxReadTill(last - 1); + } } else if (!newUnreadCount) { _firstUnreadView = nullptr; - inboxReadBefore = qMax(inboxReadBefore, msgIdForRead() + 1); + if (const auto last = msgIdForRead()) { + setInboxReadTill(last); + } } else { if (!_firstUnreadView && !_unreadBarView && loadedAtBottom()) { calculateFirstUnreadMessage(); } } - if (inChatList(Dialogs::Mode::All)) { - App::histories().unreadIncrement(newUnreadCount - _unreadCount, mute()); - if (!mute() || Global::IncludeMuted()) { - Notify::unreadCounterUpdated(); - } - } _unreadCount = newUnreadCount; + if (_unreadBarView) { const auto count = chatListUnreadCount(); if (count > 0) { @@ -1838,20 +1704,51 @@ void History::setUnreadCount(int newUnreadCount) { _unreadBarView->setUnreadBarFreezed(); } } + + const auto feed = peer->isChannel() + ? peer->asChannel()->feed() + : nullptr; + if (feed) { + const auto mutedCountDelta = (mute() && unreadCountDelta) + ? *unreadCountDelta + : 0; + feed->unreadCountChanged(unreadCountDelta, mutedCountDelta); + } + if (inChatList(Dialogs::Mode::All)) { + App::histories().unreadIncrement( + unreadCountDelta ? *unreadCountDelta : newUnreadCount, + mute()); + if (!mute() || Global::IncludeMuted()) { + Notify::unreadCounterUpdated(); + } + } if (const auto main = App::main()) { main->unreadCountChanged(this); } } } +bool History::mute() const { + return _mute; +} + bool History::changeMute(bool newMute) { if (_mute == newMute) { return false; } _mute = newMute; + + const auto feed = peer->isChannel() + ? peer->asChannel()->feed() + : nullptr; + if (feed && _unreadCount && *_unreadCount) { + const auto unreadCountDelta = 0; + const auto mutedCountDelta = _mute ? *_unreadCount : -*_unreadCount; + feed->unreadCountChanged(unreadCountDelta, mutedCountDelta); + } if (inChatList(Dialogs::Mode::All)) { - if (_unreadCount) { - App::histories().unreadMuteChanged(_unreadCount, newMute); + if (_unreadCount && *_unreadCount) { + App::histories().unreadMuteChanged(*_unreadCount, _mute); Notify::unreadCounterUpdated(); } Notify::historyMuteUpdated(this); @@ -2082,7 +1979,7 @@ bool History::chatListMutedBadge() const { } HistoryItem *History::chatsListItem() const { - return lastMsg; + return lastMessage(); } const QString &History::chatsListName() const { @@ -2149,7 +2046,8 @@ bool History::loadedAtTop() const { } bool History::isReadyFor(MsgId msgId) { - if (msgId < 0 && -msgId < ServerMaxMsgId && peer->migrateFrom()) { // old group history + if (msgId < 0 && -msgId < ServerMaxMsgId && peer->migrateFrom()) { + // Old group history. return App::history(peer->migrateFrom()->id)->isReadyFor(-msgId); } @@ -2164,9 +2062,10 @@ bool History::isReadyFor(MsgId msgId) { } } } - if (unreadCount()) { + if (unreadCount() && _inboxReadBefore) { if (!isEmpty()) { - return (loadedAtTop() || minMsgId() <= inboxReadBefore) && (loadedAtBottom() || maxMsgId() >= inboxReadBefore); + return (loadedAtTop() || minMsgId() <= *_inboxReadBefore) + && (loadedAtBottom() || maxMsgId() >= *_inboxReadBefore); } return false; } @@ -2178,10 +2077,10 @@ bool History::isReadyFor(MsgId msgId) { void History::getReadyFor(MsgId msgId) { if (msgId < 0 && -msgId < ServerMaxMsgId && peer->migrateFrom()) { - History *h = App::history(peer->migrateFrom()->id); - h->getReadyFor(-msgId); - if (h->isEmpty()) { - clear(true); + const auto migrated = App::history(peer->migrateFrom()->id); + migrated->getReadyFor(-msgId); + if (migrated->isEmpty()) { + unloadBlocks(); } return; } @@ -2189,7 +2088,7 @@ void History::getReadyFor(MsgId msgId) { if (const auto migratePeer = peer->migrateFrom()) { if (const auto migrated = App::historyLoaded(migratePeer)) { if (migrated->unreadCount()) { - clear(true); + unloadBlocks(); migrated->getReadyFor(msgId); return; } @@ -2197,7 +2096,7 @@ void History::getReadyFor(MsgId msgId) { } } if (!isReadyFor(msgId)) { - clear(true); + unloadBlocks(); if (msgId == ShowAtTheEndMsgId) { newLoaded = true; @@ -2215,30 +2114,37 @@ uint32 _dialogsPosToTopShift = 0x80000000UL; } // namespace -void History::setLastMessage(HistoryItem *msg) { - if (msg) { - if (!lastMsg) { +void History::setLastMessage(HistoryItem *item) { + if (item) { + if (_lastMessage && !*_lastMessage) { Local::removeSavedPeer(peer); } - lastMsg = msg; + _lastMessage = item; if (const auto feed = peer->feed()) { - feed->updateLastMessage(msg); + feed->updateLastMessage(item); } - setChatsListDate(msg->date); - } else if (lastMsg) { - lastMsg = nullptr; + setChatsListDate(item->date); + } else if (!_lastMessage || *_lastMessage) { + _lastMessage = nullptr; updateChatListEntry(); } } +HistoryItem *History::lastMessage() const { + return _lastMessage ? (*_lastMessage) : nullptr; +} + +bool History::lastMessageKnown() const { + return !!_lastMessage; +} + void History::updateChatListExistence() { Entry::updateChatListExistence(); - if (!lastMsg - && (!loadedAtBottom() || !loadedAtTop())) { + if (!lastMessageKnown() || !_unreadCount) { if (const auto channel = peer->asChannel()) { if (!channel->feed()) { - // After ungrouping from a feed we need to load lastMsg. - App::main()->checkPeerHistory(peer); + // After ungrouping from a feed we need to load dialog. + Auth().api().requestDialogEntry(this); } } } @@ -2259,8 +2165,91 @@ bool History::shouldBeInChatList() const { return true; } -void History::fixLastMessage(bool wasAtBottom) { - setLastMessage(wasAtBottom ? lastAvailableMessage() : nullptr); +void History::unknownMessageDeleted(MsgId messageId) { + if (_unreadCount && *_unreadCount > 0 && _inboxReadBefore) { + if (messageId >= *_inboxReadBefore) { + setUnreadCount(*_unreadCount - 1); + } + } +} + +bool History::isServerSideUnread(not_null item) const { + Expects(IsServerMsgId(item->id)); + + return item->out() + ? (!_outboxReadBefore || (item->id >= *_outboxReadBefore)) + : (!_inboxReadBefore || (item->id >= *_inboxReadBefore)); +} + +void History::applyDialog(const MTPDdialog &data) { + applyDialogFields( + data.vunread_count.v, + data.vread_inbox_max_id.v, + data.vread_outbox_max_id.v); + applyDialogTopMessage(data.vtop_message.v); + setUnreadMentionsCount(data.vunread_mentions_count.v); + if (const auto channel = peer->asChannel()) { + if (data.has_pts()) { + channel->ptsReceived(data.vpts.v); + } + if (!channel->amCreator()) { + const auto topMessageId = FullMsgId( + peerToChannel(channel->id), + data.vtop_message.v); + if (const auto item = App::histItemById(topMessageId)) { + if (item->date <= date(channel->date)) { + Auth().api().requestSelfParticipant(channel); + } + } + } + } + App::main()->applyNotifySetting( + MTP_notifyPeer(data.vpeer), + data.vnotify_settings, + this); + if (data.has_draft() && data.vdraft.type() == mtpc_draftMessage) { + Data::applyPeerCloudDraft(peer->id, data.vdraft.c_draftMessage()); + } +} + +void History::applyDialogFields( + int unreadCount, + MsgId maxInboxRead, + MsgId maxOutboxRead) { + setUnreadCount(unreadCount); + setInboxReadTill(maxInboxRead); + setOutboxReadTill(maxOutboxRead); +} + +void History::applyDialogTopMessage(MsgId topMessageId) { + if (topMessageId) { + const auto itemId = FullMsgId( + channelId(), + topMessageId); + if (const auto item = App::histItemById(itemId)) { + setLastMessage(item); + } else { + setLastMessage(nullptr); + } + } else { + setLastMessage(nullptr); + } +} + +void History::setInboxReadTill(MsgId upTo) { + if (_inboxReadBefore) { + accumulate_max(*_inboxReadBefore, upTo + 1); + } else { + _inboxReadBefore = upTo + 1; + } +} + +void History::setOutboxReadTill(MsgId upTo) { + if (_outboxReadBefore) { + accumulate_max(*_outboxReadBefore, upTo + 1); + } else { + _outboxReadBefore = upTo + 1; + } } MsgId History::minMsgId() const { @@ -2288,9 +2277,13 @@ MsgId History::maxMsgId() const { } MsgId History::msgIdForRead() const { - MsgId result = (lastMsg && lastMsg->id > 0) ? lastMsg->id : 0; - if (loadedAtBottom()) result = qMax(result, maxMsgId()); - return result; + const auto last = lastMessage(); + const auto result = (last && IsServerMsgId(last->id)) + ? last->id + : MsgId(0); + return loadedAtBottom() + ? std::max(result, maxMsgId()) + : result; } void History::resizeToWidth(int newWidth) { @@ -2310,12 +2303,16 @@ void History::resizeToWidth(int newWidth) { _height = y; } -ChannelHistory *History::asChannelHistory() { - return isChannel() ? static_cast(this) : nullptr; +ChannelId History::channelId() const { + return peerToChannel(peer->id); } -const ChannelHistory *History::asChannelHistory() const { - return isChannel() ? static_cast(this) : nullptr; +bool History::isChannel() const { + return peerIsChannel(peer->id); +} + +bool History::isMegagroup() const { + return peer->isMegagroup(); } not_null History::migrateToOrMe() const { @@ -2333,6 +2330,156 @@ History *History::migrateFrom() const { return nullptr; } +MsgRange History::rangeForDifferenceRequest() const { + auto fromId = MsgId(0); + auto toId = MsgId(0); + for (auto blockIndex = 0, blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) { + const auto &block = blocks[blockIndex]; + for (auto itemIndex = 0, itemsCount = int(block->messages.size()); itemIndex < itemsCount; ++itemIndex) { + const auto id = block->messages[itemIndex]->data()->id; + if (id > 0) { + fromId = id; + break; + } + } + if (fromId) break; + } + if (fromId) { + for (auto blockIndex = blocks.size(); blockIndex > 0;) { + const auto &block = blocks[--blockIndex]; + for (auto itemIndex = block->messages.size(); itemIndex > 0;) { + const auto id = block->messages[--itemIndex]->data()->id; + if (id > 0) { + toId = id; + break; + } + } + if (toId) break; + } + return { fromId, toId + 1 }; + } + return MsgRange(); +} + +HistoryService *History::insertJoinedMessage(bool unread) { + if (!isChannel() + || _joinedMessage + || !peer->asChannel()->amIn() + || (peer->isMegagroup() + && peer->asChannel()->mgInfo->joinedMessageFound)) { + return _joinedMessage; + } + + const auto inviter = (peer->asChannel()->inviter > 0) + ? App::userLoaded(peer->asChannel()->inviter) + : nullptr; + if (!inviter) { + return nullptr; + } + + MTPDmessage::Flags flags = 0; + if (inviter->id == Auth().userPeerId()) { + unread = false; + //} else if (unread) { + // flags |= MTPDmessage::Flag::f_unread; + } + + auto inviteDate = peer->asChannel()->inviteDate; + if (isEmpty()) { + _joinedMessage = GenerateJoinedMessage( + this, + inviteDate, + inviter, + flags); + addNewItem(_joinedMessage, unread); + return _joinedMessage; + } + + for (auto blockIndex = blocks.size(); blockIndex > 0;) { + const auto &block = blocks[--blockIndex]; + for (auto itemIndex = block->messages.size(); itemIndex > 0;) { + const auto item = block->messages[--itemIndex]->data(); + + // Due to a server bug sometimes inviteDate is less (before) than the + // first message in the megagroup (message about migration), let us + // ignore that and think, that the inviteDate is always greater-or-equal. + if (item->isGroupMigrate() + && peer->isMegagroup() + && peer->migrateFrom()) { + peer->asChannel()->mgInfo->joinedMessageFound = true; + return nullptr; + } + if (item->date <= inviteDate) { + ++itemIndex; + _joinedMessage = GenerateJoinedMessage( + this, + inviteDate, + inviter, + flags); + addNewInTheMiddle(_joinedMessage, blockIndex, itemIndex); + const auto lastDate = chatsListDate(); + if (lastDate.isNull() || inviteDate >= lastDate) { + setLastMessage(_joinedMessage); + if (unread) { + newItemAdded(_joinedMessage); + } + } + return _joinedMessage; + } + } + } + + startBuildingFrontBlock(); + _joinedMessage = GenerateJoinedMessage( + this, + inviteDate, + inviter, + flags); + addItemToBlock(_joinedMessage); + finishBuildingFrontBlock(); + + return _joinedMessage; +} + +void History::checkJoinedMessage(bool createUnread) { + if (!isChannel() || _joinedMessage || peer->asChannel()->inviter <= 0) { + return; + } + if (isEmpty()) { + if (loadedAtTop() && loadedAtBottom()) { + if (insertJoinedMessage(createUnread)) { + if (_joinedMessage->mainView()) { + setLastMessage(_joinedMessage); + } + } + return; + } + } + + QDateTime inviteDate = peer->asChannel()->inviteDate; + QDateTime firstDate, lastDate; + if (!blocks.empty()) { + firstDate = blocks.front()->messages.front()->data()->date; + lastDate = blocks.back()->messages.back()->data()->date; + } + if (!firstDate.isNull() + && !lastDate.isNull() + && (firstDate <= inviteDate || loadedAtTop()) + && (lastDate > inviteDate || loadedAtBottom())) { + const auto willBeLastMsg = (inviteDate >= lastDate); + if (insertJoinedMessage(createUnread && willBeLastMsg) + && willBeLastMsg) { + if (_joinedMessage->mainView()) { + setLastMessage(_joinedMessage); + } + } + } +} + +bool History::isEmpty() const { + return blocks.empty(); +} + bool History::isDisplayedEmpty() const { return isEmpty() || ((blocks.size() == 1) && blocks.front()->messages.size() == 1 @@ -2353,22 +2500,40 @@ bool History::hasOrphanMediaGroupPart() const { bool History::removeOrphanMediaGroupPart() { if (hasOrphanMediaGroupPart()) { - clear(true); + unloadBlocks(); return true; } return false; } -void History::clear(bool leaveItems) { - if (_unreadBarView) { - _unreadBarView = nullptr; - } - if (_firstUnreadView) { - _firstUnreadView = nullptr; - } - if (lastSentMsg) { - lastSentMsg = nullptr; +QVector History::collectMessagesFromUserToDelete( + not_null user) const { + auto result = QVector(); + for (const auto &block : blocks) { + for (const auto &message : block->messages) { + const auto item = message->data(); + if (item->from() == user && item->canDelete()) { + result.push_back(item->id); + } + } } + return result; +} + +void History::clear() { + clearBlocks(false); +} + +void History::unloadBlocks() { + clearBlocks(true); +} + +void History::clearBlocks(bool leaveItems) { + _unreadBarView = nullptr; + _firstUnreadView = nullptr; + lastSentMsg = nullptr; + _joinedMessage = nullptr; + if (scrollTopItem) { forgetScrollState(); } @@ -2387,7 +2552,7 @@ void History::clear(bool leaveItems) { } Auth().data().notifyHistoryCleared(this); } - clearBlocks(leaveItems); + blocks.clear(); if (leaveItems) { lastKeyboardInited = false; } else { @@ -2405,21 +2570,17 @@ void History::clear(bool leaveItems) { newLoaded = oldLoaded = false; forgetScrollState(); - - if (peer->isChat()) { - peer->asChat()->lastAuthors.clear(); - peer->asChat()->markupSenders.clear(); - } else if (isChannel()) { - asChannelHistory()->cleared(leaveItems); - if (isMegagroup()) { - peer->asChannel()->mgInfo->markupSenders.clear(); - } + if (const auto chat = peer->asChat()) { + chat->lastAuthors.clear(); + chat->markupSenders.clear(); + } else if (const auto channel = peer->asMegagroup()) { + channel->mgInfo->markupSenders.clear(); } } void History::clearUpTill(MsgId availableMinId) { auto minId = minMsgId(); - if (!minId || minId >= availableMinId) { + if (!minId || minId > availableMinId) { return; } do { @@ -2444,8 +2605,8 @@ void History::clearUpTill(MsgId availableMinId) { item->destroy(); } while (!isEmpty()); - if (!lastMsg) { - App::main()->checkPeerHistory(peer); + if (!lastMessageKnown()) { + Auth().api().requestDialogEntry(this); } Auth().data().sendHistoryChangeNotifications(); } @@ -2459,19 +2620,6 @@ void History::applyGroupAdminChanges( } } -void History::clearBlocks(bool leaveItems) { - const auto cleared = base::take(blocks); - for (const auto &block : cleared) { - if (leaveItems) { - block->clear(true); - } - } -} - -void History::clearOnDestroy() { - clearBlocks(false); -} - void History::changedInChatListHook(Dialogs::Mode list, bool added) { if (list == Dialogs::Mode::All && unreadCount()) { const auto delta = added ? unreadCount() : -unreadCount(); @@ -2505,9 +2653,7 @@ void History::removeBlock(not_null block) { } } -History::~History() { - clearOnDestroy(); -} +History::~History() = default; HistoryBlock::HistoryBlock(not_null history) : _history(history) { @@ -2527,17 +2673,6 @@ int HistoryBlock::resizeGetHeight(int newWidth, bool resizeAllItems) { return _height; } -void HistoryBlock::clear(bool leaveItems) { - const auto list = base::take(messages); - - if (leaveItems) { - for (const auto &message : list) { - message->data()->clearMainView(); - } - } - // #TODO feeds delete all items in history -} - void HistoryBlock::remove(not_null view) { Expects(view->block() == this); @@ -2584,6 +2719,4 @@ void HistoryBlock::refreshView(not_null view) { } } -HistoryBlock::~HistoryBlock() { - clear(); -} +HistoryBlock::~HistoryBlock() = default; diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index b5969e22a..5b9c8b773 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/flags.h" class History; -class ChannelHistory; class HistoryBlock; class HistoryItem; class HistoryMessage; @@ -50,12 +49,7 @@ enum NewMessageType : char { class Histories { public: - using Map = QHash; - Map map; - - Histories() : _a_typings(animation(this, &Histories::step_typings)) { - _selfDestructTimer.setCallback([this] { checkSelfDestructItems(); }); - } + Histories(); void registerSendAction( not_null history, @@ -64,20 +58,16 @@ public: TimeId when); void step_typings(TimeMs ms, bool timer); - History *find(const PeerId &peerId); - not_null findOrInsert(const PeerId &peerId); - not_null findOrInsert( - const PeerId &peerId, - int unreadCount, - MsgId maxInboxRead, - MsgId maxOutboxRead); + History *find(PeerId peerId) const; + not_null findOrInsert(PeerId peerId); void clear(); void remove(const PeerId &peer); HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type); - typedef QMap TypingHistories; // when typing in this history started + // When typing in this history started. + typedef QMap TypingHistories; TypingHistories typing; BasicAnimation _a_typings; @@ -114,6 +104,8 @@ public: private: void checkSelfDestructItems(); + std::unordered_map> _map; + int _unreadFull = 0; int _unreadMuted = 0; base::Observable _sendActionAnimationUpdated; @@ -136,29 +128,24 @@ public: History(const History &) = delete; History &operator=(const History &) = delete; - ChannelId channelId() const { - return peerToChannel(peer->id); - } - bool isChannel() const { - return peerIsChannel(peer->id); - } - bool isMegagroup() const { - return peer->isMegagroup(); - } - ChannelHistory *asChannelHistory(); - const ChannelHistory *asChannelHistory() const; - + ChannelId channelId() const; + bool isChannel() const; + bool isMegagroup() const; not_null migrateToOrMe() const; History *migrateFrom() const; + MsgRange rangeForDifferenceRequest() const; + HistoryService *insertJoinedMessage(bool unread); + void checkJoinedMessage(bool createUnread = false); - bool isEmpty() const { - return blocks.empty(); - } + bool isEmpty() const; bool isDisplayedEmpty() const; bool hasOrphanMediaGroupPart() const; bool removeOrphanMediaGroupPart(); + QVector collectMessagesFromUserToDelete( + not_null user) const; - void clear(bool leaveItems = false); + void clear(); + void unloadBlocks(); void clearUpTill(MsgId availableMinId); void applyGroupAdminChanges(const base::flat_map &changes); @@ -184,18 +171,17 @@ public: void newItemAdded(not_null item); int countUnread(MsgId upTo); - MsgId inboxRead(MsgId upTo); - MsgId inboxRead(HistoryItem *wasRead); - MsgId outboxRead(MsgId upTo); - MsgId outboxRead(HistoryItem *wasRead); + MsgId readInbox(); + void inboxRead(MsgId upTo); + void inboxRead(not_null wasRead); + void outboxRead(MsgId upTo); + void outboxRead(not_null wasRead); + bool isServerSideUnread(not_null item) const; + MsgId loadAroundId() const; - int unreadCount() const { - return _unreadCount; - } + int unreadCount() const; void setUnreadCount(int newUnreadCount); - bool mute() const { - return _mute; - } + bool mute() const; bool changeMute(bool newMute); void addUnreadBar(); void destroyUnreadBar(); @@ -212,46 +198,34 @@ public: bool isReadyFor(MsgId msgId); // has messages for showing history at msgId void getReadyFor(MsgId msgId); - void setLastMessage(HistoryItem *msg); - void fixLastMessage(bool wasAtBottom); + HistoryItem *lastMessage() const; + bool lastMessageKnown() const; + void unknownMessageDeleted(MsgId messageId); + void applyDialogTopMessage(MsgId topMessageId); + void applyDialog(const MTPDdialog &data); + void applyDialogFields( + int unreadCount, + MsgId maxInboxRead, + MsgId maxOutboxRead); MsgId minMsgId() const; MsgId maxMsgId() const; MsgId msgIdForRead() const; void resizeToWidth(int newWidth); + int height() const; - void removeNotification(HistoryItem *item) { - if (!notifies.isEmpty()) { - for (auto i = notifies.begin(), e = notifies.end(); i != e; ++i) { - if ((*i) == item) { - notifies.erase(i); - break; - } - } - } - } - HistoryItem *currentNotification() { - return notifies.isEmpty() ? 0 : notifies.front(); - } - bool hasNotification() const { - return !notifies.isEmpty(); - } - void skipNotification() { - if (!notifies.isEmpty()) { - notifies.pop_front(); - } - } - void popNotification(HistoryItem *item) { - if (!notifies.isEmpty() && notifies.back() == item) notifies.pop_back(); - } + void itemRemoved(not_null item); + void itemVanished(not_null item); - bool hasPendingResizedItems() const { - return _flags & Flag::f_has_pending_resized_items; - } + HistoryItem *currentNotification(); + bool hasNotification() const; + void skipNotification(); + void popNotification(HistoryItem *item); + + bool hasPendingResizedItems() const; void setHasPendingResizedItems(); - void paintDialog(Painter &p, int32 w, bool sel) const; bool mySendActionUpdated(SendAction::Type type, bool doing); bool paintSendAction(Painter &p, int x, int y, int availableWidth, int outerWidth, style::color color, TimeMs ms); @@ -345,15 +319,9 @@ public: // Still public data. std::deque> blocks; - int height() const; - int32 msgCount = 0; - MsgId inboxReadBefore = 1; - MsgId outboxReadBefore = 1; - not_null peer; bool oldLoaded = false; bool newLoaded = true; - HistoryItem *lastMsg = nullptr; HistoryItem *lastSentMsg = nullptr; typedef QList NotifyQueue; @@ -379,7 +347,17 @@ public: Text cloudDraftTextCache; -protected: +private: + friend class HistoryBlock; + + enum class Flag { + f_has_pending_resized_items = (1 << 0), + }; + using Flags = base::flags; + friend inline constexpr auto is_flag_type(Flag) { + return true; + }; + // when this item is destroyed scrollTopItem just points to the next one // and scrollTopOffset remains the same // if we are at the bottom of the window scrollTopItem == nullptr and @@ -389,7 +367,6 @@ protected: // helper method for countScrollState(int top) void countScrollTopItem(int top); - void clearOnDestroy(); HistoryItem *addNewToLastBlock(const MTPMessage &msg, NewMessageType type); // this method just removes a block from the blocks list @@ -429,25 +406,18 @@ protected: return _buildingFrontBlock != nullptr; } -private: - friend class HistoryBlock; - - enum class Flag { - f_has_pending_resized_items = (1 << 0), - }; - using Flags = base::flags; - friend inline constexpr auto is_flag_type(Flag) { - return true; - }; - void mainViewRemoved( not_null block, not_null view); + void removeNotification(not_null item); QDateTime adjustChatListDate() const override; void changedInChatListHook(Dialogs::Mode list, bool added) override; void changedChatListPinHook() override; + void setInboxReadTill(MsgId upTo); + void setOutboxReadTill(MsgId upTo); + void applyMessageChanges( not_null item, const MTPMessage &original); @@ -455,14 +425,13 @@ private: not_null item, const MTPDmessageService &data); - // After adding a new history slice check the lastMsg and newLoaded. - void checkLastMsg(); + // After adding a new history slice check the lastMessage and newLoaded. + void checkLastMessage(); + void setLastMessage(HistoryItem *item); // Add all items to the unread mentions if we were not loaded at bottom and now are. void checkAddAllToUnreadMentions(); - template - void addToSharedMedia(std::vector (&medias)[kSharedMediaTypeCount], bool force); void addToSharedMedia(const std::vector> &items); void addEdgesToSharedMedia(); @@ -480,14 +449,18 @@ private: Flags _flags = 0; bool _mute = false; - int _unreadCount = 0; int _width = 0; int _height = 0; Element *_unreadBarView = nullptr; Element *_firstUnreadView = nullptr; + HistoryService *_joinedMessage = nullptr; + base::optional _inboxReadBefore; + base::optional _outboxReadBefore; + base::optional _unreadCount; base::optional _unreadMentionsCount; base::flat_set _unreadMentions; + base::optional _lastMessage; // A pointer to the block that is currently being built. // We hold this pointer so we can destroy it while building @@ -517,37 +490,6 @@ private: }; -class ChannelHistory : public History { -public: - using History::History; - - void messageDetached(not_null message); - - void getRangeDifference(); - void getRangeDifferenceNext(int32 pts); - - HistoryService *insertJoinedMessage(bool unread); - void checkJoinedMessage(bool createUnread = false); - const QDateTime &maxReadMessageDate(); - - ~ChannelHistory(); - -private: - friend class History; - - void checkMaxReadMessageDate(); - void cleared(bool leaveItems); - - QDateTime _maxReadMessageDate; - - HistoryService *_joinedMessage = nullptr; - - MsgId _rangeDifferenceFromId, _rangeDifferenceToId; - int32 _rangeDifferencePts; - mtpRequestId _rangeDifferenceRequestId; - -}; - class HistoryBlock { public: using Element = HistoryView::Element; @@ -559,7 +501,6 @@ public: std::vector> messages; - void clear(bool leaveItems = false); void remove(not_null view); void refreshView(not_null view); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index db1f2308b..904221467 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -178,11 +178,15 @@ HistoryInner::HistoryInner( }, lifetime()); } -void HistoryInner::messagesReceived(PeerData *peer, const QVector &messages) { +void HistoryInner::messagesReceived( + PeerData *peer, + const QVector &messages) { if (_history && _history->peer == peer) { _history->addOlderSlice(messages); } else if (_migrated && _migrated->peer == peer) { - bool newLoaded = (_migrated && _migrated->isEmpty() && !_history->isEmpty()); + const auto newLoaded = _migrated + && _migrated->isEmpty() + && !_history->isEmpty(); _migrated->addOlderSlice(messages); if (newLoaded) { _migrated->addNewerSlice(QVector()); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 02a731e53..3a5e946d8 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -148,19 +148,7 @@ void HistoryItem::invalidateChatsListEntry() { void HistoryItem::finishEditionToEmpty() { finishEdition(-1); - - _history->removeNotification(this); - if (auto channel = history()->peer->asChannel()) { - if (channel->pinnedMessageId() == id) { - channel->clearPinnedMessage(); - } - } - if (history()->lastKeyboardId == id) { - history()->clearLastKeyboard(); - } - if ((!out() || isPost()) && unread() && history()->unreadCount() > 0) { - history()->setUnreadCount(history()->unreadCount() - 1); - } + _history->itemVanished(this); } bool HistoryItem::isMediaUnread() const { @@ -244,7 +232,7 @@ void HistoryItem::destroy() { if (isLogEntry()) { Assert(!mainView()); } else { - // All this must be done for all items manually in History::clear(false)! + // All this must be done for all items manually in History::clear()! eraseFromUnreadMentions(); if (IsServerMsgId(id)) { if (const auto types = sharedMediaTypes()) { @@ -256,32 +244,7 @@ void HistoryItem::destroy() { } else { Auth().api().cancelLocalItem(this); } - - const auto wasAtBottom = history->loadedAtBottom(); - history->removeNotification(this); - - removeMainView(); - - if (history->lastMsg == this) { - history->fixLastMessage(wasAtBottom); - } - if (history->lastKeyboardId == id) { - history->clearLastKeyboard(); - } - if ((!out() || isPost()) && unread() && history->unreadCount() > 0) { - history->setUnreadCount(history->unreadCount() - 1); - } - if (const auto channel = history->peer->asChannel()) { - if (channel->pinnedMessageId() == id) { - channel->clearPinnedMessage(); - } - if (const auto feed = channel->feed()) { - // Must be after histroy->lastMsg is cleared. - // Otherwise feed last message will be this value again. - feed->messageRemoved(this); - // #TODO feeds unread - } - } + _history->itemRemoved(this); } delete this; } @@ -295,9 +258,6 @@ void HistoryItem::refreshMainView() { void HistoryItem::removeMainView() { if (const auto view = mainView()) { - if (const auto channelHistory = _history->asChannelHistory()) { - channelHistory->messageDetached(this); - } Auth().data().notifyHistoryChangeDelayed(_history); view->removeFromBlock(); } @@ -551,11 +511,11 @@ bool HistoryItem::unread() const { return false; } - if (id > 0) { - if (id < history()->outboxReadBefore) { + if (IsServerMsgId(id)) { + if (!history()->isServerSideUnread(this)) { return false; } - if (auto user = history()->peer->asUser()) { + if (const auto user = history()->peer->asUser()) { if (user->botInfo) { return false; } @@ -568,8 +528,8 @@ bool HistoryItem::unread() const { return true; } - if (id > 0) { - if (id < history()->inboxReadBefore) { + if (IsServerMsgId(id)) { + if (!history()->isServerSideUnread(this)) { return false; } return true; @@ -587,24 +547,6 @@ bool HistoryItem::isEmpty() const { && !Has(); } -HistoryItem *HistoryItem::previousItem() const { - if (const auto view = mainView()) { - if (const auto previous = view->previousInBlocks()) { - return previous->data(); - } - } - return nullptr; -} - -HistoryItem *HistoryItem::nextItem() const { - if (const auto view = mainView()) { - if (const auto next = view->nextInBlocks()) { - return next->data(); - } - } - return nullptr; -} - QString HistoryItem::notificationText() const { auto getText = [this]() { if (_media) { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 3b3cc0e2e..c1e8af56b 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -254,9 +254,6 @@ public: MessageGroupId groupId() const; - HistoryItem *previousItem() const; - HistoryItem *nextItem() const; - virtual std::unique_ptr createView( not_null delegate) = 0; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index b02b39ff4..05b4cbf01 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -2256,9 +2256,7 @@ void HistoryWidget::historyToDown(History *history) { void HistoryWidget::unreadCountChanged(not_null history) { if (history == _history || history == _migrated) { updateHistoryDownVisibility(); - _historyDown->setUnreadCount( - _history->unreadCount() - + (_migrated ? _migrated->unreadCount() : 0)); + _historyDown->setUnreadCount(_history->chatListUnreadCount()); } } @@ -2402,9 +2400,9 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages if (_history->loadedAtBottom() && App::wnd()) App::wnd()->checkHistoryActivation(); } else if (_firstLoadRequest == requestId) { if (toMigrated) { - _history->clear(true); + _history->unloadBlocks(); } else if (_migrated) { - _migrated->clear(true); + _migrated->unloadBlocks(); } addMessagesToFront(peer, *histList); _firstLoadRequest = 0; @@ -2421,9 +2419,9 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages historyLoaded(); } else if (_delayedShowAtRequest == requestId) { if (toMigrated) { - _history->clear(true); + _history->unloadBlocks(); } else if (_migrated) { - _migrated->clear(true); + _migrated->unloadBlocks(); } _delayedShowAtRequest = 0; @@ -2506,15 +2504,15 @@ void HistoryWidget::firstLoadMessages() { auto offset = 0; auto loadCount = kMessagesPerPage; if (_showAtMsgId == ShowAtUnreadMsgId) { - if (_migrated && _migrated->unreadCount()) { + if (const auto around = _migrated ? _migrated->loadAroundId() : 0) { _history->getReadyFor(_showAtMsgId); from = _migrated->peer; offset = -loadCount / 2; - offsetId = _migrated->inboxReadBefore; - } else if (_history->unreadCount()) { + offsetId = around; + } else if (const auto around = _history->loadAroundId()) { _history->getReadyFor(_showAtMsgId); offset = -loadCount / 2; - offsetId = _history->inboxReadBefore; + offsetId = around; } else { _history->getReadyFor(ShowAtTheEndMsgId); } @@ -2651,13 +2649,13 @@ void HistoryWidget::delayedShowAt(MsgId showAtMsgId) { auto offset = 0; auto loadCount = kMessagesPerPage; if (_delayedShowAtMsgId == ShowAtUnreadMsgId) { - if (_migrated && _migrated->unreadCount()) { + if (const auto around = _migrated ? _migrated->loadAroundId() : 0) { from = _migrated->peer; offset = -loadCount / 2; - offsetId = _migrated->inboxReadBefore; - } else if (_history->unreadCount()) { + offsetId = around; + } else if (const auto around = _history->loadAroundId()) { offset = -loadCount / 2; - offsetId = _history->inboxReadBefore; + offsetId = around; } else { loadCount = kMessagesPerPageFirst; } @@ -3630,8 +3628,17 @@ bool HistoryWidget::inlineBotResolveFail(QString name, const RPCError &error) { } bool HistoryWidget::isBotStart() const { - if (!_peer || !_peer->isUser() || !_peer->asUser()->botInfo || !_canSendMessages) return false; - return !_peer->asUser()->botInfo->startToken.isEmpty() || (_history->isEmpty() && !_history->lastMsg); + const auto user = _peer ? _peer->asUser() : nullptr; + if (!user + || !user->botInfo + || !_canSendMessages) { + return false; + } else if (!user->botInfo->startToken.isEmpty()) { + return true; + } else if (_history->isEmpty() && !_history->lastMessage()) { + return true; + } + return false; } bool HistoryWidget::isBlocked() const { @@ -5126,14 +5133,7 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) { if (!(e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier))) { _scroll->keyPressEvent(e); } else if ((e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier)) == Qt::ControlModifier) { - if (_history && _history->lastMsg && !_editMsgId) { - if (const auto item = App::histItemById(_history->channelId(), _replyToId)) { - if (const auto next = item->nextItem()) { - Ui::showPeerHistory(_peer, next->id); - replyToMessage(next); - } - } - } + replyToNextMessage(); } } else if (e->key() == Qt::Key_Up) { if (!(e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier))) { @@ -5145,17 +5145,7 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) { } _scroll->keyPressEvent(e); } else if ((e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier)) == Qt::ControlModifier) { - if (_history && _history->lastMsg && !_editMsgId) { - if (const auto item = App::histItemById(_history->channelId(), _replyToId)) { - if (const auto previous = item->previousItem()) { - Ui::showPeerHistory(_peer, previous->id); - replyToMessage(previous); - } - } else if (const auto previous = _history->lastMsg) { - Ui::showPeerHistory(_peer, previous->id); - replyToMessage(previous); - } - } + replyToPreviousMessage(); } } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { onListEnterPressed(); @@ -5164,6 +5154,45 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) { } } +void HistoryWidget::replyToPreviousMessage() { + if (!_history || _editMsgId) { + return; + } + const auto fullId = FullMsgId( + _history->channelId(), + _replyToId); + if (const auto item = App::histItemById(fullId)) { + if (const auto view = item->mainView()) { + if (const auto previousView = view->previousInBlocks()) { + const auto previous = previousView->data(); + Ui::showPeerHistoryAtItem(previous); + replyToMessage(previous); + } + } + } else if (const auto previous = _history->lastMessage()) { + Ui::showPeerHistoryAtItem(previous); + replyToMessage(previous); + } +} + +void HistoryWidget::replyToNextMessage() { + if (!_history || _editMsgId) { + return; + } + const auto fullId = FullMsgId( + _history->channelId(), + _replyToId); + if (const auto item = App::histItemById(fullId)) { + if (const auto view = item->mainView()) { + if (const auto nextView = view->nextInBlocks()) { + const auto next = nextView->data(); + Ui::showPeerHistoryAtItem(next); + replyToMessage(next); + } + } + } +} + void HistoryWidget::onFieldTabbed() { if (!_fieldAutocomplete->isHidden()) { _fieldAutocomplete->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 1e9dcfe5d..3c62c7709 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -538,6 +538,8 @@ private: void applyInlineBotQuery(UserData *bot, const QString &query); void cancelReplyAfterMediaSend(bool lastKeyboardUsed); + void replyToPreviousMessage(); + void replyToNextMessage(); void hideSelectorControlsAnimated(); int countMembersDropdownHeightMax() const; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index f877e9bae..1a5e856a7 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -611,6 +611,9 @@ void Element::clickHandlerPressedChanged( } Element::~Element() { + if (_data->mainView() == this) { + _data->clearMainView(); + } Auth().data().unregisterItemView(this); } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index fb7a64005..9838d825b 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -887,21 +887,27 @@ void MainWidget::deleteLayer(FullMsgId itemId) { void MainWidget::cancelUploadLayer(not_null item) { const auto itemId = item->fullId(); Auth().uploader().pause(itemId); - Ui::show(Box(lang(lng_selected_cancel_sure_this), lang(lng_selected_upload_stop), lang(lng_continue), base::lambda_guarded(this, [=] { + const auto stopUpload = [=] { Ui::hideLayer(); if (const auto item = App::histItemById(itemId)) { const auto history = item->history(); - const auto wasLast = (history->lastMsg == item); item->destroy(); - if (wasLast && !history->lastMsg) { - checkPeerHistory(history->peer); + if (!history->lastMessageKnown()) { + Auth().api().requestDialogEntry(history); } Auth().data().sendHistoryChangeNotifications(); } Auth().uploader().unpause(); - }), base::lambda_guarded(this, [] { + }; + const auto continueUpload = [=] { Auth().uploader().unpause(); - }))); + }; + Ui::show(Box( + lang(lng_selected_cancel_sure_this), + lang(lng_selected_upload_stop), + lang(lng_continue), + stopUpload, + continueUpload)); } void MainWidget::deletePhotoLayer(PhotoData *photo) { @@ -1041,12 +1047,11 @@ void MainWidget::deleteConversation( if (const auto history = App::historyLoaded(peer->id)) { Auth().data().setPinnedDialog(history, false); removeDialog(history); - if (peer->isMegagroup() && peer->asChannel()->mgInfo->migrateFromPtr) { - if (auto migrated = App::historyLoaded(peer->asChannel()->mgInfo->migrateFromPtr->id)) { - if (migrated->lastMsg) { // return initial dialog - migrated->setLastMessage(migrated->lastMsg); - } else { - checkPeerHistory(migrated->peer); + if (const auto channel = peer->asMegagroup()) { + channel->addFlags(MTPDchannel::Flag::f_left); + if (const auto from = channel->mgInfo->migrateFromPtr) { + if (const auto migrated = App::historyLoaded(from)) { + migrated->updateChatListExistence(); } } } @@ -1059,7 +1064,12 @@ void MainWidget::deleteConversation( } if (deleteHistory) { DeleteHistoryRequest request = { peer, false }; - MTP::send(MTPmessages_DeleteHistory(MTP_flags(0), peer->input, MTP_int(0)), rpcDone(&MainWidget::deleteHistoryPart, request)); + MTP::send( + MTPmessages_DeleteHistory( + MTP_flags(0), + peer->input, + MTP_int(0)), + rpcDone(&MainWidget::deleteHistoryPart, request)); } } @@ -1068,47 +1078,6 @@ void MainWidget::deleteAndExit(ChatData *chat) { MTP::send(MTPmessages_DeleteChatUser(chat->inputChat, App::self()->inputUser), rpcDone(&MainWidget::deleteHistoryAfterLeave, peer), rpcFail(&MainWidget::leaveChatFailed, peer)); } -void MainWidget::deleteAllFromUser(ChannelData *channel, UserData *from) { - Assert(channel != nullptr && from != nullptr); - - QVector toDestroy; - if (auto history = App::historyLoaded(channel->id)) { - for (const auto &block : history->blocks) { - for (const auto &message : block->messages) { - const auto item = message->data(); - if (item->from() == from && item->canDelete()) { - toDestroy.push_back(item->id); - } - } - } - for_const (auto &msgId, toDestroy) { - if (auto item = App::histItemById(peerToChannel(channel->id), msgId)) { - item->destroy(); - } - } - } - MTP::send( - MTPchannels_DeleteUserHistory( - channel->inputChannel, - from->inputUser), - rpcDone(&MainWidget::deleteAllFromUserPart, { channel, from })); - Auth().data().sendHistoryChangeNotifications(); -} - -void MainWidget::deleteAllFromUserPart(DeleteAllFromUserParams params, const MTPmessages_AffectedHistory &result) { - auto &d = result.c_messages_affectedHistory(); - params.channel->ptsUpdateAndApply(d.vpts.v, d.vpts_count.v); - - auto offset = d.voffset.v; - if (offset > 0) { - MTP::send(MTPchannels_DeleteUserHistory(params.channel->inputChannel, params.from->inputUser), rpcDone(&MainWidget::deleteAllFromUserPart, params)); - } else if (auto h = App::historyLoaded(params.channel)) { - if (!h->lastMsg) { - checkPeerHistory(params.channel); - } - } -} - void MainWidget::addParticipants( not_null chatOrChannel, const std::vector> &users) { @@ -1194,99 +1163,6 @@ bool MainWidget::addParticipantsFail( return false; } -void MainWidget::checkPeerHistory(PeerData *peer) { - auto offsetId = 0; - auto offsetDate = 0; - auto addOffset = 0; - auto limit = 1; - auto maxId = 0; - auto minId = 0; - auto historyHash = 0; - MTP::send( - MTPmessages_GetHistory( - peer->input, - MTP_int(offsetId), - MTP_int(offsetDate), - MTP_int(addOffset), - MTP_int(limit), - MTP_int(maxId), - MTP_int(minId), - MTP_int(historyHash)), - rpcDone(&MainWidget::checkedHistory, peer)); -} - -void MainWidget::checkedHistory(PeerData *peer, const MTPmessages_Messages &result) { - const QVector *v = 0; - switch (result.type()) { - case mtpc_messages_messages: { - auto &d(result.c_messages_messages()); - App::feedUsers(d.vusers); - App::feedChats(d.vchats); - v = &d.vmessages.v; - } break; - - case mtpc_messages_messagesSlice: { - auto &d(result.c_messages_messagesSlice()); - App::feedUsers(d.vusers); - App::feedChats(d.vchats); - v = &d.vmessages.v; - } break; - - case mtpc_messages_channelMessages: { - auto &d(result.c_messages_channelMessages()); - if (peer && peer->isChannel()) { - peer->asChannel()->ptsReceived(d.vpts.v); - } else { - LOG(("API Error: received messages.channelMessages when no channel was passed! (MainWidget::checkedHistory)")); - } - App::feedUsers(d.vusers); - App::feedChats(d.vchats); - v = &d.vmessages.v; - } break; - - case mtpc_messages_messagesNotModified: { - LOG(("API Error: received messages.messagesNotModified! (MainWidget::checkedHistory)")); - } break; - } - - if (!v || v->isEmpty()) { - if (peer->isChat() && !peer->asChat()->haveLeft()) { - if (const auto history = App::historyLoaded(peer->id)) { - Local::addSavedPeer(peer, history->chatsListDate()); - } - } else if (const auto channel = peer->asChannel()) { - if (channel->inviter > 0 && channel->amIn()) { - if (const auto from = App::userLoaded(channel->inviter)) { - const auto history = App::history(peer->id); - history->clear(true); - history->addNewerSlice(QVector()); - history->asChannelHistory()->insertJoinedMessage(true); - } - } - } else if (const auto history = App::historyLoaded(peer->id)) { - deleteConversation(history->peer, false); - } - } else { - const auto history = App::history(peer->id); - if (!history->lastMsg) { - history->addNewMessage((*v)[0], NewMessageLast); - } - if (!history->chatsListDate().isNull() && history->loadedAtBottom()) { - if (const auto channel = peer->asChannel()) { - if (channel->inviter > 0 - && history->chatsListDate() <= channel->inviteDate - && channel->amIn()) { - if (const auto from = App::userLoaded(channel->inviter)) { - history->asChannelHistory()->insertJoinedMessage(true); - } - } - } - } - history->updateChatListExistence(); - } - Auth().data().sendHistoryChangeNotifications(); -} - bool MainWidget::sendMessageFail(const RPCError &error) { if (MTP::isDefaultHandledError(error)) return false; @@ -1562,9 +1438,9 @@ void MainWidget::messagesAffected( ptsUpdateAndApply(data.vpts.v, data.vpts_count.v); } - if (auto h = App::historyLoaded(peer ? peer->id : 0)) { - if (!h->lastMsg) { - checkPeerHistory(peer); + if (const auto history = App::historyLoaded(peer)) { + if (!history->lastMessageKnown()) { + Auth().api().requestDialogEntry(history); } } } @@ -3473,7 +3349,9 @@ void MainWidget::updSetState(int32 pts, int32 date, int32 qts, int32 seq) { } } -void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_ChannelDifference &diff) { +void MainWidget::gotChannelDifference( + ChannelData *channel, + const MTPupdates_ChannelDifference &diff) { _channelFailDifferenceTimeout.remove(channel); int32 timeout = 0; @@ -3491,28 +3369,28 @@ void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_Cha App::feedUsers(d.vusers); App::feedChats(d.vchats); - auto h = App::historyLoaded(channel->id); - if (h) { - h->setNotLoadedAtBottom(); + auto history = App::historyLoaded(channel->id); + if (history) { + history->setNotLoadedAtBottom(); } App::feedMsgs(d.vmessages, NewMessageLast); - if (h) { - if (auto item = App::histItemById(peerToChannel(channel->id), d.vtop_message.v)) { - h->setLastMessage(item); - } - if (d.vunread_count.v >= h->unreadCount()) { - h->setUnreadCount(d.vunread_count.v); - h->inboxReadBefore = d.vread_inbox_max_id.v + 1; - } - h->setUnreadMentionsCount(d.vunread_mentions_count.v); + if (history) { + history->applyDialogFields( + d.vunread_count.v, + d.vread_inbox_max_id.v, + d.vread_outbox_max_id.v); + history->applyDialogTopMessage(d.vtop_message.v); + history->setUnreadMentionsCount(d.vunread_mentions_count.v); if (_history->peer() == channel) { _history->updateHistoryDownVisibility(); _history->preloadHistoryIfNeeded(); } - h->asChannelHistory()->getRangeDifference(); + Auth().api().requestChannelRangeDifference(history); } - if (d.has_timeout()) timeout = d.vtimeout.v; + if (d.has_timeout()) { + timeout = d.vtimeout.v; + } isFinal = d.is_final(); channel->ptsInit(d.vpts.v); } break; @@ -3520,39 +3398,7 @@ void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_Cha case mtpc_updates_channelDifference: { auto &d = diff.c_updates_channelDifference(); - App::feedUsers(d.vusers); - App::feedChats(d.vchats); - - _handlingChannelDifference = true; - feedMessageIds(d.vother_updates); - - // feed messages and groups, copy from App::feedMsgs - auto h = App::history(channel->id); - auto &vmsgs = d.vnew_messages.v; - auto indices = base::flat_map(); - for (auto i = 0, l = vmsgs.size(); i != l; ++i) { - const auto &msg = vmsgs[i]; - if (msg.type() == mtpc_message) { - const auto &data = msg.c_message(); - if (App::checkEntitiesAndViewsUpdate(data)) { // new message, index my forwarded messages to links _overview, already in blocks - LOG(("Skipping message, because it is already in blocks!")); - continue; - } - } - const auto msgId = idFromMessage(msg); - indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i); - } - for (const auto [position, index] : indices) { - const auto &msg = vmsgs[index]; - if (channel->id != peerFromMessage(msg)) { - LOG(("API Error: message with invalid peer returned in channelDifference, channelId: %1, peer: %2").arg(peerToChannel(channel->id)).arg(peerFromMessage(msg))); - continue; // wtf - } - h->addNewMessage(msg, NewMessageUnread); - } - - feedUpdateVector(d.vother_updates, true); - _handlingChannelDifference = false; + feedChannelDifference(d); if (d.has_timeout()) timeout = d.vtimeout.v; isFinal = d.is_final(); @@ -3570,55 +3416,16 @@ void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_Cha } } -void MainWidget::gotRangeDifference( - ChannelData *channel, - const MTPupdates_ChannelDifference &diff) { - int32 nextRequestPts = 0; - bool isFinal = true; - switch (diff.type()) { - case mtpc_updates_channelDifferenceEmpty: { - auto &d = diff.c_updates_channelDifferenceEmpty(); - nextRequestPts = d.vpts.v; - isFinal = d.is_final(); - } break; +void MainWidget::feedChannelDifference( + const MTPDupdates_channelDifference &data) { + App::feedUsers(data.vusers); + App::feedChats(data.vchats); - case mtpc_updates_channelDifferenceTooLong: { - auto &d = diff.c_updates_channelDifferenceTooLong(); - - App::feedUsers(d.vusers); - App::feedChats(d.vchats); - - nextRequestPts = d.vpts.v; - isFinal = d.is_final(); - } break; - - case mtpc_updates_channelDifference: { - auto &d = diff.c_updates_channelDifference(); - - App::feedUsers(d.vusers); - App::feedChats(d.vchats); - - _handlingChannelDifference = true; - feedMessageIds(d.vother_updates); - App::feedMsgs(d.vnew_messages, NewMessageUnread); - feedUpdateVector(d.vother_updates, true); - _handlingChannelDifference = false; - - nextRequestPts = d.vpts.v; - isFinal = d.is_final(); - } break; - } - - if (!isFinal) { - if (const auto history = App::historyLoaded(channel->id)) { - MTP_LOG(0, ("getChannelDifference { " - "good - after not final channelDifference was received, " - "validating history part }%1" - ).arg(cTestMode() ? " TESTMODE" : "")); - history->asChannelHistory()->getRangeDifferenceNext( - nextRequestPts); - } - } + _handlingChannelDifference = true; + feedMessageIds(data.vother_updates); + App::feedMsgs(data.vnew_messages, NewMessageUnread); + feedUpdateVector(data.vother_updates, true); + _handlingChannelDifference = false; } bool MainWidget::failChannelDifference(ChannelData *channel, const RPCError &error) { @@ -3985,12 +3792,12 @@ void MainWidget::onSelfParticipantUpdated(ChannelData *channel) { history = App::history(channel); } if (history->isEmpty()) { - checkPeerHistory(channel); + Auth().api().requestDialogEntry(history); } else { - history->asChannelHistory()->checkJoinedMessage(true); + history->checkJoinedMessage(true); } } else if (history) { - history->asChannelHistory()->checkJoinedMessage(); + history->checkJoinedMessage(); } Auth().data().sendHistoryChangeNotifications(); } @@ -4768,10 +4575,9 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { const auto existing = App::histItemById(channel, newId); if (existing && !local->mainView()) { const auto history = local->history(); - const auto wasLast = (history->lastMsg == local); local->destroy(); - if (wasLast && !history->lastMsg) { - checkPeerHistory(history->peer); + if (!history->lastMessageKnown()) { + Auth().api().requestDialogEntry(history); } } else { if (existing) { @@ -5211,7 +5017,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { case mtpc_updateChannel: { auto &d = update.c_updateChannel(); - if (auto channel = App::channelLoaded(d.vchannel_id.v)) { + if (const auto channel = App::channelLoaded(d.vchannel_id.v)) { channel->inviter = 0; if (!channel->amIn()) { if (const auto history = App::historyLoaded(channel->id)) { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 28a387bc6..f229e3d33 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -215,9 +215,6 @@ public: not_null channel, const RPCError &e); // for multi invite in channels - void checkPeerHistory(PeerData *peer); - void checkedHistory(PeerData *peer, const MTPmessages_Messages &result); - bool sendMessageFail(const RPCError &error); Dialogs::IndexedList *contactsList(); @@ -288,8 +285,8 @@ public: void scheduleViewIncrement(HistoryItem *item); - void gotRangeDifference(ChannelData *channel, const MTPupdates_ChannelDifference &diff); void onSelfParticipantUpdated(ChannelData *channel); + void feedChannelDifference(const MTPDupdates_channelDifference &data); // Mayde public for ApiWrap, while it is still here. // Better would be for this to be moved to ApiWrap. @@ -478,7 +475,6 @@ private: void feedUpdate(const MTPUpdate &update); void deleteHistoryPart(DeleteHistoryRequest request, const MTPmessages_AffectedHistory &result); - void deleteAllFromUserPart(DeleteAllFromUserParams params, const MTPmessages_AffectedHistory &result); void updateReceived(const mtpPrime *from, const mtpPrime *end); bool updateFail(const RPCError &e); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 2fb97007a..c60adce19 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -593,7 +593,15 @@ void PeerMenuAddChannelMembers(not_null channel) { } void ToggleChannelGrouping(not_null channel, bool group) { - Auth().api().toggleChannelGrouping(channel, group); + const auto callback = [=] { + Ui::Toast::Show(lang(group + ? lng_feed_channel_added + : lng_feed_channel_removed)); + }; + Auth().api().toggleChannelGrouping( + channel, + group, + callback); } base::lambda ClearHistoryHandler(not_null peer) {