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_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...";

View File

@ -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<HistoryView::Media> MediaWallPaper::createView(
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

View File

@ -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<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(
const QString &attachType,
TextForMimeData &&caption);

View File

@ -291,7 +291,10 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
qs(media.vemoticon()),
media.vvalue().v);
}, [&](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 {
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<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) {
const auto date = data.vdate().v;
if (_date == date) {

View File

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

View File

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

View File

@ -248,6 +248,7 @@ TextSelection ShiftItemSelection(
QString DateTooltipText(not_null<Element*> 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<Element*> view) {
lt_date,
locale.toString(base::unixtime::parse(editedDate), format));
}
if (const auto forwarded = view->data()->Get<HistoryMessageForwarded>()) {
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<HistoryMessageForwarded>()) {
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<HistoryMessageSigned>()) {
if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
dateText += '\n'
+ 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())
, _spoiler(spoiler ? std::make_unique<MediaSpoiler>() : 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;

View File

@ -219,8 +219,9 @@ private:
mutable QImage _thumbCache;
mutable QImage _roundingMask;
mutable std::optional<Ui::BubbleRounding> _thumbCacheRounding;
mutable bool _thumbCacheBlurred = false;
mutable bool _thumbIsEllipse = false;
mutable bool _thumbCacheBlurred : 1 = 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 {
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<MediaSpoiler>() : 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();

View File

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

View File

@ -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<PhotoData*> 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)

View File

@ -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<PeerData*> peer,