Support replies to stories layout in messages.

This commit is contained in:
John Preston 2023-05-26 13:27:34 +04:00
parent b195ec4fd5
commit 2e6790c45c
15 changed files with 522 additions and 39 deletions

View File

@ -285,6 +285,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_message_text" = "New message text...";
"lng_deleted" = "Deleted Account";
"lng_deleted_message" = "Deleted message";
"lng_deleted_story" = "Deleted story";
"lng_pinned_message" = "Pinned message";
"lng_pinned_previous" = "Previous message";
"lng_pinned_unpin_sure" = "Would you like to unpin this message?";

View File

@ -272,6 +272,38 @@ void Changes::entryRemoved(not_null<Dialogs::Entry*> entry) {
_entryChanges.drop(entry);
}
void Changes::storyUpdated(
not_null<Story*> story,
StoryUpdate::Flags flags) {
const auto drop = (flags & StoryUpdate::Flag::Destroyed);
_storyChanges.updated(story, flags, drop);
if (!drop) {
scheduleNotifications();
}
}
rpl::producer<StoryUpdate> Changes::storyUpdates(
StoryUpdate::Flags flags) const {
return _storyChanges.updates(flags);
}
rpl::producer<StoryUpdate> Changes::storyUpdates(
not_null<Story*> story,
StoryUpdate::Flags flags) const {
return _storyChanges.updates(story, flags);
}
rpl::producer<StoryUpdate> Changes::storyFlagsValue(
not_null<Story*> story,
StoryUpdate::Flags flags) const {
return _storyChanges.flagsValue(story, flags);
}
rpl::producer<StoryUpdate> Changes::realtimeStoryUpdates(
StoryUpdate::Flag flag) const {
return _storyChanges.realtimeUpdates(flag);
}
void Changes::scheduleNotifications() {
if (!_notify) {
_notify = true;
@ -291,6 +323,7 @@ void Changes::sendNotifications() {
_messageChanges.sendNotifications();
_entryChanges.sendNotifications();
_topicChanges.sendNotifications();
_storyChanges.sendNotifications();
}
} // namespace Data

View File

@ -38,6 +38,7 @@ inline constexpr int CountBit(Flag Last = Flag::LastUsedBit) {
namespace Data {
class ForumTopic;
class Story;
struct NameUpdate {
NameUpdate(
@ -215,6 +216,24 @@ struct EntryUpdate {
};
struct StoryUpdate {
enum class Flag : uint32 {
None = 0,
Edited = (1U << 0),
Destroyed = (1U << 1),
NewAdded = (1U << 2),
LastUsedBit = (1U << 2),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }
not_null<Story*> story;
Flags flags = 0;
};
class Changes final {
public:
explicit Changes(not_null<Main::Session*> session);
@ -298,6 +317,20 @@ public:
EntryUpdate::Flag flag) const;
void entryRemoved(not_null<Dialogs::Entry*> entry);
void storyUpdated(
not_null<Story*> story,
StoryUpdate::Flags flags);
[[nodiscard]] rpl::producer<StoryUpdate> storyUpdates(
StoryUpdate::Flags flags) const;
[[nodiscard]] rpl::producer<StoryUpdate> storyUpdates(
not_null<Story*> story,
StoryUpdate::Flags flags) const;
[[nodiscard]] rpl::producer<StoryUpdate> storyFlagsValue(
not_null<Story*> story,
StoryUpdate::Flags flags) const;
[[nodiscard]] rpl::producer<StoryUpdate> realtimeStoryUpdates(
StoryUpdate::Flag flag) const;
void sendNotifications();
private:
@ -348,6 +381,7 @@ private:
Manager<ForumTopic, TopicUpdate> _topicChanges;
Manager<HistoryItem, MessageUpdate> _messageChanges;
Manager<Dialogs::Entry, EntryUpdate> _entryChanges;
Manager<Story, StoryUpdate> _storyChanges;
bool _notify = false;

View File

@ -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) {

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/expected.h"
class Image;
class PhotoData;
class DocumentData;
@ -39,11 +40,16 @@ public:
[[nodiscard]] not_null<PeerData*> peer() const;
[[nodiscard]] StoryId id() const;
[[nodiscard]] FullStoryId fullId() const;
[[nodiscard]] TimeId date() const;
[[nodiscard]] const StoryMedia &media() const;
[[nodiscard]] PhotoData *photo() const;
[[nodiscard]] DocumentData *document() const;
[[nodiscard]] bool hasReplyPreview() const;
[[nodiscard]] Image *replyPreview() const;
[[nodiscard]] TextWithEntities inReplyText() const;
void setPinned(bool pinned);
[[nodiscard]] bool pinned() const;
@ -55,7 +61,7 @@ public:
private:
const StoryId _id = 0;
const not_null<PeerData*> _peer;
const StoryMedia _media;
StoryMedia _media;
TextWithEntities _caption;
const TimeId _date = 0;
bool _pinned = false;
@ -84,6 +90,15 @@ public:
~Stories();
[[nodiscard]] Session &owner() const;
[[nodiscard]] Main::Session &session() const;
void updateDependentMessages(not_null<Data::Story*> story);
void registerDependentMessage(
not_null<HistoryItem*> dependent,
not_null<Data::Story*> dependency);
void unregisterDependentMessage(
not_null<HistoryItem*> dependent,
not_null<Data::Story*> dependency);
void loadMore();
void apply(const MTPDupdateStories &data);
@ -101,9 +116,15 @@ private:
[[nodiscard]] Story *parse(
not_null<PeerData*> peer,
const MTPDstoryItem &data);
void processResolvedStories(
not_null<PeerData*> peer,
const QVector<MTPStoryItem> &list);
void sendResolveRequests();
void finalizeResolve(FullStoryId id);
void pushToBack(StoriesList &&list);
void pushToFront(StoriesList &&list);
void applyDeleted(FullStoryId id);
const not_null<Session*> _owner;
base::flat_map<
@ -111,6 +132,15 @@ private:
base::flat_map<StoryId, std::unique_ptr<Story>>> _stories;
base::flat_set<FullStoryId> _deleted;
base::flat_map<
PeerId,
base::flat_map<StoryId, std::vector<Fn<void()>>>> _resolves;
base::flat_map<mtpRequestId, std::vector<Fn<void()>>> _resolveRequests;
std::map<
not_null<Data::Story*>,
base::flat_set<not_null<HistoryItem*>>> _dependentMessages;
std::vector<StoriesList> _all;
rpl::event_stream<> _allChanged;
QString _state;

View File

@ -2544,7 +2544,7 @@ void HistoryItem::setReplyFields(
&& !IsServerMsgId(reply->replyToMsgId)) {
reply->replyToMsgId = replyTo;
if (!reply->updateData(this)) {
RequestDependentMessageData(
RequestDependentMessageItem(
this,
reply->replyToPeerId,
reply->replyToMsgId);
@ -2966,12 +2966,21 @@ void HistoryItem::createComponents(CreateConfig &&config) {
reply->replyToPeerId = config.replyToPeer;
reply->replyToMsgId = config.replyTo;
reply->replyToMsgTop = isScheduled() ? 0 : config.replyToTop;
reply->replyToStoryId = config.replyToStory;
reply->storyReply = (config.replyToStory != 0);
reply->topicPost = config.replyIsTopicPost;
if (!reply->updateData(this)) {
RequestDependentMessageData(
this,
reply->replyToPeerId,
reply->replyToMsgId);
if (reply->replyToMsgId) {
RequestDependentMessageItem(
this,
reply->replyToPeerId,
reply->replyToMsgId);
} else if (reply->replyToStoryId) {
RequestDependentMessageStory(
this,
reply->replyToPeerId,
reply->replyToStoryId);
}
}
}
if (const auto via = Get<HistoryMessageVia>()) {
@ -3518,7 +3527,7 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) {
dependent->topicPost = data.is_forum_topic()
|| Has<HistoryServiceTopicInfo>();
if (!updateServiceDependent()) {
RequestDependentMessageData(
RequestDependentMessageItem(
this,
(dependent->peerId
? dependent->peerId

View File

@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document.h"
#include "data/data_web_page.h"
#include "data/data_file_click_handler.h"
#include "data/data_stories.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "api/api_bot.h"
@ -257,15 +258,17 @@ bool HistoryMessageReply::updateData(
bool force) {
const auto guard = gsl::finally([&] { refreshReplyToMedia(); });
if (!force) {
if (replyToMsg || !replyToMsgId) {
if ((replyToMsg || !replyToMsgId)
&& (replyToStory || !replyToStoryId)) {
return true;
}
}
if (!replyToMsg) {
const auto peerId = replyToPeerId
? replyToPeerId
: holder->history()->peer->id;
if (!replyToMsg && replyToMsgId) {
replyToMsg = holder->history()->owner().message(
(replyToPeerId
? replyToPeerId
: holder->history()->peer->id),
peerId,
replyToMsgId);
if (replyToMsg) {
if (replyToMsg->isEmpty()) {
@ -279,8 +282,22 @@ bool HistoryMessageReply::updateData(
}
}
}
if (!replyToStory && replyToStoryId) {
const auto maybe = holder->history()->owner().stories().lookup({
peerId,
replyToStoryId,
});
if (maybe) {
replyToStory = *maybe;
holder->history()->owner().stories().registerDependentMessage(
holder,
replyToStory.get());
} else if (maybe.error() == Data::NoStory::Deleted) {
force = true;
}
}
if (replyToMsg) {
if (replyToMsg || replyToStory) {
const auto repaint = [=] { holder->customEmojiRepaint(); };
const auto context = Core::MarkedTextContext{
.session = &holder->history()->session(),
@ -288,14 +305,16 @@ bool HistoryMessageReply::updateData(
};
replyToText.setMarkedText(
st::messageTextStyle,
replyToMsg->inReplyText(),
(replyToMsg
? replyToMsg->inReplyText()
: replyToStory->inReplyText()),
Ui::DialogTextOptions(),
context);
updateName(holder);
setReplyToLinkFrom(holder);
if (!replyToMsg->Has<HistoryMessageForwarded>()) {
if (replyToMsg && !replyToMsg->Has<HistoryMessageForwarded>()) {
if (auto bot = replyToMsg->viaBot()) {
replyToVia = std::make_unique<HistoryMessageVia>();
replyToVia->create(
@ -304,15 +323,17 @@ bool HistoryMessageReply::updateData(
}
}
{
if (replyToMsg) {
const auto peer = replyToMsg->history()->peer;
replyToColorKey = (!holder->out()
&& (peer->isMegagroup() || peer->isChat()))
? replyToMsg->from()->id
: PeerId(0);
} else {
replyToColorKey = PeerId(0);
}
const auto media = replyToMsg->media();
const auto media = replyToMsg ? replyToMsg->media() : nullptr;
if (!media || !media->hasReplyPreview() || !media->hasSpoiler()) {
spoiler = nullptr;
} else if (!spoiler) {
@ -320,19 +341,23 @@ bool HistoryMessageReply::updateData(
}
} else if (force) {
replyToMsgId = 0;
replyToStoryId = 0;
replyToColorKey = PeerId(0);
spoiler = nullptr;
}
if (force) {
holder->history()->owner().requestItemResize(holder);
}
return (replyToMsg || !replyToMsgId);
return (replyToMsg || !replyToMsgId)
&& (replyToStory || !replyToStoryId);
}
void HistoryMessageReply::setReplyToLinkFrom(
not_null<HistoryItem*> holder) {
replyToLnk = replyToMsg
? JumpToMessageClickHandler(replyToMsg.get(), holder->fullId())
: replyToStory
? JumpToStoryClickHandler(replyToStory.get())
: nullptr;
}
@ -365,7 +390,9 @@ PeerData *HistoryMessageReply::replyToFrom(
QString HistoryMessageReply::replyToFromName(
not_null<HistoryItem*> holder) const {
if (!replyToMsg) {
if (replyToStory) {
return replyToFromName(replyToStory->peer());
} else if (!replyToMsg) {
return QString();
} else if (holder->Has<HistoryMessageForwarded>()) {
if (const auto fwd = replyToMsg->Get<HistoryMessageForwarded>()) {
@ -405,10 +432,15 @@ void HistoryMessageReply::updateName(
replyToName.setText(st::fwdTextStyle, name, Ui::NameTextOptions());
if (const auto from = replyToFrom(holder)) {
replyToVersion = from->nameVersion();
} else {
} else if (replyToMsg) {
replyToVersion = replyToMsg->author()->nameVersion();
} else {
replyToVersion = replyToStory->peer()->nameVersion();
}
bool hasPreview = replyToMsg->media() ? replyToMsg->media()->hasReplyPreview() : false;
bool hasPreview = (replyToStory && replyToStory->hasReplyPreview())
|| (replyToMsg
&& replyToMsg->media()
&& replyToMsg->media()->hasReplyPreview());
int32 previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
int32 w = replyToName.maxWidth();
if (replyToVia) {
@ -417,14 +449,17 @@ void HistoryMessageReply::updateName(
maxReplyWidth = previewSkip + qMax(w, qMin(replyToText.maxWidth(), int32(st::maxSignatureSize)));
} else {
maxReplyWidth = st::msgDateFont->width(replyToMsgId ? tr::lng_profile_loading(tr::now) : tr::lng_deleted_message(tr::now));
maxReplyWidth = st::msgDateFont->width(statePhrase());
}
maxReplyWidth = st::msgReplyPadding.left() + st::msgReplyBarSkip + maxReplyWidth + st::msgReplyPadding.right();
}
void HistoryMessageReply::resize(int width) const {
if (replyToVia) {
bool hasPreview = replyToMsg->media() ? replyToMsg->media()->hasReplyPreview() : false;
bool hasPreview = (replyToStory && replyToStory->hasReplyPreview())
|| (replyToMsg
&& replyToMsg->media()
&& replyToMsg->media()->hasReplyPreview());
int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
replyToVia->resize(width - st::msgReplyBarSkip - previewSkip - replyToName.maxWidth() - st::msgServiceFont->spacew);
}
@ -471,16 +506,19 @@ void HistoryMessageReply::paint(
const auto pausedSpoiler = context.paused
|| On(PowerSaving::kChatSpoiler);
if (w > st::msgReplyBarSkip) {
if (replyToMsg) {
const auto media = replyToMsg->media();
auto hasPreview = media && media->hasReplyPreview();
if (replyToMsg || replyToStory) {
const auto media = replyToMsg ? replyToMsg->media() : nullptr;
auto hasPreview = (replyToStory && replyToStory->hasReplyPreview()) || (media && media->hasReplyPreview());
if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) {
hasPreview = false;
}
auto previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
if (hasPreview) {
if (const auto image = media->replyPreview()) {
const auto image = media
? media->replyPreview()
: replyToStory->replyPreview();
if (image) {
auto to = style::rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x);
const auto preview = image->pixSingle(
image->size() / style::DevicePixelRatio(),
@ -542,11 +580,19 @@ void HistoryMessageReply::paint(
p.setPen(inBubble
? stm->msgDateFg
: st->msgDateImgFg());
p.drawTextLeft(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2, w + 2 * x, st::msgDateFont->elided(replyToMsgId ? tr::lng_profile_loading(tr::now) : tr::lng_deleted_message(tr::now), w - st::msgReplyBarSkip));
p.drawTextLeft(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2, w + 2 * x, st::msgDateFont->elided(statePhrase(), w - st::msgReplyBarSkip));
}
}
}
QString HistoryMessageReply::statePhrase() const {
return (replyToMsgId || replyToStoryId)
? tr::lng_profile_loading(tr::now)
: storyReply
? tr::lng_deleted_story(tr::now)
: tr::lng_deleted_message(tr::now);
}
void HistoryMessageReply::refreshReplyToMedia() {
replyToDocumentId = 0;
replyToWebPageId = 0;

View File

@ -25,6 +25,7 @@ struct PeerUserpicView;
namespace Data {
class Session;
class Story;
} // namespace Data
namespace Media::Player {
@ -182,6 +183,44 @@ private:
};
class ReplyToStoryPointer final {
public:
ReplyToStoryPointer(Data::Story *story = nullptr) : _data(story) {
}
ReplyToStoryPointer(ReplyToStoryPointer &&other)
: _data(base::take(other._data)) {
}
ReplyToStoryPointer &operator=(ReplyToStoryPointer &&other) {
_data = base::take(other._data);
return *this;
}
ReplyToStoryPointer &operator=(Data::Story *item) {
_data = item;
return *this;
}
[[nodiscard]] bool empty() const {
return !_data;
}
[[nodiscard]] Data::Story *get() const {
return _data;
}
explicit operator bool() const {
return !empty();
}
[[nodiscard]] Data::Story *operator->() const {
return _data;
}
[[nodiscard]] Data::Story &operator*() const {
return *_data;
}
private:
Data::Story *_data = nullptr;
};
struct HistoryMessageReply
: public RuntimeComponent<HistoryMessageReply, HistoryItem> {
HistoryMessageReply() = default;
@ -236,6 +275,7 @@ struct HistoryMessageReply
[[nodiscard]] ClickHandlerPtr replyToLink() const {
return replyToLnk;
}
[[nodiscard]] QString statePhrase() const;
void setReplyToLinkFrom(not_null<HistoryItem*> holder);
void refreshReplyToMedia();
@ -249,6 +289,7 @@ struct HistoryMessageReply
DocumentId replyToDocumentId = 0;
WebPageId replyToWebPageId = 0;
ReplyToMessagePointer replyToMsg;
ReplyToStoryPointer replyToStory;
std::unique_ptr<HistoryMessageVia> replyToVia;
std::unique_ptr<Ui::SpoilerAnimation> spoiler;
ClickHandlerPtr replyToLnk;
@ -257,6 +298,7 @@ struct HistoryMessageReply
mutable int maxReplyWidth = 0;
int toWidth = 0;
bool topicPost = false;
bool storyReply = false;
};

View File

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_media_types.h"
#include "data/data_message_reactions.h"
#include "data/data_session.h"
#include "data/data_stories.h"
#include "data/data_user.h"
#include "history/history.h"
#include "history/history_item.h"
@ -132,7 +133,7 @@ QString GetErrorTextForSending(
return GetErrorTextForSending(thread->peer(), std::move(request));
}
void RequestDependentMessageData(
void RequestDependentMessageItem(
not_null<HistoryItem*> item,
PeerId peerId,
MsgId msgId) {
@ -153,6 +154,23 @@ void RequestDependentMessageData(
done);
}
void RequestDependentMessageStory(
not_null<HistoryItem*> item,
PeerId peerId,
StoryId storyId) {
const auto fullId = item->fullId();
const auto history = item->history();
const auto session = &history->session();
const auto done = [=] {
if (const auto item = session->data().message(fullId)) {
item->updateDependencyItem();
}
};
history->owner().stories().resolve(
{ peerId ? peerId : history->peer->id, storyId },
done);
}
MessageFlags NewMessageFlags(not_null<PeerData*> peer) {
return MessageFlag::BeingSent
| (peer->isSelf() ? MessageFlag() : MessageFlag::Outgoing);
@ -266,6 +284,24 @@ ClickHandlerPtr JumpToMessageClickHandler(
});
}
ClickHandlerPtr JumpToStoryClickHandler(not_null<Data::Story*> story) {
return JumpToStoryClickHandler(story->peer(), story->id());
}
ClickHandlerPtr JumpToStoryClickHandler(
not_null<PeerData*> peer,
StoryId storyId) {
return std::make_shared<LambdaClickHandler>([=] {
const auto separate = Core::App().separateWindowForPeer(peer);
const auto controller = separate
? separate->sessionController()
: peer->session().tryResolveWindow();
if (controller) {
controller->openPeerStory(peer, storyId);
}
});
}
MessageFlags FlagsFromMTP(
MsgId id,
MTPDmessage::Flags flags,

View File

@ -15,6 +15,7 @@ struct SendAction;
} // namespace Api
namespace Data {
class Story;
class Thread;
} // namespace Data
@ -69,10 +70,14 @@ void CheckReactionNotificationSchedule(
const TextWithEntities &text = TextWithEntities());
[[nodiscard]] TextWithEntities UnsupportedMessageText();
void RequestDependentMessageData(
void RequestDependentMessageItem(
not_null<HistoryItem*> item,
PeerId peerId,
MsgId msgId);
void RequestDependentMessageStory(
not_null<HistoryItem*> item,
PeerId peerId,
StoryId storyId);
[[nodiscard]] MessageFlags NewMessageFlags(not_null<PeerData*> peer);
[[nodiscard]] bool ShouldSendSilent(
not_null<PeerData*> peer,
@ -115,6 +120,11 @@ struct SendingErrorRequest {
[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler(
not_null<HistoryItem*> item,
FullMsgId returnToId = FullMsgId());
[[nodiscard]] ClickHandlerPtr JumpToStoryClickHandler(
not_null<Data::Story*> story);
ClickHandlerPtr JumpToStoryClickHandler(
not_null<PeerData*> peer,
StoryId storyId);
[[nodiscard]] not_null<HistoryItem*> GenerateJoinedMessage(
not_null<History*> history,

View File

@ -2268,7 +2268,8 @@ bool Message::getStateReplyInfo(
if (auto reply = displayedReply()) {
int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
if (point.y() >= trect.top() && point.y() < trect.top() + h) {
if (reply->replyToMsg && QRect(trect.x(), trect.y() + st::msgReplyPadding.top(), trect.width(), st::msgReplyBarSize.height()).contains(point)) {
if ((reply->replyToMsg || reply->replyToStory)
&& QRect(trect.x(), trect.y() + st::msgReplyPadding.top(), trect.width(), st::msgReplyBarSize.height()).contains(point)) {
outResult->link = reply->replyToLink();
}
return true;

View File

@ -10,10 +10,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h"
#include "base/power_save_blocker.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/data_changes.h"
#include "data/data_file_origin.h"
#include "data/data_session.h"
#include "data/data_stories.h"
#include "data/data_user.h"
#include "main/main_session.h"
#include "media/stories/media_stories_caption_full_view.h"
#include "media/stories/media_stories_delegate.h"
#include "media/stories/media_stories_header.h"
@ -394,6 +396,18 @@ void Controller::show(
_slider->show({ .index = _index, .total = list.total });
_replyArea->show({ .user = list.user, .id = id });
const auto session = &list.user->session();
if (_session != session) {
_session = session;
_sessionLifetime = session->changes().storyUpdates(
Data::StoryUpdate::Flag::Destroyed
) | rpl::start_with_next([=](Data::StoryUpdate update) {
if (update.story->fullId() == _shown) {
_delegate->storiesClose();
}
});
}
if (_contentFaded) {
togglePaused(true);
}

View File

@ -28,6 +28,10 @@ namespace Ui {
class RpWidget;
} // namespace Ui
namespace Main {
class Session;
} // namespace Main
namespace Media::Player {
struct TrackState;
} // namespace Media::Player
@ -152,6 +156,9 @@ private:
std::unique_ptr<base::PowerSaveBlocker> _powerSaveBlocker;
Main::Session *_session = nullptr;
rpl::lifetime _sessionLifetime;
rpl::lifetime _lifetime;
};

View File

@ -2464,6 +2464,18 @@ Ui::ChatThemeBackgroundData SessionController::backgroundData(
};
}
void SessionController::openPeerStory(
not_null<PeerData*> peer,
StoryId storyId) {
using namespace Media::View;
using namespace Data;
auto &stories = session().data().stories();
if (const auto from = stories.lookup({ peer->id, storyId })) {
window().openInMediaView(OpenRequest(this, *from));
}
}
void SessionController::openPeerStories(PeerId peerId) {
using namespace Media::View;
using namespace Data;
@ -2474,9 +2486,7 @@ void SessionController::openPeerStories(PeerId peerId) {
return list.user->id;
});
if (i != end(all) && !i->ids.empty()) {
if (const auto from = stories.lookup({ peerId, i->ids.front() })) {
window().openInMediaView(OpenRequest(this, *from));
}
openPeerStory(i->user, i->ids.front());
}
}

View File

@ -564,6 +564,7 @@ public:
return _peerThemeOverride.value();
}
void openPeerStory(not_null<PeerData*> peer, StoryId storyId);
void openPeerStories(PeerId peerId);
struct PaintContextArgs {