Display shared stories in message history.

This commit is contained in:
John Preston 2023-06-12 22:37:17 +04:00
parent d7186e68e2
commit c133f4de69
13 changed files with 229 additions and 43 deletions

View File

@ -1503,6 +1503,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_pinned_media_sticker" = "a sticker"; "lng_action_pinned_media_sticker" = "a sticker";
"lng_action_pinned_media_emoji_sticker" = "a {emoji} sticker"; "lng_action_pinned_media_emoji_sticker" = "a {emoji} sticker";
"lng_action_pinned_media_game" = "the game «{game}»"; "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#one" = "{from} scored {count} in {game}";
"lng_action_game_score#other" = "{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}"; "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_button" = "Archive";
"lng_stories_archive_title" = "Stories Archive"; "lng_stories_archive_title" = "Stories Archive";
"lng_stories_link_invalid" = "This link is broken or has expired.";
// Wnd specific // Wnd specific
"lng_wnd_choose_program_menu" = "Choose Default Program..."; "lng_wnd_choose_program_menu" = "Choose Default Program...";

View File

@ -56,6 +56,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_poll.h" #include "data/data_poll.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_stories.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_session_settings.h" #include "main/main_session_settings.h"
#include "core/application.h" #include "core/application.h"
@ -72,6 +73,7 @@ namespace {
constexpr auto kFastRevokeRestriction = 24 * 60 * TimeId(60); constexpr auto kFastRevokeRestriction = 24 * 60 * TimeId(60);
constexpr auto kMaxPreviewImages = 3; constexpr auto kMaxPreviewImages = 3;
constexpr auto kLoadingStoryPhotoId = PhotoId(0x7FFF'DEAD'FFFF'FFFFULL);
using ItemPreview = HistoryView::ItemPreview; using ItemPreview = HistoryView::ItemPreview;
using ItemPreviewImage = HistoryView::ItemPreviewImage; using ItemPreviewImage = HistoryView::ItemPreviewImage;
@ -404,6 +406,10 @@ const WallPaper *Media::paper() const {
return nullptr; return nullptr;
} }
FullStoryId Media::storyId() const {
return {};
}
bool Media::uploading() const { bool Media::uploading() const {
return false; return false;
} }
@ -1968,4 +1974,82 @@ std::unique_ptr<HistoryView::Media> MediaWallPaper::createView(
std::make_unique<HistoryView::ThemeDocumentBox>(message, _paper)); std::make_unique<HistoryView::ThemeDocumentBox>(message, _paper));
} }
MediaStory::MediaStory(not_null<HistoryItem*> 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<Media> MediaStory::clone(not_null<HistoryItem*> parent) {
return std::make_unique<MediaStory>(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<HistoryView::Media> MediaStory::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> 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<HistoryView::Photo>(
message,
realParent,
photo,
spoiler);
} else {
return std::make_unique<HistoryView::Gif>(
message,
realParent,
story->document(),
spoiler);
}
}
return std::make_unique<HistoryView::Photo>(
message,
realParent,
realParent->history()->owner().photo(kLoadingStoryPhotoId),
spoiler);
}
} // namespace Data } // namespace Data

View File

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "base/weak_ptr.h"
#include "data/data_location.h" #include "data/data_location.h"
#include "data/data_wall_paper.h" #include "data/data_wall_paper.h"
@ -110,6 +111,7 @@ public:
virtual CloudImage *location() const; virtual CloudImage *location() const;
virtual PollData *poll() const; virtual PollData *poll() const;
virtual const WallPaper *paper() const; virtual const WallPaper *paper() const;
virtual FullStoryId storyId() const;
virtual bool uploading() const; virtual bool uploading() const;
virtual Storage::SharedMediaTypesMask sharedMediaTypes() 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<HistoryItem*> parent, FullStoryId storyId);
std::unique_ptr<Media> clone(not_null<HistoryItem*> 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<HistoryView::Media> createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing = nullptr) override;
private:
const FullStoryId _storyId;
};
[[nodiscard]] TextForMimeData WithCaptionClipboardText( [[nodiscard]] TextForMimeData WithCaptionClipboardText(
const QString &attachType, const QString &attachType,
TextForMimeData &&caption); TextForMimeData &&caption);

View File

@ -291,7 +291,10 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
qs(media.vemoticon()), qs(media.vemoticon()),
media.vvalue().v); media.vvalue().v);
}, [&](const MTPDmessageMediaStory &media) -> Result { }, [&](const MTPDmessageMediaStory &media) -> Result {
return nullptr; // #TODO stories return std::make_unique<Data::MediaStory>(item, FullStoryId{
peerFromUser(media.vuser_id()),
media.vid().v,
});
}, [](const MTPDmessageMediaEmpty &) -> Result { }, [](const MTPDmessageMediaEmpty &) -> Result {
return nullptr; return nullptr;
}, [](const MTPDmessageMediaUnsupported &) -> Result { }, [](const MTPDmessageMediaUnsupported &) -> Result {
@ -3594,9 +3597,29 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) {
void HistoryItem::setMedia(const MTPMessageMedia &media) { void HistoryItem::setMedia(const MTPMessageMedia &media) {
_media = CreateMedia(this, media); _media = CreateMedia(this, media);
checkStoryForwardInfo();
checkBuyButton(); checkBuyButton();
} }
void HistoryItem::checkStoryForwardInfo() {
if (const auto storyId = _media ? _media->storyId() : FullStoryId()) {
const auto adding = !Has<HistoryMessageForwarded>();
if (adding) {
AddComponents(HistoryMessageForwarded::Bit());
}
const auto forwarded = Get<HistoryMessageForwarded>();
if (forwarded->story || adding) {
const auto peer = history()->owner().peer(storyId.peer);
forwarded->story = true;
forwarded->originalSender = peer;
}
} else if (const auto forwarded = Get<HistoryMessageForwarded>()) {
if (forwarded->story) {
RemoveComponents(HistoryMessageForwarded::Bit());
}
}
}
void HistoryItem::applyServiceDateEdition(const MTPDmessageService &data) { void HistoryItem::applyServiceDateEdition(const MTPDmessageService &data) {
const auto date = data.vdate().v; const auto date = data.vdate().v;
if (_date == date) { if (_date == date) {

View File

@ -192,6 +192,7 @@ public:
[[nodiscard]] MsgId dependencyMsgId() const; [[nodiscard]] MsgId dependencyMsgId() const;
[[nodiscard]] bool notificationReady() const; [[nodiscard]] bool notificationReady() const;
[[nodiscard]] PeerData *specialNotificationPeer() const; [[nodiscard]] PeerData *specialNotificationPeer() const;
void checkStoryForwardInfo();
void checkBuyButton(); void checkBuyButton();
void updateServiceText(PreparedServiceText &&text); void updateServiceText(PreparedServiceText &&text);

View File

@ -129,6 +129,7 @@ struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded
PeerData *savedFromPeer = nullptr; PeerData *savedFromPeer = nullptr;
MsgId savedFromMsgId = 0; MsgId savedFromMsgId = 0;
bool imported = false; bool imported = false;
bool story = false;
}; };
struct HistoryMessageSponsored : public RuntimeComponent<HistoryMessageSponsored, HistoryItem> { struct HistoryMessageSponsored : public RuntimeComponent<HistoryMessageSponsored, HistoryItem> {

View File

@ -248,6 +248,7 @@ TextSelection ShiftItemSelection(
QString DateTooltipText(not_null<Element*> view) { QString DateTooltipText(not_null<Element*> view) {
const auto locale = QLocale(); const auto locale = QLocale();
const auto format = QLocale::LongFormat; const auto format = QLocale::LongFormat;
const auto item = view->data();
auto dateText = locale.toString(view->dateTime(), format); auto dateText = locale.toString(view->dateTime(), format);
if (const auto editedDate = view->displayedEditDate()) { if (const auto editedDate = view->displayedEditDate()) {
dateText += '\n' + tr::lng_edited_date( dateText += '\n' + tr::lng_edited_date(
@ -255,18 +256,22 @@ QString DateTooltipText(not_null<Element*> view) {
lt_date, lt_date,
locale.toString(base::unixtime::parse(editedDate), format)); locale.toString(base::unixtime::parse(editedDate), format));
} }
if (const auto forwarded = view->data()->Get<HistoryMessageForwarded>()) { if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
dateText += '\n' + tr::lng_forwarded_date( if (!forwarded->story && forwarded->psaType.isEmpty()) {
tr::now, dateText += '\n' + tr::lng_forwarded_date(
lt_date, tr::now,
locale.toString(base::unixtime::parse(forwarded->originalDate), format)); lt_date,
if (forwarded->imported) { locale.toString(
dateText = tr::lng_forwarded_imported(tr::now) base::unixtime::parse(forwarded->originalDate),
+ "\n\n" + dateText; format));
if (forwarded->imported) {
dateText = tr::lng_forwarded_imported(tr::now)
+ "\n\n" + dateText;
}
} }
} }
if (view->isSignedAuthorElided()) { if (view->isSignedAuthorElided()) {
if (const auto msgsigned = view->data()->Get<HistoryMessageSigned>()) { if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
dateText += '\n' dateText += '\n'
+ tr::lng_signed_author(tr::now, lt_user, msgsigned->author); + tr::lng_signed_author(tr::now, lt_user, msgsigned->author);
} }

View File

@ -90,6 +90,11 @@ Gif::Gif(
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right())
, _spoiler(spoiler ? std::make_unique<MediaSpoiler>() : nullptr) , _spoiler(spoiler ? std::make_unique<MediaSpoiler>() : nullptr)
, _downloadSize(Ui::FormatSizeText(_data->size)) { , _downloadSize(Ui::FormatSizeText(_data->size)) {
if (const auto media = realParent->media()) {
if (media->storyId()) {
_story = true;
}
}
setDocumentLinks(_data, realParent, [=] { setDocumentLinks(_data, realParent, [=] {
if (!_data->createMediaView()->canBePlayed(realParent) if (!_data->createMediaView()->canBePlayed(realParent)
|| !_data->isAnimation() || !_data->isAnimation()
@ -1441,7 +1446,9 @@ void Gif::hideSpoilers() {
} }
bool Gif::needsBubble() const { bool Gif::needsBubble() const {
if (_data->isVideoMessage()) { if (_story) {
return true;
} else if (_data->isVideoMessage()) {
return false; return false;
} else if (!_caption.isEmpty()) { } else if (!_caption.isEmpty()) {
return true; return true;

View File

@ -219,8 +219,9 @@ private:
mutable QImage _thumbCache; mutable QImage _thumbCache;
mutable QImage _roundingMask; mutable QImage _roundingMask;
mutable std::optional<Ui::BubbleRounding> _thumbCacheRounding; mutable std::optional<Ui::BubbleRounding> _thumbCacheRounding;
mutable bool _thumbCacheBlurred = false; mutable bool _thumbCacheBlurred : 1 = false;
mutable bool _thumbIsEllipse = false; mutable bool _thumbIsEllipse : 1 = false;
mutable bool _story : 1 = false;
}; };

View File

@ -40,6 +40,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView { namespace HistoryView {
namespace { namespace {
constexpr auto kStoryWidth = 720;
constexpr auto kStoryHeight = 1280;
using Data::PhotoSize; using Data::PhotoSize;
} // namespace } // namespace
@ -67,6 +70,11 @@ Photo::Photo(
, _data(photo) , _data(photo)
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right())
, _spoiler(spoiler ? std::make_unique<MediaSpoiler>() : nullptr) { , _spoiler(spoiler ? std::make_unique<MediaSpoiler>() : nullptr) {
if (const auto media = realParent->media()) {
if (media->storyId()) {
_story = true;
}
}
_caption = createCaption(realParent); _caption = createCaption(realParent);
create(realParent->fullId()); create(realParent->fullId());
} }
@ -167,7 +175,7 @@ QSize Photo::countOptimalSize() {
_parent->skipBlockHeight()); _parent->skipBlockHeight());
} }
const auto dimensions = QSize(_data->width(), _data->height()); const auto dimensions = photoSize();
const auto scaled = CountDesiredMediaSize(dimensions); const auto scaled = CountDesiredMediaSize(dimensions);
const auto minWidth = std::clamp( const auto minWidth = std::clamp(
_parent->minWidthForMedia(), _parent->minWidthForMedia(),
@ -210,7 +218,7 @@ QSize Photo::countCurrentSize(int newWidth) {
? st::historyPhotoBubbleMinWidth ? st::historyPhotoBubbleMinWidth
: st::minPhotoSize), : st::minPhotoSize),
thumbMaxWidth); thumbMaxWidth);
const auto dimensions = QSize(_data->width(), _data->height()); const auto dimensions = photoSize();
auto pix = CountPhotoMediaSize( auto pix = CountPhotoMediaSize(
CountDesiredMediaSize(dimensions), CountDesiredMediaSize(dimensions),
newWidth, newWidth,
@ -255,7 +263,11 @@ int Photo::adjustHeightForLessCrop(QSize dimensions, QSize current) const {
} }
void Photo::draw(Painter &p, const PaintContext &context) 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(); ensureDataMediaCreated();
_dataMedia->automaticLoad(_realParent->fullId(), _parent->data()); _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 { TextState Photo::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent); auto result = TextState(_parent);
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
return result; return result;
} else if (_story && _data->isNull()) {
return result;
} }
auto paintx = 0, painty = 0, paintw = width(), painth = height(); auto paintx = 0, painty = 0, paintw = width(), painth = height();
auto bubble = _parent->hasBubble(); auto bubble = _parent->hasBubble();
@ -657,9 +678,8 @@ TextState Photo::textState(QPoint point, StateRequest request) const {
} }
QSize Photo::sizeForGroupingOptimal(int maxWidth) const { QSize Photo::sizeForGroupingOptimal(int maxWidth) const {
const auto width = _data->width(); const auto size = photoSize();
const auto height = _data->height(); return { std::max(size.width(), 1), std::max(size.height(), 1)};
return { std::max(width, 1), std::max(height, 1) };
} }
QSize Photo::sizeForGrouping(int width) const { QSize Photo::sizeForGrouping(int width) const {
@ -848,8 +868,9 @@ void Photo::validateGroupedCache(
return; return;
} }
const auto originalWidth = style::ConvertScale(_data->width()); const auto unscaled = photoSize();
const auto originalHeight = style::ConvertScale(_data->height()); const auto originalWidth = style::ConvertScale(unscaled.width());
const auto originalHeight = style::ConvertScale(unscaled.height());
const auto pixSize = Ui::GetImageScaleSizeForGeometry( const auto pixSize = Ui::GetImageScaleSizeForGeometry(
{ originalWidth, originalHeight }, { originalWidth, originalHeight },
{ width, height }); { width, height });
@ -1012,7 +1033,7 @@ void Photo::hideSpoilers() {
} }
bool Photo::needsBubble() const { bool Photo::needsBubble() const {
if (!_caption.isEmpty()) { if (_story || !_caption.isEmpty()) {
return true; return true;
} }
const auto item = _parent->data(); const auto item = _parent->data();

View File

@ -158,6 +158,8 @@ private:
const PaintContext &context, const PaintContext &context,
QPoint photoPosition) const; QPoint photoPosition) const;
[[nodiscard]] QSize photoSize() const;
const not_null<PhotoData*> _data; const not_null<PhotoData*> _data;
Ui::Text::String _caption; Ui::Text::String _caption;
mutable std::shared_ptr<Data::PhotoMedia> _dataMedia; mutable std::shared_ptr<Data::PhotoMedia> _dataMedia;
@ -165,9 +167,10 @@ private:
const std::unique_ptr<MediaSpoiler> _spoiler; const std::unique_ptr<MediaSpoiler> _spoiler;
mutable QImage _imageCache; mutable QImage _imageCache;
mutable std::optional<Ui::BubbleRounding> _imageCacheRounding; mutable std::optional<Ui::BubbleRounding> _imageCacheRounding;
int _serviceWidth : 30 = 0; int _serviceWidth : 29 = 0;
mutable int _imageCacheForum : 1 = 0; mutable int _imageCacheForum : 1 = 0;
mutable int _imageCacheBlurred : 1 = 0; mutable int _imageCacheBlurred : 1 = 0;
mutable int _story : 1 = 0;
}; };

View File

@ -512,7 +512,7 @@ void SessionNavigation::showPeerByLinkResolved(
storyId.story, storyId.story,
Data::StoriesContext{ Data::StoriesContextSingle() }); Data::StoriesContext{ Data::StoriesContextSingle() });
} else { } else {
showToast(tr::lng_confirm_phone_link_invalid(tr::now)); showToast(tr::lng_stories_link_invalid(tr::now));
} }
})); }));
} else if (bot && resolveType == ResolveType::BotApp) { } else if (bot && resolveType == ResolveType::BotApp) {
@ -2155,14 +2155,12 @@ void SessionController::openPhoto(
not_null<PhotoData*> photo, not_null<PhotoData*> photo,
FullMsgId contextId, FullMsgId contextId,
MsgId topicRootId) { MsgId topicRootId) {
if (openStory(contextId)) { const auto item = session().data().message(contextId);
if (openSharedStory(item) || openFakeItemStory(contextId)) {
return; return;
} }
_window->openInMediaView(Media::View::OpenRequest( _window->openInMediaView(
this, Media::View::OpenRequest(this, photo, item, topicRootId));
photo,
session().data().message(contextId),
topicRootId));
} }
void SessionController::openPhoto( void SessionController::openPhoto(
@ -2176,24 +2174,34 @@ void SessionController::openDocument(
FullMsgId contextId, FullMsgId contextId,
MsgId topicRootId, MsgId topicRootId,
bool showInMediaView) { bool showInMediaView) {
if (openStory(contextId)) { const auto item = session().data().message(contextId);
if (openSharedStory(item) || openFakeItemStory(contextId)) {
return; return;
} else if (showInMediaView) { } else if (showInMediaView) {
_window->openInMediaView(Media::View::OpenRequest( _window->openInMediaView(
this, Media::View::OpenRequest(this, document, item, topicRootId));
document,
session().data().message(contextId),
topicRootId));
return; return;
} }
Data::ResolveDocument( Data::ResolveDocument(this, document, item, topicRootId);
this,
document,
session().data().message(contextId),
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, FullMsgId fakeItemId,
bool forceArchiveContext) { bool forceArchiveContext) {
if (!peerIsUser(fakeItemId.peer) if (!peerIsUser(fakeItemId.peer)

View File

@ -492,7 +492,10 @@ public:
FullMsgId contextId, FullMsgId contextId,
MsgId topicRootId, MsgId topicRootId,
bool showInMediaView = false); bool showInMediaView = false);
bool openStory(FullMsgId fakeItemId, bool forceArchiveContext = false); bool openSharedStory(HistoryItem *item);
bool openFakeItemStory(
FullMsgId fakeItemId,
bool forceArchiveContext = false);
void showChooseReportMessages( void showChooseReportMessages(
not_null<PeerData*> peer, not_null<PeerData*> peer,