diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 48c5590ad..fdd35ffdd 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1503,6 +1503,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_pinned_media_sticker" = "a sticker"; "lng_action_pinned_media_emoji_sticker" = "a {emoji} sticker"; "lng_action_pinned_media_game" = "the game «{game}»"; +"lng_action_pinned_media_story" = "a story"; "lng_action_game_score#one" = "{from} scored {count} in {game}"; "lng_action_game_score#other" = "{from} scored {count} in {game}"; "lng_action_game_you_scored#one" = "You scored {count} in {game}"; @@ -3810,6 +3811,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stories_archive_button" = "Archive"; "lng_stories_archive_title" = "Stories Archive"; +"lng_stories_link_invalid" = "This link is broken or has expired."; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 4e311e1f1..5a13cf2d5 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -56,6 +56,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_poll.h" #include "data/data_channel.h" #include "data/data_file_origin.h" +#include "data/data_stories.h" #include "main/main_session.h" #include "main/main_session_settings.h" #include "core/application.h" @@ -72,6 +73,7 @@ namespace { constexpr auto kFastRevokeRestriction = 24 * 60 * TimeId(60); constexpr auto kMaxPreviewImages = 3; +constexpr auto kLoadingStoryPhotoId = PhotoId(0x7FFF'DEAD'FFFF'FFFFULL); using ItemPreview = HistoryView::ItemPreview; using ItemPreviewImage = HistoryView::ItemPreviewImage; @@ -404,6 +406,10 @@ const WallPaper *Media::paper() const { return nullptr; } +FullStoryId Media::storyId() const { + return {}; +} + bool Media::uploading() const { return false; } @@ -1968,4 +1974,82 @@ std::unique_ptr MediaWallPaper::createView( std::make_unique(message, _paper)); } +MediaStory::MediaStory(not_null parent, FullStoryId storyId) +: Media(parent) +, _storyId(storyId) { + const auto stories = &parent->history()->owner().stories(); + if (!stories->lookup(storyId)) { + stories->resolve(storyId, crl::guard(this, [=] { + if (stories->lookup(storyId)) { + parent->history()->owner().requestItemViewRefresh(parent); + } + })); + } +} + +std::unique_ptr MediaStory::clone(not_null parent) { + return std::make_unique(parent, _storyId); +} + +FullStoryId MediaStory::storyId() const { + return _storyId; +} + +TextWithEntities MediaStory::notificationText() const { + const auto stories = &parent()->history()->owner().stories(); + const auto maybeStory = stories->lookup(_storyId); + return WithCaptionNotificationText( + tr::lng_in_dlg_story(tr::now), + (maybeStory + ? (*maybeStory)->caption() + : TextWithEntities())); +} + +QString MediaStory::pinnedTextSubstring() const { + return tr::lng_action_pinned_media_story(tr::now); +} + +TextForMimeData MediaStory::clipboardText() const { + return WithCaptionClipboardText( + tr::lng_in_dlg_story(tr::now), + parent()->clipboardText()); +} + +bool MediaStory::updateInlineResultMedia(const MTPMessageMedia &media) { + return false; +} + +bool MediaStory::updateSentMedia(const MTPMessageMedia &media) { + return false; +} + +std::unique_ptr MediaStory::createView( + not_null message, + not_null realParent, + HistoryView::Element *replacing) { + const auto spoiler = false; + const auto stories = &parent()->history()->owner().stories(); + const auto maybeStory = stories->lookup(_storyId); + if (const auto story = maybeStory ? maybeStory->get() : nullptr) { + if (const auto photo = story->photo()) { + return std::make_unique( + message, + realParent, + photo, + spoiler); + } else { + return std::make_unique( + message, + realParent, + story->document(), + spoiler); + } + } + return std::make_unique( + message, + realParent, + realParent->history()->owner().photo(kLoadingStoryPhotoId), + spoiler); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 985e2c8aa..614d0333a 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/weak_ptr.h" #include "data/data_location.h" #include "data/data_wall_paper.h" @@ -110,6 +111,7 @@ public: virtual CloudImage *location() const; virtual PollData *poll() const; virtual const WallPaper *paper() const; + virtual FullStoryId storyId() const; virtual bool uploading() const; virtual Storage::SharedMediaTypesMask sharedMediaTypes() const; @@ -563,6 +565,30 @@ private: }; +class MediaStory final : public Media, public base::has_weak_ptr { +public: + MediaStory(not_null parent, FullStoryId storyId); + + std::unique_ptr clone(not_null parent) override; + + [[nodiscard]] FullStoryId storyId() const override; + + TextWithEntities notificationText() const override; + QString pinnedTextSubstring() const override; + TextForMimeData clipboardText() const override; + + bool updateInlineResultMedia(const MTPMessageMedia &media) override; + bool updateSentMedia(const MTPMessageMedia &media) override; + std::unique_ptr createView( + not_null message, + not_null realParent, + HistoryView::Element *replacing = nullptr) override; + +private: + const FullStoryId _storyId; + +}; + [[nodiscard]] TextForMimeData WithCaptionClipboardText( const QString &attachType, TextForMimeData &&caption); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index e886b7ed7..2e73286a8 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -291,7 +291,10 @@ std::unique_ptr HistoryItem::CreateMedia( qs(media.vemoticon()), media.vvalue().v); }, [&](const MTPDmessageMediaStory &media) -> Result { - return nullptr; // #TODO stories + return std::make_unique(item, FullStoryId{ + peerFromUser(media.vuser_id()), + media.vid().v, + }); }, [](const MTPDmessageMediaEmpty &) -> Result { return nullptr; }, [](const MTPDmessageMediaUnsupported &) -> Result { @@ -3594,9 +3597,29 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) { void HistoryItem::setMedia(const MTPMessageMedia &media) { _media = CreateMedia(this, media); + checkStoryForwardInfo(); checkBuyButton(); } +void HistoryItem::checkStoryForwardInfo() { + if (const auto storyId = _media ? _media->storyId() : FullStoryId()) { + const auto adding = !Has(); + if (adding) { + AddComponents(HistoryMessageForwarded::Bit()); + } + const auto forwarded = Get(); + if (forwarded->story || adding) { + const auto peer = history()->owner().peer(storyId.peer); + forwarded->story = true; + forwarded->originalSender = peer; + } + } else if (const auto forwarded = Get()) { + if (forwarded->story) { + RemoveComponents(HistoryMessageForwarded::Bit()); + } + } +} + void HistoryItem::applyServiceDateEdition(const MTPDmessageService &data) { const auto date = data.vdate().v; if (_date == date) { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index c2f26fb44..a5bbb3ced 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -192,6 +192,7 @@ public: [[nodiscard]] MsgId dependencyMsgId() const; [[nodiscard]] bool notificationReady() const; [[nodiscard]] PeerData *specialNotificationPeer() const; + void checkStoryForwardInfo(); void checkBuyButton(); void updateServiceText(PreparedServiceText &&text); diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 99100f6b0..57d38514e 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -129,6 +129,7 @@ struct HistoryMessageForwarded : public RuntimeComponent { diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index d6e7c4099..4d734bfcb 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -248,6 +248,7 @@ TextSelection ShiftItemSelection( QString DateTooltipText(not_null view) { const auto locale = QLocale(); const auto format = QLocale::LongFormat; + const auto item = view->data(); auto dateText = locale.toString(view->dateTime(), format); if (const auto editedDate = view->displayedEditDate()) { dateText += '\n' + tr::lng_edited_date( @@ -255,18 +256,22 @@ QString DateTooltipText(not_null view) { lt_date, locale.toString(base::unixtime::parse(editedDate), format)); } - if (const auto forwarded = view->data()->Get()) { - dateText += '\n' + tr::lng_forwarded_date( - tr::now, - lt_date, - locale.toString(base::unixtime::parse(forwarded->originalDate), format)); - if (forwarded->imported) { - dateText = tr::lng_forwarded_imported(tr::now) - + "\n\n" + dateText; + if (const auto forwarded = item->Get()) { + if (!forwarded->story && forwarded->psaType.isEmpty()) { + dateText += '\n' + tr::lng_forwarded_date( + tr::now, + lt_date, + locale.toString( + base::unixtime::parse(forwarded->originalDate), + format)); + if (forwarded->imported) { + dateText = tr::lng_forwarded_imported(tr::now) + + "\n\n" + dateText; + } } } if (view->isSignedAuthorElided()) { - if (const auto msgsigned = view->data()->Get()) { + if (const auto msgsigned = item->Get()) { dateText += '\n' + tr::lng_signed_author(tr::now, lt_user, msgsigned->author); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 67e4c8f3f..e1c78f8b5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -90,6 +90,11 @@ Gif::Gif( , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) , _spoiler(spoiler ? std::make_unique() : nullptr) , _downloadSize(Ui::FormatSizeText(_data->size)) { + if (const auto media = realParent->media()) { + if (media->storyId()) { + _story = true; + } + } setDocumentLinks(_data, realParent, [=] { if (!_data->createMediaView()->canBePlayed(realParent) || !_data->isAnimation() @@ -1441,7 +1446,9 @@ void Gif::hideSpoilers() { } bool Gif::needsBubble() const { - if (_data->isVideoMessage()) { + if (_story) { + return true; + } else if (_data->isVideoMessage()) { return false; } else if (!_caption.isEmpty()) { return true; diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index 7900f9e46..6a8fca3b5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -219,8 +219,9 @@ private: mutable QImage _thumbCache; mutable QImage _roundingMask; mutable std::optional _thumbCacheRounding; - mutable bool _thumbCacheBlurred = false; - mutable bool _thumbIsEllipse = false; + mutable bool _thumbCacheBlurred : 1 = false; + mutable bool _thumbIsEllipse : 1 = false; + mutable bool _story : 1 = false; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 78c4be6f3..efa945cc4 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -40,6 +40,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { namespace { +constexpr auto kStoryWidth = 720; +constexpr auto kStoryHeight = 1280; + using Data::PhotoSize; } // namespace @@ -67,6 +70,11 @@ Photo::Photo( , _data(photo) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) , _spoiler(spoiler ? std::make_unique() : nullptr) { + if (const auto media = realParent->media()) { + if (media->storyId()) { + _story = true; + } + } _caption = createCaption(realParent); create(realParent->fullId()); } @@ -167,7 +175,7 @@ QSize Photo::countOptimalSize() { _parent->skipBlockHeight()); } - const auto dimensions = QSize(_data->width(), _data->height()); + const auto dimensions = photoSize(); const auto scaled = CountDesiredMediaSize(dimensions); const auto minWidth = std::clamp( _parent->minWidthForMedia(), @@ -210,7 +218,7 @@ QSize Photo::countCurrentSize(int newWidth) { ? st::historyPhotoBubbleMinWidth : st::minPhotoSize), thumbMaxWidth); - const auto dimensions = QSize(_data->width(), _data->height()); + const auto dimensions = photoSize(); auto pix = CountPhotoMediaSize( CountDesiredMediaSize(dimensions), newWidth, @@ -255,7 +263,11 @@ int Photo::adjustHeightForLessCrop(QSize dimensions, QSize current) const { } void Photo::draw(Painter &p, const PaintContext &context) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + return; + } else if (_story && _data->isNull()) { + return; + } ensureDataMediaCreated(); _dataMedia->automaticLoad(_realParent->fullId(), _parent->data()); @@ -590,11 +602,20 @@ void Photo::paintUserpicFrame( } } +QSize Photo::photoSize() const { + if (_story) { + return { kStoryWidth, kStoryHeight }; + } + return QSize(_data->width(), _data->height()); +} + TextState Photo::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { return result; + } else if (_story && _data->isNull()) { + return result; } auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto bubble = _parent->hasBubble(); @@ -657,9 +678,8 @@ TextState Photo::textState(QPoint point, StateRequest request) const { } QSize Photo::sizeForGroupingOptimal(int maxWidth) const { - const auto width = _data->width(); - const auto height = _data->height(); - return { std::max(width, 1), std::max(height, 1) }; + const auto size = photoSize(); + return { std::max(size.width(), 1), std::max(size.height(), 1)}; } QSize Photo::sizeForGrouping(int width) const { @@ -848,8 +868,9 @@ void Photo::validateGroupedCache( return; } - const auto originalWidth = style::ConvertScale(_data->width()); - const auto originalHeight = style::ConvertScale(_data->height()); + const auto unscaled = photoSize(); + const auto originalWidth = style::ConvertScale(unscaled.width()); + const auto originalHeight = style::ConvertScale(unscaled.height()); const auto pixSize = Ui::GetImageScaleSizeForGeometry( { originalWidth, originalHeight }, { width, height }); @@ -1012,7 +1033,7 @@ void Photo::hideSpoilers() { } bool Photo::needsBubble() const { - if (!_caption.isEmpty()) { + if (_story || !_caption.isEmpty()) { return true; } const auto item = _parent->data(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index 9440d1421..31d2b08bc 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -158,6 +158,8 @@ private: const PaintContext &context, QPoint photoPosition) const; + [[nodiscard]] QSize photoSize() const; + const not_null _data; Ui::Text::String _caption; mutable std::shared_ptr _dataMedia; @@ -165,9 +167,10 @@ private: const std::unique_ptr _spoiler; mutable QImage _imageCache; mutable std::optional _imageCacheRounding; - int _serviceWidth : 30 = 0; + int _serviceWidth : 29 = 0; mutable int _imageCacheForum : 1 = 0; mutable int _imageCacheBlurred : 1 = 0; + mutable int _story : 1 = 0; }; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 0e3277348..2d447621c 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -512,7 +512,7 @@ void SessionNavigation::showPeerByLinkResolved( storyId.story, Data::StoriesContext{ Data::StoriesContextSingle() }); } else { - showToast(tr::lng_confirm_phone_link_invalid(tr::now)); + showToast(tr::lng_stories_link_invalid(tr::now)); } })); } else if (bot && resolveType == ResolveType::BotApp) { @@ -2155,14 +2155,12 @@ void SessionController::openPhoto( not_null photo, FullMsgId contextId, MsgId topicRootId) { - if (openStory(contextId)) { + const auto item = session().data().message(contextId); + if (openSharedStory(item) || openFakeItemStory(contextId)) { return; } - _window->openInMediaView(Media::View::OpenRequest( - this, - photo, - session().data().message(contextId), - topicRootId)); + _window->openInMediaView( + Media::View::OpenRequest(this, photo, item, topicRootId)); } void SessionController::openPhoto( @@ -2176,24 +2174,34 @@ void SessionController::openDocument( FullMsgId contextId, MsgId topicRootId, bool showInMediaView) { - if (openStory(contextId)) { + const auto item = session().data().message(contextId); + if (openSharedStory(item) || openFakeItemStory(contextId)) { return; } else if (showInMediaView) { - _window->openInMediaView(Media::View::OpenRequest( - this, - document, - session().data().message(contextId), - topicRootId)); + _window->openInMediaView( + Media::View::OpenRequest(this, document, item, topicRootId)); return; } - Data::ResolveDocument( - this, - document, - session().data().message(contextId), - topicRootId); + Data::ResolveDocument(this, document, item, topicRootId); } -bool SessionController::openStory( +bool SessionController::openSharedStory(HistoryItem *item) { + if (const auto media = item ? item->media() : nullptr) { + if (const auto storyId = media->storyId()) { + const auto story = session().data().stories().lookup(storyId); + if (story) { + _window->openInMediaView(::Media::View::OpenRequest( + this, + *story, + Data::StoriesContext{ Data::StoriesContextSingle() })); + } + return true; + } + } + return false; +} + +bool SessionController::openFakeItemStory( FullMsgId fakeItemId, bool forceArchiveContext) { if (!peerIsUser(fakeItemId.peer) diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 1fb50ac2a..845cdb223 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -492,7 +492,10 @@ public: FullMsgId contextId, MsgId topicRootId, bool showInMediaView = false); - bool openStory(FullMsgId fakeItemId, bool forceArchiveContext = false); + bool openSharedStory(HistoryItem *item); + bool openFakeItemStory( + FullMsgId fakeItemId, + bool forceArchiveContext = false); void showChooseReportMessages( not_null peer,