mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-08-30 22:16:14 +00:00
Support replies to stories layout in messages.
This commit is contained in:
@@ -8,11 +8,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_stories.h"
|
||||
|
||||
#include "api/api_text_entities.h"
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_session.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
|
||||
// #TODO stories testing
|
||||
#include "data/data_user.h"
|
||||
@@ -23,6 +27,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxResolveTogether = 100;
|
||||
|
||||
using UpdateFlag = StoryUpdate::Flag;
|
||||
|
||||
} // namespace
|
||||
|
||||
bool StoriesList::unread() const {
|
||||
@@ -56,6 +64,10 @@ StoryId Story::id() const {
|
||||
return _id;
|
||||
}
|
||||
|
||||
FullStoryId Story::fullId() const {
|
||||
return { _peer->id, _id };
|
||||
}
|
||||
|
||||
TimeId Story::date() const {
|
||||
return _date;
|
||||
}
|
||||
@@ -74,6 +86,45 @@ DocumentData *Story::document() const {
|
||||
return result ? result->get() : nullptr;
|
||||
}
|
||||
|
||||
bool Story::hasReplyPreview() const {
|
||||
return v::match(_media.data, [](not_null<PhotoData*> photo) {
|
||||
return !photo->isNull();
|
||||
}, [](not_null<DocumentData*> document) {
|
||||
return document->hasThumbnail();
|
||||
});
|
||||
}
|
||||
|
||||
Image *Story::replyPreview() const {
|
||||
return v::match(_media.data, [&](not_null<PhotoData*> photo) {
|
||||
return photo->getReplyPreview(
|
||||
Data::FileOriginStory(_peer->id, _id),
|
||||
_peer,
|
||||
false);
|
||||
}, [&](not_null<DocumentData*> document) {
|
||||
return document->getReplyPreview(
|
||||
Data::FileOriginStory(_peer->id, _id),
|
||||
_peer,
|
||||
false);
|
||||
});
|
||||
}
|
||||
|
||||
TextWithEntities Story::inReplyText() const {
|
||||
const auto type = u"Story"_q;
|
||||
return _caption.text.isEmpty()
|
||||
? Ui::Text::PlainLink(type)
|
||||
: tr::lng_dialogs_text_media(
|
||||
tr::now,
|
||||
lt_media_part,
|
||||
tr::lng_dialogs_text_media_wrapped(
|
||||
tr::now,
|
||||
lt_media,
|
||||
Ui::Text::PlainLink(type),
|
||||
Ui::Text::WithEntities),
|
||||
lt_caption,
|
||||
_caption,
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
|
||||
void Story::setPinned(bool pinned) {
|
||||
_pinned = pinned;
|
||||
}
|
||||
@@ -110,6 +161,10 @@ Session &Stories::owner() const {
|
||||
return *_owner;
|
||||
}
|
||||
|
||||
Main::Session &Stories::session() const {
|
||||
return _owner->session();
|
||||
}
|
||||
|
||||
void Stories::apply(const MTPDupdateStories &data) {
|
||||
pushToFront(parse(data.vstories()));
|
||||
_allChanged.fire({});
|
||||
@@ -137,10 +192,7 @@ StoriesList Stories::parse(const MTPUserStories &stories) {
|
||||
}, [&](const MTPDstoryItemSkipped &data) {
|
||||
result.ids.push_back(data.vid().v);
|
||||
}, [&](const MTPDstoryItemDeleted &data) {
|
||||
_deleted.emplace(FullStoryId{
|
||||
.peer = peerFromUser(userId),
|
||||
.story = data.vid().v,
|
||||
});
|
||||
applyDeleted({ peerFromUser(userId), data.vid().v });
|
||||
--result.total;
|
||||
});
|
||||
}
|
||||
@@ -189,6 +241,35 @@ Story *Stories::parse(not_null<PeerData*> peer, const MTPDstoryItem &data) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void Stories::updateDependentMessages(not_null<Data::Story*> story) {
|
||||
const auto i = _dependentMessages.find(story);
|
||||
if (i != end(_dependentMessages)) {
|
||||
for (const auto &dependent : i->second) {
|
||||
dependent->updateDependencyItem();
|
||||
}
|
||||
}
|
||||
session().changes().storyUpdated(
|
||||
story,
|
||||
Data::StoryUpdate::Flag::Edited);
|
||||
}
|
||||
|
||||
void Stories::registerDependentMessage(
|
||||
not_null<HistoryItem*> dependent,
|
||||
not_null<Data::Story*> dependency) {
|
||||
_dependentMessages[dependency].emplace(dependent);
|
||||
}
|
||||
|
||||
void Stories::unregisterDependentMessage(
|
||||
not_null<HistoryItem*> dependent,
|
||||
not_null<Data::Story*> dependency) {
|
||||
const auto i = _dependentMessages.find(dependency);
|
||||
if (i != end(_dependentMessages)) {
|
||||
if (i->second.remove(dependent) && i->second.empty()) {
|
||||
_dependentMessages.erase(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Stories::loadMore() {
|
||||
if (_loadMoreRequestId || _allLoaded) {
|
||||
return;
|
||||
@@ -216,6 +297,119 @@ void Stories::loadMore() {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void Stories::sendResolveRequests() {
|
||||
if (!_resolveRequests.empty()) {
|
||||
return;
|
||||
}
|
||||
struct Prepared {
|
||||
QVector<MTPint> ids;
|
||||
std::vector<Fn<void()>> callbacks;
|
||||
};
|
||||
auto leftToSend = kMaxResolveTogether;
|
||||
auto byPeer = base::flat_map<PeerId, Prepared>();
|
||||
for (auto i = begin(_resolves); i != end(_resolves);) {
|
||||
auto &[peerId, ids] = *i;
|
||||
auto &prepared = byPeer[peerId];
|
||||
for (auto &[storyId, callbacks] : ids) {
|
||||
prepared.ids.push_back(MTP_int(storyId));
|
||||
prepared.callbacks.insert(
|
||||
end(prepared.callbacks),
|
||||
std::make_move_iterator(begin(callbacks)),
|
||||
std::make_move_iterator(end(callbacks)));
|
||||
if (!--leftToSend) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const auto sending = int(prepared.ids.size());
|
||||
if (sending == ids.size()) {
|
||||
i = _resolves.erase(i);
|
||||
if (!leftToSend) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ids.erase(begin(ids), begin(ids) + sending);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const auto api = &_owner->session().api();
|
||||
for (auto &entry : byPeer) {
|
||||
const auto peerId = entry.first;
|
||||
auto &prepared = entry.second;
|
||||
const auto finish = [=, ids = prepared.ids](mtpRequestId id) {
|
||||
for (const auto &id : ids) {
|
||||
finalizeResolve({ peerId, id.v });
|
||||
}
|
||||
if (auto callbacks = _resolveRequests.take(id)) {
|
||||
for (const auto &callback : *callbacks) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
if (_resolveRequests.empty() && !_resolves.empty()) {
|
||||
crl::on_main(&session(), [=] { sendResolveRequests(); });
|
||||
}
|
||||
};
|
||||
const auto user = _owner->session().data().peer(peerId)->asUser();
|
||||
if (!user) {
|
||||
_resolveRequests[0] = std::move(prepared.callbacks);
|
||||
finish(0);
|
||||
continue;
|
||||
}
|
||||
const auto requestId = api->request(MTPstories_GetStoriesByID(
|
||||
user->inputUser,
|
||||
MTP_vector<MTPint>(std::move(prepared.ids))
|
||||
)).done([=](const MTPstories_Stories &result, mtpRequestId id) {
|
||||
owner().processUsers(result.data().vusers());
|
||||
processResolvedStories(user, result.data().vstories().v);
|
||||
finish(id);
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId id) {
|
||||
finish(id);
|
||||
}).send();
|
||||
_resolveRequests.emplace(requestId, std::move(prepared.callbacks));
|
||||
}
|
||||
}
|
||||
|
||||
void Stories::processResolvedStories(
|
||||
not_null<PeerData*> peer,
|
||||
const QVector<MTPStoryItem> &list) {
|
||||
for (const auto &item : list) {
|
||||
item.match([&](const MTPDstoryItem &data) {
|
||||
[[maybe_unused]] const auto story = parse(peer, data);
|
||||
}, [&](const MTPDstoryItemSkipped &data) {
|
||||
LOG(("API Error: Unexpected storyItemSkipped in resolve."));
|
||||
}, [&](const MTPDstoryItemDeleted &data) {
|
||||
applyDeleted({ peer->id, data.vid().v });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Stories::finalizeResolve(FullStoryId id) {
|
||||
const auto already = lookup(id);
|
||||
if (!already.has_value() && already.error() == NoStory::Unknown) {
|
||||
LOG(("API Error: Could not resolve story %1_%2"
|
||||
).arg(id.peer.value
|
||||
).arg(id.story));
|
||||
applyDeleted(id);
|
||||
}
|
||||
}
|
||||
|
||||
void Stories::applyDeleted(FullStoryId id) {
|
||||
const auto i = _stories.find(id.peer);
|
||||
if (i != end(_stories)) {
|
||||
const auto j = i->second.find(id.story);
|
||||
if (j != end(i->second)) {
|
||||
auto story = std::move(j->second);
|
||||
i->second.erase(j);
|
||||
session().changes().storyUpdated(
|
||||
story.get(),
|
||||
UpdateFlag::Destroyed);
|
||||
if (i->second.empty()) {
|
||||
_stories.erase(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
_deleted.emplace(id);
|
||||
}
|
||||
|
||||
const std::vector<StoriesList> &Stories::all() {
|
||||
return _all;
|
||||
}
|
||||
@@ -242,6 +436,21 @@ base::expected<not_null<Story*>, NoStory> Stories::lookup(
|
||||
}
|
||||
|
||||
void Stories::resolve(FullStoryId id, Fn<void()> done) {
|
||||
const auto already = lookup(id);
|
||||
if (already.has_value() || already.error() != NoStory::Unknown) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
auto &ids = _resolves[id.peer];
|
||||
if (ids.empty()) {
|
||||
crl::on_main(&session(), [=] {
|
||||
sendResolveRequests();
|
||||
});
|
||||
}
|
||||
auto &callbacks = ids[id.story];
|
||||
if (done) {
|
||||
callbacks.push_back(std::move(done));
|
||||
}
|
||||
}
|
||||
|
||||
void Stories::pushToBack(StoriesList &&list) {
|
||||
|
Reference in New Issue
Block a user