From 03cdddfe1875d4ead7574b3956fc00d76b281355 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 12 Aug 2019 17:33:36 +0100 Subject: [PATCH] Allow sending scheduled messages. --- Telegram/SourceFiles/api/api_sending.cpp | 15 ++- Telegram/SourceFiles/apiwrap.cpp | 88 ++++++++++----- Telegram/SourceFiles/boxes/confirm_box.cpp | 10 +- .../data/data_scheduled_messages.cpp | 91 ++++++++++++++-- .../data/data_scheduled_messages.h | 9 +- Telegram/SourceFiles/data/data_session.cpp | 11 +- Telegram/SourceFiles/data/data_session.h | 2 + Telegram/SourceFiles/data/data_types.h | 6 - Telegram/SourceFiles/history/history.cpp | 92 ++++++++-------- Telegram/SourceFiles/history/history.h | 8 +- Telegram/SourceFiles/history/history_item.cpp | 5 + Telegram/SourceFiles/history/history_item.h | 103 +++++++++--------- .../SourceFiles/history/history_service.cpp | 4 +- .../SourceFiles/history/history_widget.cpp | 38 +++++-- Telegram/SourceFiles/mainwidget.cpp | 24 ++-- Telegram/SourceFiles/mtproto/type_utils.h | 4 +- .../passport/passport_form_controller.cpp | 4 +- .../window/themes/window_theme.cpp | 2 +- 18 files changed, 336 insertions(+), 180 deletions(-) diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 67d205cd3..0d7b4575c 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -40,7 +40,9 @@ void SendExistingMedia( message.action.generateLocal = true; api->sendAction(message.action); - const auto newId = FullMsgId(peerToChannel(peer->id), clientMsgId()); + const auto newId = FullMsgId( + peerToChannel(peer->id), + session->data().nextLocalMessageId()); const auto randomId = rand_value(); auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media; @@ -84,6 +86,13 @@ void SendExistingMedia( const auto replyTo = message.action.replyTo; const auto captionText = caption.text; + if (message.action.options.scheduled) { + flags |= MTPDmessage::Flag::f_from_scheduled; + sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; + } else { + clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry; + } + session->data().registerMessageRandomId(randomId, newId); history->addNewLocalMessage( @@ -92,7 +101,7 @@ void SendExistingMedia( clientFlags, 0, replyTo, - base::unixtime::now(), + HistoryItem::NewMessageDate(message.action.options.scheduled), messageFromId, messagePostAuthor, media, @@ -111,7 +120,7 @@ void SendExistingMedia( MTP_long(randomId), MTPReplyMarkup(), sentEntities, - MTP_int(0) // schedule_date + MTP_int(message.action.options.scheduled) )).done([=](const MTPUpdates &result) { api->applyUpdates(result, randomId); }).fail([=](const RPCError &error) { diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 0dbc89a99..4bae3e00b 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_media_types.h" #include "data/data_sparse_ids.h" #include "data/data_search_controller.h" +#include "data/data_scheduled_messages.h" #include "data/data_channel_admins.h" #include "data/data_session.h" #include "data/data_channel.h" @@ -4427,6 +4428,12 @@ void ApiWrap::forwardMessages( if (silentPost) { sendFlags |= MTPmessages_ForwardMessages::Flag::f_silent; } + if (action.options.scheduled) { + flags |= MTPDmessage::Flag::f_from_scheduled; + sendFlags |= MTPmessages_ForwardMessages::Flag::f_schedule_date; + } else { + clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry; + } auto forwardFrom = items.front()->history()->peer; auto currentGroupId = items.front()->groupId(); @@ -4448,7 +4455,7 @@ void ApiWrap::forwardMessages( MTP_vector(ids), MTP_vector(randomIds), peer->input, - MTP_int(0) // schedule_date + MTP_int(action.options.scheduled) )).done([=, callback = std::move(successCallback)]( const MTPUpdates &updates) { applyUpdates(updates); @@ -4480,7 +4487,7 @@ void ApiWrap::forwardMessages( if (const auto message = item->toHistoryMessage()) { const auto newId = FullMsgId( peerToChannel(peer->id), - clientMsgId()); + session().data().nextLocalMessageId()); const auto self = _session->user(); const auto messageFromId = channelPost ? UserId(0) @@ -4492,7 +4499,7 @@ void ApiWrap::forwardMessages( newId.msg, flags, clientFlags, - base::unixtime::now(), + HistoryItem::NewMessageDate(action.options.scheduled), messageFromId, messagePostAuthor, message); @@ -4554,7 +4561,9 @@ void ApiWrap::sendSharedContact( const auto history = action.history; const auto peer = history->peer; - const auto newId = FullMsgId(history->channelId(), clientMsgId()); + const auto newId = FullMsgId( + history->channelId(), + session().data().nextLocalMessageId()); const auto channelPost = peer->isChannel() && !peer->isMegagroup(); auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media; @@ -4571,6 +4580,11 @@ void ApiWrap::sendSharedContact( } else { flags |= MTPDmessage::Flag::f_from_id; } + if (action.options.scheduled) { + flags |= MTPDmessage::Flag::f_from_scheduled; + } else { + clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry; + } const auto messageFromId = channelPost ? 0 : _session->userId(); const auto messagePostAuthor = channelPost ? App::peerName(_session->user()) @@ -4586,7 +4600,7 @@ void ApiWrap::sendSharedContact( MTPMessageFwdHeader(), MTPint(), MTP_int(action.replyTo), - MTP_int(base::unixtime::now()), + MTP_int(HistoryItem::NewMessageDate(action.options.scheduled)), MTP_string(), MTP_messageMediaContact( MTP_string(phone), @@ -4907,7 +4921,9 @@ void ApiWrap::sendMessage(MessageToSend &&message) { HistoryItem *lastMessage = nullptr; while (TextUtilities::CutPart(sending, left, MaxMessageSize)) { - auto newId = FullMsgId(peerToChannel(peer->id), clientMsgId()); + auto newId = FullMsgId( + peerToChannel(peer->id), + session().data().nextLocalMessageId()); auto randomId = rand_value(); TextUtilities::Trim(sending); @@ -4963,6 +4979,12 @@ void ApiWrap::sendMessage(MessageToSend &&message) { auto messagePostAuthor = channelPost ? App::peerName(_session->user()) : QString(); + if (action.options.scheduled) { + flags |= MTPDmessage::Flag::f_from_scheduled; + sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date; + } else { + clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry; + } lastMessage = history->addNewMessage( MTP_message( MTP_flags(flags), @@ -4972,7 +4994,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) { MTPMessageFwdHeader(), MTPint(), MTP_int(action.replyTo), - MTP_int(base::unixtime::now()), + MTP_int( + HistoryItem::NewMessageDate(action.options.scheduled)), msgText, media, MTPReplyMarkup(), @@ -4993,7 +5016,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) { MTP_long(randomId), MTPReplyMarkup(), sentEntities, - MTP_int(0) // schedule_date + MTP_int(action.options.scheduled) )).done([=](const MTPUpdates &result) { applyUpdates(result, randomId); history->clearSentDraftText(QString()); @@ -5053,7 +5076,9 @@ void ApiWrap::sendInlineResult( const auto history = action.history; const auto peer = history->peer; - const auto newId = FullMsgId(peerToChannel(peer->id), clientMsgId()); + const auto newId = FullMsgId( + peerToChannel(peer->id), + session().data().nextLocalMessageId()); const auto randomId = rand_value(); auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media; @@ -5081,14 +5106,17 @@ void ApiWrap::sendInlineResult( if (bot) { flags |= MTPDmessage::Flag::f_via_bot_id; } + if (action.options.scheduled) { + flags |= MTPDmessage::Flag::f_from_scheduled; + sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_schedule_date; + } else { + clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry; + } - auto messageFromId = channelPost ? 0 : _session->userId(); - auto messagePostAuthor = channelPost + const auto messageFromId = channelPost ? 0 : _session->userId(); + const auto messagePostAuthor = channelPost ? App::peerName(_session->user()) : QString(); - MTPint messageDate = MTP_int(base::unixtime::now()); - UserId messageViaBotId = bot ? peerToUser(bot->id) : 0; - MsgId messageId = newId.msg; _session->data().registerMessageRandomId(randomId, newId); @@ -5096,10 +5124,10 @@ void ApiWrap::sendInlineResult( history, flags, clientFlags, - messageId, + newId.msg, messageFromId, - messageDate, - messageViaBotId, + MTP_int(HistoryItem::NewMessageDate(action.options.scheduled)), + bot ? peerToUser(bot->id) : 0, action.replyTo, messagePostAuthor); @@ -5113,7 +5141,7 @@ void ApiWrap::sendInlineResult( MTP_long(randomId), MTP_long(data->getQueryId()), MTP_string(data->getId()), - MTP_int(0) // schedule_date + MTP_int(action.options.scheduled) )).done([=](const MTPUpdates &result) { applyUpdates(result, randomId); history->clearSentDraftText(QString()); @@ -5236,6 +5264,9 @@ void ApiWrap::sendMediaWithRandomId( : MTPmessages_SendMedia::Flag(0)) | (!sentEntities.v.isEmpty() ? MTPmessages_SendMedia::Flag::f_entities + : MTPmessages_SendMedia::Flag(0)) + | (options.scheduled + ? MTPmessages_SendMedia::Flag::f_schedule_date : MTPmessages_SendMedia::Flag(0)); const auto peer = history->peer; @@ -5249,7 +5280,7 @@ void ApiWrap::sendMediaWithRandomId( MTP_long(randomId), MTPReplyMarkup(), sentEntities, - MTP_int(0) // schedule_date + MTP_int(options.scheduled) )).done([=](const MTPUpdates &result) { applyUpdates(result); }).fail([=](const RPCError &error) { @@ -5330,6 +5361,9 @@ void ApiWrap::sendAlbumIfReady(not_null album) { : MTPmessages_SendMultiMedia::Flag(0)) | (album->options.silent ? MTPmessages_SendMultiMedia::Flag::f_silent + : MTPmessages_SendMultiMedia::Flag(0)) + | (album->options.scheduled + ? MTPmessages_SendMultiMedia::Flag::f_schedule_date : MTPmessages_SendMultiMedia::Flag(0)); const auto peer = history->peer; history->sendRequestId = request(MTPmessages_SendMultiMedia( @@ -5337,7 +5371,7 @@ void ApiWrap::sendAlbumIfReady(not_null album) { peer->input, MTP_int(replyTo), MTP_vector(medias), - MTP_int(0) // schedule_date + MTP_int(album->options.scheduled) )).done([=](const MTPUpdates &result) { _sendingAlbums.remove(groupId); applyUpdates(result); @@ -5360,10 +5394,7 @@ FileLoadTo ApiWrap::fileLoadTaskOptions(const SendAction &action) const { if (_session->data().notifySilentPosts(peer)) { options.silent = true; } - return FileLoadTo( - peer->id, - action.options, - action.replyTo); + return FileLoadTo(peer->id, action.options, action.replyTo); } void ApiWrap::requestSupportContact(FnMut callback) { @@ -5387,7 +5418,9 @@ void ApiWrap::uploadPeerPhoto(not_null peer, QImage &&image) { peer = peer->migrateToOrMe(); const auto ready = PreparePeerPhoto(peer->id, std::move(image)); - const auto fakeId = FullMsgId(peerToChannel(peer->id), clientMsgId()); + const auto fakeId = FullMsgId( + peerToChannel(peer->id), + session().data().nextLocalMessageId()); const auto already = ranges::find( _peerPhotoUploads, peer, @@ -5817,6 +5850,9 @@ void ApiWrap::createPoll( if (silentPost) { sendFlags |= MTPmessages_SendMedia::Flag::f_silent; } + if (action.options.scheduled) { + sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; + } const auto replyTo = action.replyTo; history->sendRequestId = request(MTPmessages_SendMedia( @@ -5828,7 +5864,7 @@ void ApiWrap::createPoll( MTP_long(rand_value()), MTPReplyMarkup(), MTPVector(), - MTP_int(0) // schedule_date + MTP_int(action.options.scheduled) )).done([=, done = std::move(done)](const MTPUpdates &result) mutable { applyUpdates(result); done(); diff --git a/Telegram/SourceFiles/boxes/confirm_box.cpp b/Telegram/SourceFiles/boxes/confirm_box.cpp index f83449b44..088c4034e 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.cpp +++ b/Telegram/SourceFiles/boxes/confirm_box.cpp @@ -771,8 +771,14 @@ void DeleteMessagesBox::deleteAndClear() { if (const auto item = _session->data().message(itemId)) { const auto history = item->history(); if (item->isScheduled()) { - scheduledIdsByPeer[history->peer].push_back(MTP_int( - _session->data().scheduledMessages().lookupId(item))); + const auto wasOnServer = !item->isSending() + && !item->hasFailed(); + if (wasOnServer) { + scheduledIdsByPeer[history->peer].push_back(MTP_int( + _session->data().scheduledMessages().lookupId(item))); + } else { + _session->data().scheduledMessages().removeSending(item); + } continue; } const auto wasOnServer = IsServerMsgId(item->id); diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.cpp b/Telegram/SourceFiles/data/data_scheduled_messages.cpp index 45dba1d27..1a38ade7d 100644 --- a/Telegram/SourceFiles/data/data_scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/data_scheduled_messages.cpp @@ -137,6 +137,49 @@ void ScheduledMessages::apply( _updates.fire_copy(history); } +void ScheduledMessages::apply( + const MTPDupdateMessageID &update, + not_null local) { + const auto id = update.vid().v; + const auto i = _data.find(local->history()); + Assert(i != end(_data)); + auto &list = i->second; + const auto j = list.itemById.find(id); + if (j != end(list.itemById)) { + local->destroy(); + } else { + Assert(!list.itemById.contains(local->id)); + Assert(!list.idByItem.contains(local)); + local->setRealId(local->history()->nextNonHistoryEntryId()); + list.idByItem.emplace(local, id); + list.itemById.emplace(id, local); + } +} + +void ScheduledMessages::appendSending(not_null item) { + Expects(item->isSending()); + Expects(item->isScheduled()); + + const auto history = item->history(); + auto &list = _data[history]; + list.items.emplace_back(item); + sort(list); + _updates.fire_copy(history); +} + +void ScheduledMessages::removeSending(not_null item) { + Expects(item->isSending() || item->hasFailed()); + Expects(item->isScheduled()); + + const auto history = item->history(); + auto &list = _data[history]; + Assert(!list.itemById.contains(item->id)); + Assert(!list.idByItem.contains(item)); + list.items.erase( + ranges::remove(list.items, item.get(), &OwnedItem::get), + end(list.items)); +} + rpl::producer<> ScheduledMessages::updates(not_null history) { request(history); @@ -175,10 +218,12 @@ void ScheduledMessages::request(not_null history) { if (request.requestId) { return; } + const auto i = _data.find(history); + const auto hash = (i != end(_data)) ? countListHash(i->second) : 0; request.requestId = _session->api().request( MTPmessages_GetScheduledHistory( history->peer->input, - MTP_int(request.hash)) + MTP_int(hash)) ).done([=](const MTPmessages_Messages &result) { parse(history, result); }).fail([=](const RPCError &error) { @@ -197,14 +242,33 @@ void ScheduledMessages::parse( }, [&](const auto &data) { _session->data().processUsers(data.vusers()); _session->data().processChats(data.vchats()); + const auto &messages = data.vmessages().v; if (messages.isEmpty()) { + if (element != end(_data)) { + _data.erase(element); + element = end(_data); + _updates.fire_copy(history); + } return; } element = _data.emplace(history, List()).first; + auto received = base::flat_set>(); auto &list = element->second; for (const auto &message : messages) { - append(history, list, message); + if (const auto item = append(history, list, message)) { + received.emplace(item); + } + } + auto clear = base::flat_set>(); + for (const auto &owned : list.items) { + const auto item = owned.get(); + if (!item->isSending() && !received.contains(item)) { + clear.emplace(item); + } + } + for (const auto item : clear) { + item->destroy(); } if (!list.items.empty()) { sort(list); @@ -214,24 +278,21 @@ void ScheduledMessages::parse( } _updates.fire_copy(history); }); - - request.hash = (element != end(_data)) - ? countListHash(element->second) - : 0; - if (!request.requestId && !request.hash) { + if (!request.requestId) { _requests.remove(history); } } -void ScheduledMessages::append( +HistoryItem *ScheduledMessages::append( not_null history, List &list, const MTPMessage &message) { const auto id = message.match([&](const auto &data) { return data.vid().v; }); - if (list.itemById.find(id) != end(list.itemById)) { - return; + const auto i = list.itemById.find(id); + if (i != end(list.itemById)) { + return i->second; } const auto item = _session->data().addNewMessage( @@ -240,11 +301,12 @@ void ScheduledMessages::append( NewMessageType::Existing); if (!item || item->history() != history) { LOG(("API Error: Bad data received in scheduled messages.")); - return; + return nullptr; } list.items.emplace_back(item); list.itemById.emplace(id, item); list.idByItem.emplace(item, id); + return item; } void ScheduledMessages::sort(List &list) { @@ -277,7 +339,12 @@ int32 ScheduledMessages::countListHash(const List &list) const { using namespace Api; auto hash = HashInit(); - for (const auto &item : list.items | ranges::view::reverse) { + auto &&serverside = ranges::view::all( + list.items + ) | ranges::view::filter([](const OwnedItem &item) { + return !item->isSending() && !item->hasFailed(); + }) | ranges::view::reverse; + for (const auto &item : serverside) { const auto j = list.idByItem.find(item.get()); HashUpdate(hash, j->second); if (const auto edited = item->Get()) { diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.h b/Telegram/SourceFiles/data/data_scheduled_messages.h index 53ba5e80d..d29189e42 100644 --- a/Telegram/SourceFiles/data/data_scheduled_messages.h +++ b/Telegram/SourceFiles/data/data_scheduled_messages.h @@ -32,6 +32,12 @@ public: void apply(const MTPDupdateNewScheduledMessage &update); void apply(const MTPDupdateDeleteScheduledMessages &update); + void apply( + const MTPDupdateMessageID &update, + not_null local); + + void appendSending(not_null item); + void removeSending(not_null item); [[nodiscard]] rpl::producer<> updates(not_null history); [[nodiscard]] Data::MessagesSlice list(not_null history); @@ -44,7 +50,6 @@ private: base::flat_map, MsgId> idByItem; }; struct Request { - int32 hash = 0; mtpRequestId requestId = 0; }; @@ -52,7 +57,7 @@ private: void parse( not_null history, const MTPmessages_Messages &list); - void append( + HistoryItem *append( not_null history, List &list, const MTPMessage &message); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 899099fee..b650abf8b 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1844,6 +1844,12 @@ void Session::destroyMessage(not_null item) { list->erase(item->id); } +MsgId Session::nextLocalMessageId() { + Expects(_localMessageIdCounter < EndClientMsgId); + + return _localMessageIdCounter++; +} + HistoryItem *Session::message(ChannelId channelId, MsgId itemId) const { if (!itemId) { return nullptr; @@ -3627,13 +3633,14 @@ void Session::insertCheckedServiceNotification( const auto flags = MTPDmessage::Flag::f_entities | MTPDmessage::Flag::f_from_id | MTPDmessage::Flag::f_media; - const auto clientFlags = MTPDmessage_ClientFlag::f_clientside_unread; + const auto clientFlags = MTPDmessage_ClientFlag::f_clientside_unread + | MTPDmessage_ClientFlag::f_local_history_entry; auto sending = TextWithEntities(), left = message; while (TextUtilities::CutPart(sending, left, MaxMessageSize)) { addNewMessage( MTP_message( MTP_flags(flags), - MTP_int(clientMsgId()), + MTP_int(nextLocalMessageId()), MTP_int(peerToUser(PeerData::kServiceNotificationsId)), MTP_peerUser(MTP_int(_session->userId())), MTPMessageFwdHeader(), diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index ca9d4c3f3..0fe5b4c7f 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -387,6 +387,7 @@ public: ChannelId channelId, const QVector &data); + [[nodiscard]] MsgId nextLocalMessageId(); [[nodiscard]] HistoryItem *message( ChannelId channelId, MsgId itemId) const; @@ -877,6 +878,7 @@ private: Dialogs::IndexedList _contactsList; Dialogs::IndexedList _contactsNoChatsList; + MsgId _localMessageIdCounter = StartClientMsgId; Messages _messages; std::map _channelMessages; std::map< diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 04f5e887c..2b452b3e4 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -415,12 +415,6 @@ inline bool operator!=(const AudioMsgId &a, const AudioMsgId &b) { return !(a == b); } -inline MsgId clientMsgId() { - static MsgId CurrentClientMsgId = StartClientMsgId; - Assert(CurrentClientMsgId < EndClientMsgId); - return CurrentClientMsgId++; -} - struct MessageCursor { MessageCursor() = default; MessageCursor(int position, int anchor, int scroll) diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index f40d83fc5..c177d5c65 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -16,6 +16,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_drafts.h" #include "data/data_session.h" #include "data/data_media_types.h" +#include "data/data_channel_admins.h" +#include "data/data_scheduled_messages.h" +#include "data/data_folder.h" +#include "data/data_photo.h" +#include "data/data_channel.h" +#include "data/data_chat.h" +#include "data/data_user.h" #include "lang/lang_keys.h" #include "apiwrap.h" #include "mainwidget.h" @@ -29,12 +36,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_shared_media.h" //#include "storage/storage_feed_messages.h" // #feed #include "support/support_helper.h" -#include "data/data_channel_admins.h" -#include "data/data_folder.h" -#include "data/data_photo.h" -#include "data/data_channel.h" -#include "data/data_chat.h" -#include "data/data_user.h" #include "ui/image/image.h" #include "ui/text_options.h" #include "core/crash_reports.h" @@ -622,51 +623,50 @@ HistoryItem *History::addNewMessage( const MTPMessage &msg, MTPDmessage_ClientFlags clientFlags, NewMessageType type) { - if (type == NewMessageType::Existing) { - return addToHistory(msg, clientFlags); - } - if (!loadedAtBottom() || peer->migrateTo()) { - if (const auto item = addToHistory(msg, clientFlags)) { - setLastMessage(item); - if (type == NewMessageType::Unread) { - newItemAdded(item); - } - return item; - } + const auto detachExistingItem = (type == NewMessageType::Unread); + const auto item = createItem(msg, clientFlags, detachExistingItem); + if (!item) { return nullptr; } - - return addNewToLastBlock(msg, clientFlags, type); -} - -HistoryItem *History::addNewToLastBlock( - const MTPMessage &msg, - MTPDmessage_ClientFlags clientFlags, - NewMessageType type) { - Expects(type != NewMessageType::Existing); - - const auto detachExistingItem = (type != NewMessageType::Last); - const auto item = createItem(msg, clientFlags, detachExistingItem); - if (!item || item->mainView()) { + if (type == NewMessageType::Existing || item->mainView()) { return item; } - const auto newUnreadMessage = (type == NewMessageType::Unread); - if (newUnreadMessage) { + const auto unread = (type == NewMessageType::Unread); + if (unread && item->isHistoryEntry()) { applyMessageChanges(item, msg); } - const auto result = addNewItem(item, newUnreadMessage); - checkForLoadedAtTop(result); - if (type == NewMessageType::Last) { - // When we add just one last item, like we do while loading dialogs, - // we want to remove a single added grouped media, otherwise it will - // jump once we open the message history (first we show only that - // media, then we load the rest of the group and show the group). - // - // That way when we open the message history we show nothing until a - // whole history part is loaded, it certainly will contain the group. - removeOrphanMediaGroupPart(); + return addNewItem(item, unread); +} + +not_null History::addNewItem( + not_null item, + bool unread) { + if (item->isScheduled()) { + session().data().scheduledMessages().appendSending(item); + return item; + } else if (!item->isHistoryEntry()) { + return item; } - return result; + if (!loadedAtBottom() || peer->migrateTo()) { + setLastMessage(item); + if (unread) { + newItemAdded(item); + } + } else { + addNewToBack(item, unread); + checkForLoadedAtTop(item); + if (!unread) { + // When we add just one last item, like we do while loading dialogs, + // we want to remove a single added grouped media, otherwise it will + // jump once we open the message history (first we show only that + // media, then we load the rest of the group and show the group). + // + // That way when we open the message history we show nothing until a + // whole history part is loaded, it certainly will contain the group. + removeOrphanMediaGroupPart(); + } + } + return item; } void History::checkForLoadedAtTop(not_null added) { @@ -896,7 +896,7 @@ void History::addUnreadMentionsSlice(const MTPmessages_Messages &result) { Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::UnreadMentionsChanged); } -not_null History::addNewItem( +not_null History::addNewToBack( not_null item, bool unread) { Expects(!isBuildingFrontBlock()); @@ -2866,7 +2866,7 @@ void History::insertLocalMessage(not_null item) { Expects(item->mainView() == nullptr); if (isEmpty()) { - addNewItem(item, false); + addNewToBack(item, false); return; } diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index f881b0add..48db8369e 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -387,11 +387,6 @@ private: // helper method for countScrollState(int top) void countScrollTopItem(int top); - HistoryItem *addNewToLastBlock( - const MTPMessage &msg, - MTPDmessage_ClientFlags clientFlags, - NewMessageType type); - // this method just removes a block from the blocks list // when the last item from this block was detached and // calls the required previousItemChanged() @@ -401,6 +396,9 @@ private: not_null addNewItem( not_null item, bool unread); + not_null addNewToBack( + not_null item, + bool unread); not_null addNewInTheMiddle( not_null item, int blockIndex, diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index fe3ba57b9..07082e6f9 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -187,6 +187,11 @@ TimeId HistoryItem::date() const { return _date; } +TimeId HistoryItem::NewMessageDate(TimeId scheduled) { + const auto now = base::unixtime::now(); + return scheduled ? std::max(scheduled, now + 60) : now; +} + void HistoryItem::finishEdition(int oldKeyboardTop) { _history->owner().requestItemViewRefresh(this); invalidateChatListEntry(); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 6f95b9767..60d66dec8 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -86,7 +86,8 @@ public: UserData *getMessageBot() const; [[nodiscard]] bool isHistoryEntry() const { - return (id < ServerMaxMsgId); + return IsServerMsgId(id) + || (_clientFlags & MTPDmessage_ClientFlag::f_local_history_entry); } [[nodiscard]] bool isFromScheduled() const { return isHistoryEntry() @@ -190,13 +191,13 @@ public: return _clientFlags & MTPDmessage_ClientFlag::f_failed; } void sendFailed(); - virtual int viewsCount() const { + [[nodiscard]] virtual int viewsCount() const { return hasViews() ? 1 : -1; } - virtual bool needCheck() const; + [[nodiscard]] virtual bool needCheck() const; - virtual bool serviceMsg() const { + [[nodiscard]] virtual bool serviceMsg() const { return false; } virtual void applyEdition(const MTPDmessage &message) { @@ -218,14 +219,14 @@ public: virtual void addToUnreadMentions(UnreadMentionType type); virtual void eraseFromUnreadMentions() { } - virtual Storage::SharedMediaTypesMask sharedMediaTypes() const = 0; + [[nodiscard]] virtual Storage::SharedMediaTypesMask sharedMediaTypes() const = 0; void indexAsNewItem(); - virtual QString notificationHeader() const { + [[nodiscard]] virtual QString notificationHeader() const { return QString(); } - virtual QString notificationText() const; + [[nodiscard]] virtual QString notificationText() const; enum class DrawInDialog { Normal, @@ -234,15 +235,15 @@ public: // Returns text with link-start and link-end commands for service-color highlighting. // Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text" - virtual QString inDialogsText(DrawInDialog way) const; - virtual QString inReplyText() const { + [[nodiscard]] virtual QString inDialogsText(DrawInDialog way) const; + [[nodiscard]] virtual QString inReplyText() const { return inDialogsText(DrawInDialog::WithoutSender); } - virtual Ui::Text::IsolatedEmoji isolatedEmoji() const; - virtual TextWithEntities originalText() const { + [[nodiscard]] virtual Ui::Text::IsolatedEmoji isolatedEmoji() const; + [[nodiscard]] virtual TextWithEntities originalText() const { return TextWithEntities(); } - virtual TextForMimeData clipboardText() const { + [[nodiscard]] virtual TextForMimeData clipboardText() const { return TextForMimeData(); } @@ -259,81 +260,83 @@ public: const HistoryItem *&cacheFor, Ui::Text::String &cache) const; - bool emptyText() const { + [[nodiscard]] bool emptyText() const { return _text.isEmpty(); } - bool isPinned() const; - bool canPin() const; - bool canStopPoll() const; - virtual bool allowsSendNow() const; - virtual bool allowsForward() const; - virtual bool allowsEdit(TimeId now) const; - bool canDelete() const; - bool canDeleteForEveryone(TimeId now) const; - bool suggestReport() const; - bool suggestBanReport() const; - bool suggestDeleteAllReport() const; + [[nodiscard]] bool isPinned() const; + [[nodiscard]] bool canPin() const; + [[nodiscard]] bool canStopPoll() const; + [[nodiscard]] virtual bool allowsSendNow() const; + [[nodiscard]] virtual bool allowsForward() const; + [[nodiscard]] virtual bool allowsEdit(TimeId now) const; + [[nodiscard]] bool canDelete() const; + [[nodiscard]] bool canDeleteForEveryone(TimeId now) const; + [[nodiscard]] bool suggestReport() const; + [[nodiscard]] bool suggestBanReport() const; + [[nodiscard]] bool suggestDeleteAllReport() const; - bool hasDirectLink() const; + [[nodiscard]] bool hasDirectLink() const; - MsgId id; - - ChannelId channelId() const; - FullMsgId fullId() const { + [[nodiscard]] ChannelId channelId() const; + [[nodiscard]] FullMsgId fullId() const { return FullMsgId(channelId(), id); } - Data::MessagePosition position() const; - TimeId date() const; + [[nodiscard]] Data::MessagePosition position() const; + [[nodiscard]] TimeId date() const; - Data::Media *media() const { + [[nodiscard]] static TimeId NewMessageDate(TimeId scheduled); + + [[nodiscard]] Data::Media *media() const { return _media.get(); } virtual void setText(const TextWithEntities &textWithEntities) { } - virtual bool textHasLinks() const { + [[nodiscard]] virtual bool textHasLinks() const { return false; } - virtual HistoryMessage *toHistoryMessage() { // dynamic_cast optimize + [[nodiscard]] virtual HistoryMessage *toHistoryMessage() { // dynamic_cast optimize return nullptr; } - virtual const HistoryMessage *toHistoryMessage() const { // dynamic_cast optimize + [[nodiscard]] virtual const HistoryMessage *toHistoryMessage() const { // dynamic_cast optimize return nullptr; } - MsgId replyToId() const; + [[nodiscard]] MsgId replyToId() const; - not_null author() const; + [[nodiscard]] not_null author() const; - TimeId dateOriginal() const; - PeerData *senderOriginal() const; - const HiddenSenderInfo *hiddenForwardedInfo() const; - not_null fromOriginal() const; - QString authorOriginal() const; - MsgId idOriginal() const; + [[nodiscard]] TimeId dateOriginal() const; + [[nodiscard]] PeerData *senderOriginal() const; + [[nodiscard]] const HiddenSenderInfo *hiddenForwardedInfo() const; + [[nodiscard]] not_null fromOriginal() const; + [[nodiscard]] QString authorOriginal() const; + [[nodiscard]] MsgId idOriginal() const; - bool isEmpty() const; + [[nodiscard]] bool isEmpty() const; - MessageGroupId groupId() const; + [[nodiscard]] MessageGroupId groupId() const; - const HistoryMessageReplyMarkup *inlineReplyMarkup() const { + [[nodiscard]] const HistoryMessageReplyMarkup *inlineReplyMarkup() const { return const_cast(this)->inlineReplyMarkup(); } - const ReplyKeyboard *inlineReplyKeyboard() const { + [[nodiscard]] const ReplyKeyboard *inlineReplyKeyboard() const { return const_cast(this)->inlineReplyKeyboard(); } - HistoryMessageReplyMarkup *inlineReplyMarkup(); - ReplyKeyboard *inlineReplyKeyboard(); + [[nodiscard]] HistoryMessageReplyMarkup *inlineReplyMarkup(); + [[nodiscard]] ReplyKeyboard *inlineReplyKeyboard(); [[nodiscard]] ChannelData *discussionPostOriginalSender() const; [[nodiscard]] bool isDiscussionPost() const; [[nodiscard]] PeerData *displayFrom() const; - virtual std::unique_ptr createView( + [[nodiscard]] virtual std::unique_ptr createView( not_null delegate) = 0; virtual ~HistoryItem(); + MsgId id; + protected: HistoryItem( not_null history, diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index ce81f0b66..bd1891114 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -799,8 +799,8 @@ not_null GenerateJoinedMessage( MTPDmessage::Flags flags) { return new HistoryService( history, - MTPDmessage_ClientFlags(), - clientMsgId(), + MTPDmessage_ClientFlag::f_local_history_entry, + history->owner().nextLocalMessageId(), inviteDate, GenerateJoinedText(history, inviter), flags); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 3aad4a198..d0d8c2752 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -634,12 +634,19 @@ HistoryWidget::HistoryWidget( ) | rpl::filter([=](const Api::SendAction &action) { return (action.history == _history); }) | rpl::start_with_next([=](const Api::SendAction &action) { - fastShowAtEnd(action.history); - const auto lastKeyboardUsed = lastForceReplyReplied(FullMsgId( - action.history->channelId(), - action.replyTo)); - if (cancelReply(lastKeyboardUsed) && !action.clearDraft) { - onCloudDraftSave(); + if (action.options.scheduled) { + crl::on_main(this, [=, history = action.history]{ + controller->showSection( + HistoryView::ScheduledMemento(action.history)); + }); + } else { + fastShowAtEnd(action.history); + const auto lastKeyboardUsed = lastForceReplyReplied(FullMsgId( + action.history->channelId(), + action.replyTo)); + if (cancelReply(lastKeyboardUsed) && !action.clearDraft) { + onCloudDraftSave(); + } } if (action.options.handleSupportSwitch) { handleSupportSwitch(action.history); @@ -2953,7 +2960,7 @@ void HistoryWidget::sendSilent() { void HistoryWidget::sendScheduled() { auto options = Api::SendOptions(); - options.scheduled = INT_MAX; + options.scheduled = base::unixtime::now() + 86400; send(options); } @@ -4400,7 +4407,8 @@ void HistoryWidget::sendFileConfirmed( channelId, file->to.replyTo)); - const auto newId = oldId.value_or(FullMsgId(channelId, clientMsgId())); + const auto newId = oldId.value_or( + FullMsgId(channelId, session().data().nextLocalMessageId())); auto groupId = file->album ? file->album->groupId : uint64(0); if (file->album) { const auto proj = [](const SendingAlbum::Item &item) { @@ -4423,6 +4431,7 @@ void HistoryWidget::sendFileConfirmed( const auto peer = history->peer; auto action = Api::SendAction(history); + action.options = file->to.options; action.clearDraft = false; action.replyTo = file->to.replyTo; action.generateLocal = true; @@ -4469,6 +4478,12 @@ void HistoryWidget::sendFileConfirmed( if (groupId) { flags |= MTPDmessage::Flag::f_grouped_id; } + if (file->to.options.scheduled) { + flags |= MTPDmessage::Flag::f_from_scheduled; + } else { + clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry; + } + const auto messageFromId = channelPost ? 0 : session().userId(); const auto messagePostAuthor = channelPost ? App::peerName(session().user()) @@ -4489,7 +4504,7 @@ void HistoryWidget::sendFileConfirmed( MTPMessageFwdHeader(), MTPint(), MTP_int(file->to.replyTo), - MTP_int(base::unixtime::now()), + MTP_int(HistoryItem::NewMessageDate(file->to.options.scheduled)), MTP_string(caption.text), photo, MTPReplyMarkup(), @@ -4525,7 +4540,7 @@ void HistoryWidget::sendFileConfirmed( MTPMessageFwdHeader(), MTPint(), MTP_int(file->to.replyTo), - MTP_int(base::unixtime::now()), + MTP_int(HistoryItem::NewMessageDate(file->to.options.scheduled)), MTP_string(caption.text), document, MTPReplyMarkup(), @@ -4564,7 +4579,8 @@ void HistoryWidget::sendFileConfirmed( MTPMessageFwdHeader(), MTPint(), MTP_int(file->to.replyTo), - MTP_int(base::unixtime::now()), + MTP_int( + HistoryItem::NewMessageDate(file->to.options.scheduled)), MTP_string(caption.text), document, MTPReplyMarkup(), diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index d75505f0b..b8b2290e1 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -3899,19 +3899,25 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { const auto &d = update.c_updateMessageID(); const auto randomId = d.vrandom_id().v; if (const auto id = session().data().messageIdByRandomId(randomId)) { - const auto channel = id.channel; const auto newId = d.vid().v; if (const auto local = session().data().message(id)) { - const auto existing = session().data().message(channel, newId); - if (existing && !local->mainView()) { - const auto history = local->history(); - local->destroy(); - history->requestChatListMessage(); + if (local->isScheduled()) { + session().data().scheduledMessages().apply(d, local); } else { - if (existing) { - existing->destroy(); + const auto channel = id.channel; + const auto existing = session().data().message( + channel, + newId); + if (existing && !local->mainView()) { + const auto history = local->history(); + local->destroy(); + history->requestChatListMessage(); + } else { + if (existing) { + existing->destroy(); + } + local->setRealId(d.vid().v); } - local->setRealId(d.vid().v); } } session().data().unregisterMessageRandomId(randomId); diff --git a/Telegram/SourceFiles/mtproto/type_utils.h b/Telegram/SourceFiles/mtproto/type_utils.h index 228a3f13c..ccaab5d3c 100644 --- a/Telegram/SourceFiles/mtproto/type_utils.h +++ b/Telegram/SourceFiles/mtproto/type_utils.h @@ -66,8 +66,8 @@ enum class MTPDmessage_ClientFlag : uint32 { // message has no media and only a several emoji text f_isolated_emoji = (1U << 21), - // update this when adding new client side flags - MIN_FIELD = (1U << 21), + // message is local message existing in the message history + f_local_history_entry = (1U << 20), }; inline constexpr bool is_flag_type(MTPDmessage_ClientFlag) { return true; } using MTPDmessage_ClientFlags = base::flags; diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp index 6670c8a29..c24813c2b 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -1520,7 +1520,9 @@ void FormController::uploadEncryptedFile( prepared->setFileData(prepared->content); prepared->filemd5 = file.uploadData->md5checksum; - file.uploadData->fullId = FullMsgId(0, clientMsgId()); + file.uploadData->fullId = FullMsgId( + 0, + Auth().data().nextLocalMessageId()); Auth().uploader().upload(file.uploadData->fullId, std::move(prepared)); } diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp index 21451d8b8..daf581689 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme.cpp @@ -429,7 +429,7 @@ void ChatBackground::checkUploadWallPaper() { const auto ready = PrepareWallPaper(_original); const auto documentId = ready.id; - _wallPaperUploadId = FullMsgId(0, clientMsgId()); + _wallPaperUploadId = FullMsgId(0, _session->data().nextLocalMessageId()); _session->uploader().uploadMedia(_wallPaperUploadId, ready); if (_wallPaperUploadLifetime) { return;