From 2e6790c45c44ed0372946cd866fbdea790a06bcd Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 26 May 2023 13:27:34 +0400 Subject: [PATCH] Support replies to stories layout in messages. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/data/data_changes.cpp | 33 +++ Telegram/SourceFiles/data/data_changes.h | 34 +++ Telegram/SourceFiles/data/data_stories.cpp | 219 +++++++++++++++++- Telegram/SourceFiles/data/data_stories.h | 32 ++- Telegram/SourceFiles/history/history_item.cpp | 21 +- .../history/history_item_components.cpp | 88 +++++-- .../history/history_item_components.h | 42 ++++ .../history/history_item_helpers.cpp | 38 ++- .../history/history_item_helpers.h | 12 +- .../history/view/history_view_message.cpp | 3 +- .../stories/media_stories_controller.cpp | 14 ++ .../media/stories/media_stories_controller.h | 7 + .../window/window_session_controller.cpp | 16 +- .../window/window_session_controller.h | 1 + 15 files changed, 522 insertions(+), 39 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 54b768de8..1b1d41d18 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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?"; diff --git a/Telegram/SourceFiles/data/data_changes.cpp b/Telegram/SourceFiles/data/data_changes.cpp index 9f28a7af0..f20041565 100644 --- a/Telegram/SourceFiles/data/data_changes.cpp +++ b/Telegram/SourceFiles/data/data_changes.cpp @@ -272,6 +272,38 @@ void Changes::entryRemoved(not_null entry) { _entryChanges.drop(entry); } +void Changes::storyUpdated( + not_null story, + StoryUpdate::Flags flags) { + const auto drop = (flags & StoryUpdate::Flag::Destroyed); + _storyChanges.updated(story, flags, drop); + if (!drop) { + scheduleNotifications(); + } +} + +rpl::producer Changes::storyUpdates( + StoryUpdate::Flags flags) const { + return _storyChanges.updates(flags); +} + +rpl::producer Changes::storyUpdates( + not_null story, + StoryUpdate::Flags flags) const { + return _storyChanges.updates(story, flags); +} + +rpl::producer Changes::storyFlagsValue( + not_null story, + StoryUpdate::Flags flags) const { + return _storyChanges.flagsValue(story, flags); +} + +rpl::producer 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 diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index e5bbc48e4..ee8ff6075 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -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; + friend inline constexpr auto is_flag_type(Flag) { return true; } + + not_null story; + Flags flags = 0; + +}; + class Changes final { public: explicit Changes(not_null session); @@ -298,6 +317,20 @@ public: EntryUpdate::Flag flag) const; void entryRemoved(not_null entry); + void storyUpdated( + not_null story, + StoryUpdate::Flags flags); + [[nodiscard]] rpl::producer storyUpdates( + StoryUpdate::Flags flags) const; + [[nodiscard]] rpl::producer storyUpdates( + not_null story, + StoryUpdate::Flags flags) const; + [[nodiscard]] rpl::producer storyFlagsValue( + not_null story, + StoryUpdate::Flags flags) const; + [[nodiscard]] rpl::producer realtimeStoryUpdates( + StoryUpdate::Flag flag) const; + void sendNotifications(); private: @@ -348,6 +381,7 @@ private: Manager _topicChanges; Manager _messageChanges; Manager _entryChanges; + Manager _storyChanges; bool _notify = false; diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index dbddd0da0..2ee3be031 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -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 photo) { + return !photo->isNull(); + }, [](not_null document) { + return document->hasThumbnail(); + }); +} + +Image *Story::replyPreview() const { + return v::match(_media.data, [&](not_null photo) { + return photo->getReplyPreview( + Data::FileOriginStory(_peer->id, _id), + _peer, + false); + }, [&](not_null 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 peer, const MTPDstoryItem &data) { return result; } +void Stories::updateDependentMessages(not_null 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 dependent, + not_null dependency) { + _dependentMessages[dependency].emplace(dependent); +} + +void Stories::unregisterDependentMessage( + not_null dependent, + not_null 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 ids; + std::vector> callbacks; + }; + auto leftToSend = kMaxResolveTogether; + auto byPeer = base::flat_map(); + 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(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 peer, + const QVector &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 &Stories::all() { return _all; } @@ -242,6 +436,21 @@ base::expected, NoStory> Stories::lookup( } void Stories::resolve(FullStoryId id, Fn 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) { diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index e5f3b49db..641e26edf 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -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 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 _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 story); + void registerDependentMessage( + not_null dependent, + not_null dependency); + void unregisterDependentMessage( + not_null dependent, + not_null dependency); void loadMore(); void apply(const MTPDupdateStories &data); @@ -101,9 +116,15 @@ private: [[nodiscard]] Story *parse( not_null peer, const MTPDstoryItem &data); + void processResolvedStories( + not_null peer, + const QVector &list); + void sendResolveRequests(); + void finalizeResolve(FullStoryId id); void pushToBack(StoriesList &&list); void pushToFront(StoriesList &&list); + void applyDeleted(FullStoryId id); const not_null _owner; base::flat_map< @@ -111,6 +132,15 @@ private: base::flat_map>> _stories; base::flat_set _deleted; + base::flat_map< + PeerId, + base::flat_map>>> _resolves; + base::flat_map>> _resolveRequests; + + std::map< + not_null, + base::flat_set>> _dependentMessages; + std::vector _all; rpl::event_stream<> _allChanged; QString _state; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index cab562735..c0e60d38b 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -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()) { @@ -3518,7 +3527,7 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) { dependent->topicPost = data.is_forum_topic() || Has(); if (!updateServiceDependent()) { - RequestDependentMessageData( + RequestDependentMessageItem( this, (dependent->peerId ? dependent->peerId diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 0cecbe2fe..4b5284555 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -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()) { + if (replyToMsg && !replyToMsg->Has()) { if (auto bot = replyToMsg->viaBot()) { replyToVia = std::make_unique(); 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 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 holder) const { - if (!replyToMsg) { + if (replyToStory) { + return replyToFromName(replyToStory->peer()); + } else if (!replyToMsg) { return QString(); } else if (holder->Has()) { if (const auto fwd = replyToMsg->Get()) { @@ -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; diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 0545a9760..32d1d43f9 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -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() = default; @@ -236,6 +275,7 @@ struct HistoryMessageReply [[nodiscard]] ClickHandlerPtr replyToLink() const { return replyToLnk; } + [[nodiscard]] QString statePhrase() const; void setReplyToLinkFrom(not_null holder); void refreshReplyToMedia(); @@ -249,6 +289,7 @@ struct HistoryMessageReply DocumentId replyToDocumentId = 0; WebPageId replyToWebPageId = 0; ReplyToMessagePointer replyToMsg; + ReplyToStoryPointer replyToStory; std::unique_ptr replyToVia; std::unique_ptr spoiler; ClickHandlerPtr replyToLnk; @@ -257,6 +298,7 @@ struct HistoryMessageReply mutable int maxReplyWidth = 0; int toWidth = 0; bool topicPost = false; + bool storyReply = false; }; diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index a7def067b..68eaa0d1e 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -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 item, PeerId peerId, MsgId msgId) { @@ -153,6 +154,23 @@ void RequestDependentMessageData( done); } +void RequestDependentMessageStory( + not_null 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 peer) { return MessageFlag::BeingSent | (peer->isSelf() ? MessageFlag() : MessageFlag::Outgoing); @@ -266,6 +284,24 @@ ClickHandlerPtr JumpToMessageClickHandler( }); } +ClickHandlerPtr JumpToStoryClickHandler(not_null story) { + return JumpToStoryClickHandler(story->peer(), story->id()); +} + +ClickHandlerPtr JumpToStoryClickHandler( + not_null peer, + StoryId storyId) { + return std::make_shared([=] { + 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, diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index 46b772517..f9f45d029 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -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 item, PeerId peerId, MsgId msgId); +void RequestDependentMessageStory( + not_null item, + PeerId peerId, + StoryId storyId); [[nodiscard]] MessageFlags NewMessageFlags(not_null peer); [[nodiscard]] bool ShouldSendSilent( not_null peer, @@ -115,6 +120,11 @@ struct SendingErrorRequest { [[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler( not_null item, FullMsgId returnToId = FullMsgId()); +[[nodiscard]] ClickHandlerPtr JumpToStoryClickHandler( + not_null story); +ClickHandlerPtr JumpToStoryClickHandler( + not_null peer, + StoryId storyId); [[nodiscard]] not_null GenerateJoinedMessage( not_null history, diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 5e0f36ddf..18f699b21 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -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; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 02b8282d4..bbd787d91 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -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); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index ebc4a0d47..6619e64a6 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -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 _powerSaveBlocker; + Main::Session *_session = nullptr; + rpl::lifetime _sessionLifetime; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 7ca01634e..81cb18923 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -2464,6 +2464,18 @@ Ui::ChatThemeBackgroundData SessionController::backgroundData( }; } +void SessionController::openPeerStory( + not_null 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()); } } diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 8f6ed90ec..07f763ca8 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -564,6 +564,7 @@ public: return _peerThemeOverride.value(); } + void openPeerStory(not_null peer, StoryId storyId); void openPeerStories(PeerId peerId); struct PaintContextArgs {