Load albums of last chat messages.

This commit is contained in:
John Preston 2021-10-04 23:37:55 +04:00
parent 576883ddc8
commit 730412fefe
14 changed files with 253 additions and 122 deletions

View file

@ -560,7 +560,8 @@ void ApiWrap::resolveMessageDatas() {
)).done([=](
const MTPmessages_Messages &result,
mtpRequestId requestId) {
gotMessageDatas(nullptr, result, requestId);
_session->data().processExistingMessages(nullptr, result);
finalizeMessageDataRequest(nullptr, requestId);
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
finalizeMessageDataRequest(nullptr, requestId);
}).afterDelay(kSmallDelayMs).send();
@ -586,7 +587,8 @@ void ApiWrap::resolveMessageDatas() {
)).done([=](
const MTPmessages_Messages &result,
mtpRequestId requestId) {
gotMessageDatas(channel, result, requestId);
_session->data().processExistingMessages(channel, result);
finalizeMessageDataRequest(channel, requestId);
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
finalizeMessageDataRequest(channel, requestId);
}).afterDelay(kSmallDelayMs).send();
@ -602,37 +604,6 @@ void ApiWrap::resolveMessageDatas() {
}
}
void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId requestId) {
const auto handleResult = [&](auto &&result) {
_session->data().processUsers(result.vusers());
_session->data().processChats(result.vchats());
_session->data().processMessages(
result.vmessages(),
NewMessageType::Existing);
};
switch (msgs.type()) {
case mtpc_messages_messages:
handleResult(msgs.c_messages_messages());
break;
case mtpc_messages_messagesSlice:
handleResult(msgs.c_messages_messagesSlice());
break;
case mtpc_messages_channelMessages: {
auto &d = msgs.c_messages_channelMessages();
if (channel) {
channel->ptsReceived(d.vpts().v);
} else {
LOG(("App Error: received messages.channelMessages when no channel was passed! (ApiWrap::gotDependencyItem)"));
}
handleResult(d);
} break;
case mtpc_messages_messagesNotModified:
LOG(("API Error: received messages.messagesNotModified! (ApiWrap::gotDependencyItem)"));
break;
}
finalizeMessageDataRequest(channel, requestId);
}
void ApiWrap::finalizeMessageDataRequest(
ChannelData *channel,
mtpRequestId requestId) {

View file

@ -458,7 +458,6 @@ private:
void saveDraftsToCloud();
void resolveMessageDatas();
void gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId requestId);
void finalizeMessageDataRequest(
ChannelData *channel,
mtpRequestId requestId);

View file

@ -438,6 +438,49 @@ void Histories::requestFakeChatListMessage(
});
}
void Histories::requestGroupAround(not_null<HistoryItem*> item) {
const auto history = item->history();
const auto id = item->id;
const auto i = _chatListGroupRequests.find(history);
if (i != end(_chatListGroupRequests)) {
if (i->second.aroundId == id) {
return;
} else {
cancelRequest(i->second.requestId);
_chatListGroupRequests.erase(i);
}
}
constexpr auto kMaxAlbumCount = 10;
const auto requestId = sendRequest(history, RequestType::History, [=](
Fn<void()> finish) {
return session().api().request(MTPmessages_GetHistory(
history->peer->input,
MTP_int(id),
MTP_int(0), // offset_date
MTP_int(-kMaxAlbumCount),
MTP_int(2 * kMaxAlbumCount - 1),
MTP_int(0), // max_id
MTP_int(0), // min_id
MTP_long(0) // hash
)).done([=](const MTPmessages_Messages &result) {
_owner->processExistingMessages(
history->peer->asChannel(),
result);
_chatListGroupRequests.remove(history);
history->migrateToOrMe()->applyChatListGroup(
history->channelId(),
result);
finish();
}).fail([=](const MTP::Error &error) {
_chatListGroupRequests.remove(history);
finish();
}).send();
});
_chatListGroupRequests.emplace(
history,
ChatListGroupRequest{ .aroundId = id, .requestId = requestId });
}
void Histories::sendPendingReadInbox(not_null<History*> history) {
if (const auto state = lookup(history)) {
DEBUG_LOG(("Reading: send pending now with till %1 and when %2"

View file

@ -59,6 +59,8 @@ public:
void changeDialogUnreadMark(not_null<History*> history, bool unread);
void requestFakeChatListMessage(not_null<History*> history);
void requestGroupAround(not_null<HistoryItem*> item);
void deleteMessages(
not_null<History*> history,
const QVector<MTPint> &ids,
@ -95,6 +97,10 @@ private:
bool sentReadDone = false;
bool postponedRequestEntry = false;
};
struct ChatListGroupRequest {
MsgId aroundId = 0;
mtpRequestId requestId = 0;
};
void readInboxTill(not_null<History*> history, MsgId tillId, bool force);
void sendReadRequests();
@ -130,6 +136,10 @@ private:
base::flat_set<not_null<History*>> _fakeChatListRequests;
base::flat_map<
not_null<History*>,
ChatListGroupRequest> _chatListGroupRequests;
};
} // namespace Data

View file

@ -63,6 +63,7 @@ constexpr auto kFastRevokeRestriction = 24 * 60 * TimeId(60);
constexpr auto kMaxPreviewImages = 3;
using ItemPreview = HistoryView::ItemPreview;
using ItemPreviewImage = HistoryView::ItemPreviewImage;
[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
auto result = Call();
@ -191,25 +192,18 @@ using ItemPreview = HistoryView::ItemPreview;
return square;
}
struct PreparedPreview {
QImage preview;
bool loading = false;
explicit operator bool() const {
return !preview.isNull() || loading;
}
};
[[nodiscard]] PreparedPreview PreparePhotoPreview(
[[nodiscard]] ItemPreviewImage PreparePhotoPreview(
not_null<const HistoryItem*> item,
const std::shared_ptr<PhotoMedia> &media,
ImageRoundRadius radius) {
const auto photo = media->owner();
const auto readyCacheKey = reinterpret_cast<uint64>(photo.get());
if (const auto small = media->image(PhotoSize::Small)) {
return { PreparePreviewImage(small, radius) };
return { PreparePreviewImage(small, radius), readyCacheKey };
} else if (const auto thumbnail = media->image(PhotoSize::Thumbnail)) {
return { PreparePreviewImage(thumbnail, radius) };
return { PreparePreviewImage(thumbnail, radius), readyCacheKey };
} else if (const auto large = media->image(PhotoSize::Large)) {
return { PreparePreviewImage(large, radius) };
return { PreparePreviewImage(large, radius), readyCacheKey };
}
const auto allowedToDownload = [&] {
const auto photo = media->owner();
@ -223,29 +217,32 @@ struct PreparedPreview {
item->history()->peer,
photo);
}();
const auto cacheKey = allowedToDownload ? 0 : readyCacheKey;
if (allowedToDownload) {
media->owner()->load(PhotoSize::Small, item->fullId());
}
if (const auto blurred = media->thumbnailInline()) {
return { PreparePreviewImage(blurred, radius), allowedToDownload };
return { PreparePreviewImage(blurred, radius), cacheKey };
}
return { QImage(), allowedToDownload };
return { QImage(), allowedToDownload ? 0 : cacheKey };
}
[[nodiscard]] PreparedPreview PrepareFilePreviewImage(
[[nodiscard]] ItemPreviewImage PrepareFilePreviewImage(
not_null<const HistoryItem*> item,
const std::shared_ptr<DocumentMedia> &media,
ImageRoundRadius radius) {
Expects(media->owner()->hasThumbnail());
const auto document = media->owner();
const auto readyCacheKey = reinterpret_cast<uint64>(document.get());
if (const auto thumbnail = media->thumbnail()) {
return { PreparePreviewImage(thumbnail, radius) };
return { PreparePreviewImage(thumbnail, radius), readyCacheKey };
}
media->owner()->loadThumbnail(item->fullId());
document->loadThumbnail(item->fullId());
if (const auto blurred = media->thumbnailInline()) {
return { PreparePreviewImage(blurred, radius), true };
return { PreparePreviewImage(blurred, radius), 0 };
}
return { QImage(), true };
return { QImage(), 0 };
}
[[nodiscard]] QImage PutPlayIcon(QImage preview) {
@ -260,28 +257,17 @@ struct PreparedPreview {
return preview;
}
[[nodiscard]] PreparedPreview PrepareFilePreview(
[[nodiscard]] ItemPreviewImage PrepareFilePreview(
not_null<const HistoryItem*> item,
const std::shared_ptr<DocumentMedia> &media,
ImageRoundRadius radius) {
if (auto result = PrepareFilePreviewImage(item, media, radius)) {
const auto document = media->owner();
if (!result.preview.isNull()
&& (document->isVideoFile() || document->isVideoMessage())) {
result.preview = PutPlayIcon(std::move(result.preview));
}
return result;
auto result = PrepareFilePreviewImage(item, media, radius);
const auto document = media->owner();
if (!result.data.isNull()
&& (document->isVideoFile() || document->isVideoMessage())) {
result.data = PutPlayIcon(std::move(result.data));
}
Expects(media->owner()->hasThumbnail());
if (const auto thumbnail = media->thumbnail()) {
return { PreparePreviewImage(thumbnail, radius) };
}
media->owner()->loadThumbnail(item->fullId());
if (const auto blurred = media->thumbnailInline()) {
return { PreparePreviewImage(blurred, radius), true };
}
return { QImage(), true };
return result;
}
[[nodiscard]] bool TryFilePreview(not_null<DocumentData*> document) {
@ -290,6 +276,20 @@ struct PreparedPreview {
&& !document->isAudioFile();
}
template <typename MediaType>
[[nodiscard]] ItemPreviewImage FindCachedPreview(
const std::vector<ItemPreviewImage> *existing,
not_null<MediaType*> data) {
if (!existing) {
return {};
}
const auto i = ranges::find(
*existing,
reinterpret_cast<uint64>(data.get()),
&ItemPreviewImage::cacheKey);
return (i != end(*existing)) ? *i : ItemPreviewImage();
}
} // namespace
TextForMimeData WithCaptionClipboardText(
@ -554,22 +554,27 @@ ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const {
return toGroupPreview(group->items, options);
}
}
const auto caption = options.hideCaption
? QString()
: parent()->originalText().text;
auto images = std::vector<QImage>();
const auto media = _photo->createMediaView();
const auto radius = _chat
? ImageRoundRadius::Ellipse
: ImageRoundRadius::Small;
auto images = std::vector<ItemPreviewImage>();
auto context = std::any();
if (auto prepared = PreparePhotoPreview(parent(), media, radius)) {
images.push_back(std::move(prepared.preview));
if (prepared.loading) {
context = media;
if (auto cached = FindCachedPreview(options.existing, _photo)) {
images.push_back(std::move(cached));
} else {
const auto media = _photo->createMediaView();
const auto radius = _chat
? ImageRoundRadius::Ellipse
: ImageRoundRadius::Small;
if (auto prepared = PreparePhotoPreview(parent(), media, radius)
; prepared || !prepared.cacheKey) {
images.push_back(std::move(prepared));
if (!prepared.cacheKey) {
context = media;
}
}
}
const auto type = tr::lng_in_dlg_photo(tr::now);
const auto caption = options.hideCaption
? QString()
: parent()->originalText().text;
return {
.text = WithCaptionDialogsText(type, caption, !images.empty()),
.images = std::move(images),
@ -751,6 +756,23 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
if (const auto sticker = _document->sticker()) {
return Media::toPreview(options);
}
auto images = std::vector<ItemPreviewImage>();
auto context = std::any();
if (auto cached = FindCachedPreview(options.existing, _document)) {
images.push_back(std::move(cached));
} else if (TryFilePreview(_document)) {
const auto media = _document->createMediaView();
const auto radius = _document->isVideoMessage()
? ImageRoundRadius::Ellipse
: ImageRoundRadius::Small;
if (auto prepared = PrepareFilePreview(parent(), media, radius)
; prepared || !prepared.cacheKey) {
images.push_back(std::move(prepared));
if (!prepared.cacheKey) {
context = media;
}
}
}
const auto type = [&] {
using namespace Ui::Text;
if (_document->isVideoMessage()) {
@ -772,22 +794,6 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
const auto caption = options.hideCaption
? QString()
: parent()->originalText().text;
auto images = std::vector<QImage>();
const auto media = TryFilePreview(_document)
? _document->createMediaView()
: nullptr;
const auto radius = _document->isVideoMessage()
? ImageRoundRadius::Ellipse
: ImageRoundRadius::Small;
auto context = std::any();
if (media) {
if (auto prepared = PrepareFilePreview(parent(), media, radius)) {
images.push_back(std::move(prepared.preview));
if (prepared.loading) {
context = media;
}
}
}
return {
.text = WithCaptionDialogsText(type, caption, !images.empty()),
.images = std::move(images),

View file

@ -28,6 +28,7 @@ enum class Context : char;
class Element;
class Media;
struct ItemPreview;
struct ItemPreviewImage;
struct ToPreviewOptions;
} // namespace HistoryView
@ -75,6 +76,7 @@ public:
not_null<HistoryItem*> parent() const;
using ToPreviewOptions = HistoryView::ToPreviewOptions;
using ItemPreviewImage = HistoryView::ItemPreviewImage;
using ItemPreview = HistoryView::ItemPreview;
virtual std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) = 0;

View file

@ -1859,6 +1859,26 @@ void Session::processMessages(
processMessages(data.v, type);
}
void Session::processExistingMessages(
ChannelData *channel,
const MTPmessages_Messages &data) {
data.match([&](const MTPDmessages_channelMessages &data) {
if (channel) {
channel->ptsReceived(data.vpts().v);
} else {
LOG(("App Error: received messages.channelMessages!"));
}
}, [](const auto &) {});
data.match([&](const MTPDmessages_messagesNotModified&) {
LOG(("API Error: received messages.messagesNotModified!"));
}, [&](const auto &data) {
processUsers(data.vusers());
processChats(data.vchats());
processMessages(data.vmessages(), NewMessageType::Existing);
});
}
const Session::Messages *Session::messagesList(ChannelId channelId) const {
if (channelId == NoChannel) {
return &_messages;

View file

@ -341,6 +341,9 @@ public:
void processMessages(
const MTPVector<MTPMessage> &data,
NewMessageType type);
void processExistingMessages(
ChannelData *channel,
const MTPmessages_Messages &data);
void processMessagesDeleted(
ChannelId channelId,
const QVector<MTPint> &data);

View file

@ -105,6 +105,7 @@ void MessageView::paint(
return;
}
if (_textCachedFor != item.get()) {
options.existing = &_imagesCache;
auto preview = item->toPreview(options);
if (!preview.images.empty() && preview.imagesInTextPosition > 0) {
_senderCache.setText(
@ -140,7 +141,11 @@ void MessageView::paint(
? st::dialogsTextPaletteOver
: st::dialogsTextPalette);
p.setFont(st::dialogsTextFont);
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
p.setPen(active
? st::dialogsTextFgActive
: selected
? st::dialogsTextFgOver
: st::dialogsTextFg);
const auto guard = gsl::finally([&] {
p.restoreTextPalette();
});
@ -161,7 +166,10 @@ void MessageView::paint(
if (rect.width() < st::dialogsMiniPreview) {
break;
}
p.drawImage(rect.x(), rect.y() + st::dialogsMiniPreviewTop, image);
p.drawImage(
rect.x(),
rect.y() + st::dialogsMiniPreviewTop,
image.data);
rect.setLeft(rect.x()
+ st::dialogsMiniPreview
+ st::dialogsMiniPreviewSkip);

View file

@ -18,6 +18,7 @@ namespace Ui {
namespace HistoryView {
struct ToPreviewOptions;
struct ItemPreviewImage;
struct ItemPreview;
} // namespace HistoryView
@ -31,6 +32,7 @@ public:
~MessageView();
using ToPreviewOptions = HistoryView::ToPreviewOptions;
using ItemPreviewImage = HistoryView::ItemPreviewImage;
using ItemPreview = HistoryView::ItemPreview;
void itemInvalidated(not_null<const HistoryItem*> item);
@ -50,7 +52,7 @@ private:
mutable const HistoryItem *_textCachedFor = nullptr;
mutable Ui::Text::String _senderCache;
mutable Ui::Text::String _textCache;
mutable std::vector<QImage> _imagesCache;
mutable std::vector<ItemPreviewImage> _imagesCache;
mutable std::unique_ptr<LoadingContext> _loadingContext;
};

View file

@ -1253,27 +1253,31 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
}
if (const auto added = createItems(slice); !added.empty()) {
startBuildingFrontBlock(added.size());
for (const auto &item : added) {
addItemToBlock(item);
}
finishBuildingFrontBlock();
if (loadedAtBottom()) {
// Add photos to overview and authors to lastAuthors.
addItemsToLists(added);
}
addToSharedMedia(added);
addCreatedOlderSlice(added);
} else {
// If no items were added it means we've loaded everything old.
_loadedAtTop = true;
addEdgesToSharedMedia();
}
checkLocalMessages();
checkLastMessage();
}
void History::addCreatedOlderSlice(
const std::vector<not_null<HistoryItem*>> &items) {
startBuildingFrontBlock(items.size());
for (const auto &item : items) {
addItemToBlock(item);
}
finishBuildingFrontBlock();
if (loadedAtBottom()) {
// Add photos to overview and authors to lastAuthors.
addItemsToLists(items);
}
addToSharedMedia(items);
}
void History::addNewerSlice(const QVector<MTPMessage> &slice) {
bool wasLoadedAtBottom = loadedAtBottom();
@ -2266,6 +2270,14 @@ void History::setChatListMessage(HistoryItem *item) {
}
_chatListMessage = item;
setChatListTimeId(item->date());
// If we have a single message from a group, request the full album.
if (hasOrphanMediaGroupPart()
&& !item->toPreview({
.hideSender = true,
.hideCaption = true }).images.empty()) {
owner().histories().requestGroupAround(item);
}
} else if (!_chatListMessage || *_chatListMessage) {
_chatListMessage = nullptr;
updateChatListEntry();
@ -2422,6 +2434,44 @@ void History::setFakeChatListMessageFrom(const MTPmessages_Messages &data) {
setChatListMessage(item);
}
void History::applyChatListGroup(
ChannelId channelId,
const MTPmessages_Messages &data) {
if (!isEmpty()
|| !_chatListMessage
|| !*_chatListMessage
|| (*_chatListMessage)->history()->channelId() != channelId
|| (*_chatListMessage)->history() != this
|| !_lastMessage
|| !*_lastMessage) {
return;
}
// Apply loaded album as a last slice.
const auto processMessages = [&](const MTPVector<MTPMessage> &messages) {
auto items = std::vector<not_null<HistoryItem*>>();
items.reserve(messages.v.size());
for (const auto &message : messages.v) {
const auto id = IdFromMessage(message);
if (const auto message = owner().message(channelId, id)) {
items.push_back(message);
}
}
if (!ranges::contains(items, not_null(*_lastMessage))
|| !ranges::contains(items, not_null(*_chatListMessage))) {
return;
}
_loadedAtBottom = true;
ranges::sort(items, ranges::less{}, &HistoryItem::id);
addCreatedOlderSlice(items);
checkLocalMessages();
checkLastMessage();
};
data.match([&](const MTPDmessages_messagesNotModified &) {
}, [&](const auto &data) {
processMessages(data.vmessages());
});
}
HistoryItem *History::lastMessage() const {
return _lastMessage.value_or(nullptr);
}

View file

@ -409,6 +409,10 @@ public:
void setFakeChatListMessageFrom(const MTPmessages_Messages &data);
void checkChatListMessageRemoved(not_null<HistoryItem*> item);
void applyChatListGroup(
ChannelId channelId,
const MTPmessages_Messages &data);
void forgetScrollState() {
scrollTopItem = nullptr;
}
@ -516,6 +520,9 @@ private:
return _buildingFrontBlock != nullptr;
}
void addCreatedOlderSlice(
const std::vector<not_null<HistoryItem*>> &items);
void checkForLoadedAtTop(not_null<HistoryItem*> added);
void mainViewRemoved(
not_null<HistoryBlock*> block,

View file

@ -57,20 +57,30 @@ enum class PointState : char;
enum class Context : char;
class ElementDelegate;
struct ToPreviewOptions {
bool hideSender = false;
bool hideCaption = false;
bool generateImages = true;
bool ignoreGroup = false;
struct ItemPreviewImage {
QImage data;
uint64 cacheKey = 0;
explicit operator bool() const {
return !data.isNull();
}
};
struct ItemPreview {
QString text;
std::vector<QImage> images;
std::vector<ItemPreviewImage> images;
int imagesInTextPosition = 0;
std::any loadingContext;
};
struct ToPreviewOptions {
const std::vector<ItemPreviewImage> *existing = nullptr;
bool hideSender = false;
bool hideCaption = false;
bool generateImages = true;
bool ignoreGroup = false;
};
} // namespace HistoryView
struct HiddenSenderInfo;

View file

@ -804,7 +804,7 @@ void Notification::updateNotifyDisplay() {
p.setTextPalette(st::dialogsTextPalette);
p.setPen(st::dialogsTextFg);
p.setFont(st::dialogsTextFont);
const auto text = _item // #TODO minis use images
const auto text = _item
? _item->toPreview({
.hideSender = reminder,
.generateImages = false,