/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "iv/iv_cached_media.h" #include "base/algorithm.h" #include "base/flat_map.h" #include "base/weak_ptr.h" #include "core/application.h" #include "data/data_channel.h" #include "data/data_cloud_file.h" #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_file_origin.h" #include "data/data_location.h" #include "data/data_media_types.h" #include "data/data_peer.h" #include "data/data_photo_media.h" #include "data/data_session.h" #include "data/data_web_page.h" #include "history/history.h" #include "history/history_item.h" #include "history/view/history_view_element.h" #include "history/view/media/history_view_location.h" #include "history/view/media/history_view_photo.h" #include "info/profile/info_profile_values.h" #include "iv/markdown/iv_markdown_common.h" #include "iv/markdown/iv_markdown_history_view_media.h" #include "iv/markdown/iv_markdown_prepare.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "media/view/media_view_open_common.h" #include "storage/file_download.h" #include "ui/dynamic_image.h" #include "ui/dynamic_thumbnails.h" #include "ui/image/image.h" #include "ui/painter.h" #include "window/window_controller.h" #include "window/window_session_controller.h" #include "styles/palette.h" #include "styles/style_chat.h" #include #include #include namespace Iv { namespace { constexpr auto kGeoPointScale = 1; constexpr auto kGeoPointZoomMin = 13; enum class CachedPagePhotoImageKind { Thumbnail, Full, }; [[nodiscard]] QString SerializeNativeIvChannelContext( uint64 channelId, QString username) { auto result = QString::number(channelId); if (!username.isEmpty()) { result += u"\n"_q + username; } return result; } [[nodiscard]] Window::SessionController *CurrentSessionController( not_null session) { if (const auto window = Core::App().activeWindow()) { if (const auto current = window->sessionController(); current && (¤t->session() == session)) { return current; } } return nullptr; } class CachedPagePhotoDynamicImage final : public Ui::DynamicImage { public: CachedPagePhotoDynamicImage( std::shared_ptr<::Data::PhotoMedia> media, not_null photo, ::Data::FileOrigin origin, CachedPagePhotoImageKind kind, QSize requestedSize); [[nodiscard]] std::shared_ptr clone() override; [[nodiscard]] QImage image(int size) override; void subscribeToUpdates(Fn callback) override; private: void ensureWanted(); [[nodiscard]] Image *resolvedImage() const; [[nodiscard]] QImage prepareImage(QImage image, int size) const; [[nodiscard]] QSize requestedSize(int size) const; const std::shared_ptr<::Data::PhotoMedia> _media; const not_null _photo; const ::Data::FileOrigin _origin; const CachedPagePhotoImageKind _kind; const QSize _requestedSize; mutable QImage _cached; rpl::lifetime _subscription; }; CachedPagePhotoDynamicImage::CachedPagePhotoDynamicImage( std::shared_ptr<::Data::PhotoMedia> media, not_null photo, ::Data::FileOrigin origin, CachedPagePhotoImageKind kind, QSize requestedSize) : _media(std::move(media)) , _photo(photo) , _origin(std::move(origin)) , _kind(kind) , _requestedSize(requestedSize) { } std::shared_ptr CachedPagePhotoDynamicImage::clone() { return std::make_shared( _media, _photo, _origin, _kind, _requestedSize); } QImage CachedPagePhotoDynamicImage::image(int size) { ensureWanted(); if (const auto image = resolvedImage()) { return prepareImage(image->original(), size); } return QImage(); } void CachedPagePhotoDynamicImage::subscribeToUpdates(Fn callback) { if (!callback) { _subscription = {}; return; } _subscription = _photo->owner().photoLoadProgress( ) | rpl::filter([photo = _photo](not_null updated) { return (updated == photo); }) | rpl::on_next([=, callback = std::move(callback)] { _cached = QImage(); callback(); }); } void CachedPagePhotoDynamicImage::ensureWanted() { switch (_kind) { case CachedPagePhotoImageKind::Thumbnail: _media->wanted(::Data::PhotoSize::Small, _origin); break; case CachedPagePhotoImageKind::Full: _media->wanted(::Data::PhotoSize::Large, _origin); break; } } Image *CachedPagePhotoDynamicImage::resolvedImage() const { switch (_kind) { case CachedPagePhotoImageKind::Full: if (const auto large = _media->image(::Data::PhotoSize::Large)) { return large; } [[fallthrough]]; case CachedPagePhotoImageKind::Thumbnail: if (const auto small = _media->image(::Data::PhotoSize::Small)) { return small; } else if (const auto thumbnail = _media->image(::Data::PhotoSize::Thumbnail)) { return thumbnail; } return _media->thumbnailInline(); } return nullptr; } QImage CachedPagePhotoDynamicImage::prepareImage( QImage image, int size) const { const auto requested = requestedSize(size); if (requested.isEmpty() || image.isNull()) { return image; } const auto ratio = style::DevicePixelRatio(); if (!_cached.isNull()) { const auto cachedSize = _cached.size() / ratio; if (cachedSize.width() == requested.width() || cachedSize.height() == requested.height()) { return _cached; } } const auto to = image.size().scaled(requested, Qt::KeepAspectRatio); _cached = image.scaled( QSize(std::max(to.width(), 1), std::max(to.height(), 1)) * ratio, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); _cached.setDevicePixelRatio(ratio); return _cached; } QSize CachedPagePhotoDynamicImage::requestedSize(int size) const { if (!_requestedSize.isEmpty()) { return _requestedSize; } return (size > 0) ? QSize(size, size) : QSize(); } class CachedPagePhotoRuntime final : public Markdown::PhotoRuntime { public: CachedPagePhotoRuntime( not_null session, not_null photo, ::Data::FileOrigin origin); [[nodiscard]] std::shared_ptr thumbnail( QSize size) const override; [[nodiscard]] std::shared_ptr full( QSize size) const override; [[nodiscard]] bool loaded() const override; [[nodiscard]] bool loading() const override; [[nodiscard]] double progress() const override; void open(Qt::MouseButton button) const override; private: const not_null _session; const not_null _photo; const ::Data::FileOrigin _origin; const std::shared_ptr<::Data::PhotoMedia> _media; }; CachedPagePhotoRuntime::CachedPagePhotoRuntime( not_null session, not_null photo, ::Data::FileOrigin origin) : _session(session) , _photo(photo) , _origin(std::move(origin)) , _media(photo->createMediaView()) { } std::shared_ptr CachedPagePhotoRuntime::thumbnail( QSize size) const { _media->wanted(::Data::PhotoSize::Small, _origin); return std::make_shared( _media, _photo, _origin, CachedPagePhotoImageKind::Thumbnail, size); } std::shared_ptr CachedPagePhotoRuntime::full( QSize size) const { _media->wanted(::Data::PhotoSize::Large, _origin); return std::make_shared( _media, _photo, _origin, CachedPagePhotoImageKind::Full, size); } bool CachedPagePhotoRuntime::loaded() const { _media->wanted(::Data::PhotoSize::Large, _origin); return _media->loaded(); } bool CachedPagePhotoRuntime::loading() const { _media->wanted(::Data::PhotoSize::Large, _origin); return _photo->displayLoading(); } double CachedPagePhotoRuntime::progress() const { _media->wanted(::Data::PhotoSize::Large, _origin); return _media->progress(); } void CachedPagePhotoRuntime::open(Qt::MouseButton button) const { if (button != Qt::LeftButton && button != Qt::MiddleButton) { return; } if (const auto window = Core::App().activeWindow()) { const auto item = (HistoryItem*)nullptr; window->openInMediaView({ CurrentSessionController(_session), _photo, item, MsgId(0), PeerId(0), }); } } [[nodiscard]] ImageWithLocation CachedPageMapImageData( double latitude, double longitude, uint64 accessHash, QSize size, int zoom) { const auto location = GeoPointLocation{ .lat = latitude, .lon = longitude, .access = accessHash, .width = std::max(size.width(), 1), .height = std::max(size.height(), 1), .zoom = std::max(zoom, kGeoPointZoomMin), .scale = kGeoPointScale, }; return { .location = ImageLocation( { location }, location.width, location.height), }; } [[nodiscard]] ::Data::LocationPoint CachedPageMapPoint( double latitude, double longitude, uint64 accessHash) { const auto point = MTP_geoPoint( MTP_flags(0), MTP_double(longitude), MTP_double(latitude), MTP_long(accessHash), MTP_int(0)); return ::Data::LocationPoint(point.c_geoPoint()); } [[nodiscard]] bool CanHostNativeIvVideoDocument( not_null document) { return !document->isVideoMessage() && (document->isVideoFile() || document->isAnimation()); } [[nodiscard]] ::Data::MediaFile::Args CachedPageVideoMediaArgs( not_null session, not_null document) { const auto video = document->video(); return { .hasQualitiesList = video && !video->qualities.empty(), .skipPremiumEffect = !session->premium(), }; } class CachedPageDocumentRuntime final : public Markdown::DocumentRuntime { public: CachedPageDocumentRuntime( not_null session, not_null document, ::Data::FileOrigin origin); [[nodiscard]] std::shared_ptr thumbnail( QSize size) const override; [[nodiscard]] std::shared_ptr full( QSize size) const override; [[nodiscard]] bool loaded() const override; [[nodiscard]] bool loading() const override; [[nodiscard]] double progress() const override; void open(Qt::MouseButton button) const override; private: const not_null _session; const not_null _document; const ::Data::FileOrigin _origin; const std::shared_ptr<::Data::DocumentMedia> _media; }; CachedPageDocumentRuntime::CachedPageDocumentRuntime( not_null session, not_null document, ::Data::FileOrigin origin) : _session(session) , _document(document) , _origin(std::move(origin)) , _media(document->createMediaView()) { } std::shared_ptr CachedPageDocumentRuntime::thumbnail( QSize size) const { return Ui::MakeDocumentThumbnailFit(_document, _origin); } std::shared_ptr CachedPageDocumentRuntime::full( QSize size) const { return Ui::MakeDocumentThumbnail(_document, _origin); } bool CachedPageDocumentRuntime::loaded() const { return _media->loaded(); } bool CachedPageDocumentRuntime::loading() const { return _document->displayLoading(); } double CachedPageDocumentRuntime::progress() const { return _document->progress(); } void CachedPageDocumentRuntime::open(Qt::MouseButton button) const { if (button != Qt::LeftButton && button != Qt::MiddleButton) { return; } if (const auto window = Core::App().activeWindow()) { const auto item = (HistoryItem*)nullptr; window->openInMediaView({ CurrentSessionController(_session), _document, item, MsgId(0), PeerId(0), }); } } class CachedPageInlineDocumentImage final : public Ui::DynamicImage { public: CachedPageInlineDocumentImage( not_null document, ::Data::FileOrigin origin, QSize requestedSize); [[nodiscard]] std::shared_ptr clone() override; [[nodiscard]] QImage image(int size) override; void subscribeToUpdates(Fn callback) override; private: void ensureWanted(); [[nodiscard]] bool fullImageLoaded() const; [[nodiscard]] Image *resolvedFullPhotoImage() const; [[nodiscard]] Image *resolvedPhotoImage() const; [[nodiscard]] Image *resolvedThumbnailImage() const; [[nodiscard]] QImage resolvedDocumentImage(); [[nodiscard]] QImage prepareImage( QImage image, int size, bool full = false) const; [[nodiscard]] QSize requestedSize(int size) const; const not_null _document; const ::Data::FileOrigin _origin; const QSize _requestedSize; const std::shared_ptr<::Data::DocumentMedia> _media; const std::shared_ptr<::Data::PhotoMedia> _photoMedia; QImage _documentImage; mutable QImage _cached; bool _documentImageRead = false; mutable bool _cachedFull = false; rpl::lifetime _subscription; }; [[nodiscard]] std::shared_ptr<::Data::PhotoMedia> CachedPageInlinePhotoMedia( not_null document) { const auto photo = document->goodThumbnailPhoto(); return photo ? photo->createMediaView() : nullptr; } CachedPageInlineDocumentImage::CachedPageInlineDocumentImage( not_null document, ::Data::FileOrigin origin, QSize requestedSize) : _document(document) , _origin(std::move(origin)) , _requestedSize(requestedSize) , _media(document->createMediaView()) , _photoMedia(CachedPageInlinePhotoMedia(document)) { } std::shared_ptr CachedPageInlineDocumentImage::clone() { return std::make_shared( _document, _origin, _requestedSize); } QImage CachedPageInlineDocumentImage::image(int size) { ensureWanted(); if (const auto image = resolvedFullPhotoImage()) { return prepareImage(image->original(), size, true); } else if (auto image = resolvedDocumentImage(); !image.isNull()) { return prepareImage(std::move(image), size, true); } else if (const auto image = resolvedPhotoImage()) { return prepareImage(image->original(), size); } else if (const auto thumbnail = resolvedThumbnailImage()) { return prepareImage(thumbnail->original(), size); } return QImage(); } void CachedPageInlineDocumentImage::subscribeToUpdates(Fn callback) { _subscription.destroy(); if (!callback || (!_photoMedia && !_document->isImage() && !_document->hasThumbnail())) { return; } ensureWanted(); if (fullImageLoaded()) { return; } _document->session().downloaderTaskFinished( ) | rpl::filter([=] { return fullImageLoaded() || (!_photoMedia && !_document->isImage() && resolvedThumbnailImage()); }) | rpl::take(1) | rpl::on_next(std::move(callback), _subscription); } void CachedPageInlineDocumentImage::ensureWanted() { if (_photoMedia) { _photoMedia->wanted(::Data::PhotoSize::Large, _origin); } if (_document->isImage()) { _document->forceToCache(true); _document->save(_origin, QString(), LoadFromCloudOrLocal, true); } else { _media->thumbnailWanted(_origin); } } bool CachedPageInlineDocumentImage::fullImageLoaded() const { return resolvedFullPhotoImage() || (_document->isImage() && _media->loaded(true)); } Image *CachedPageInlineDocumentImage::resolvedFullPhotoImage() const { return _photoMedia ? _photoMedia->image(::Data::PhotoSize::Large) : nullptr; } Image *CachedPageInlineDocumentImage::resolvedPhotoImage() const { if (!_photoMedia) { return nullptr; } else if (const auto small = _photoMedia->image(::Data::PhotoSize::Small)) { return small; } else if (const auto thumbnail = _photoMedia->image( ::Data::PhotoSize::Thumbnail)) { return thumbnail; } return _photoMedia->thumbnailInline(); } Image *CachedPageInlineDocumentImage::resolvedThumbnailImage() const { if (const auto image = _media->thumbnail()) { return image; } return _media->thumbnailInline(); } QImage CachedPageInlineDocumentImage::resolvedDocumentImage() { if (!_document->isImage() || !_media->loaded(true)) { return QImage(); } else if (_documentImageRead) { return _documentImage; } _documentImageRead = true; _document->saveFromDataSilent(); auto &location = _document->location(true); if (location.accessEnable()) { _documentImage = Images::Read({ .path = location.name(), .maxSize = requestedSize(0) * style::DevicePixelRatio(), }).image; location.accessDisable(); } else { _documentImage = Images::Read({ .content = _media->bytes(), .maxSize = requestedSize(0) * style::DevicePixelRatio(), }).image; } return _documentImage; } QImage CachedPageInlineDocumentImage::prepareImage( QImage image, int size, bool full) const { const auto requested = requestedSize(size); if (requested.isEmpty() || image.isNull()) { return image; } const auto ratio = style::DevicePixelRatio(); if (!_cached.isNull() && (_cachedFull || !full)) { const auto cachedSize = _cached.size() / ratio; if (cachedSize.width() == requested.width() || cachedSize.height() == requested.height()) { return _cached; } } const auto to = image.size().scaled(requested, Qt::KeepAspectRatio); _cached = image.scaled( QSize(std::max(to.width(), 1), std::max(to.height(), 1)) * ratio, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); _cached.setDevicePixelRatio(style::DevicePixelRatio()); return _cached; } QSize CachedPageInlineDocumentImage::requestedSize(int size) const { if (!_requestedSize.isEmpty()) { return _requestedSize; } return (size > 0) ? QSize(size, size) : QSize(); } class CachedPageMapDynamicImage final : public Ui::DynamicImage { public: CachedPageMapDynamicImage( not_null<::Data::CloudImage*> data, not_null session, ::Data::FileOrigin origin); [[nodiscard]] std::shared_ptr clone() override; [[nodiscard]] QImage image(int size) override; void subscribeToUpdates(Fn callback) override; private: const not_null<::Data::CloudImage*> _data; const not_null _session; const ::Data::FileOrigin _origin; std::shared_ptr _view; QImage _prepared; int _paletteVersion = 0; rpl::lifetime _subscription; }; CachedPageMapDynamicImage::CachedPageMapDynamicImage( not_null<::Data::CloudImage*> data, not_null session, ::Data::FileOrigin origin) : _data(data) , _session(session) , _origin(std::move(origin)) { } std::shared_ptr CachedPageMapDynamicImage::clone() { return std::make_shared( _data, _session, _origin); } QImage CachedPageMapDynamicImage::image(int size) { const auto loaded = _view ? *_view : QImage(); if (loaded.isNull()) { return QImage(); } const auto paletteVersion = style::PaletteVersion(); if (_prepared.size() == loaded.size() && _prepared.devicePixelRatio() == loaded.devicePixelRatio() && _paletteVersion == paletteVersion) { return _prepared; } _paletteVersion = paletteVersion; _prepared = loaded.copy(); _prepared.setDevicePixelRatio(loaded.devicePixelRatio()); const auto ratio = loaded.devicePixelRatio(); const auto width = int(loaded.width() / ratio); const auto height = int(loaded.height() / ratio); const auto markerSize = std::min(width, height); auto p = Painter(&_prepared); auto hq = PainterHighQualityEnabler(p); const auto pinScale = std::min({ 1.0, width / (st::historyMapPoint.height() * 2.5), height / (st::historyMapPoint.height() * 2.5), }); const auto center = QPointF(width / 2.0, height / 2.0); p.translate(center); p.scale(pinScale, pinScale); p.translate(-center); const auto paintMarker = [&](const style::icon &icon) { icon.paint( p, (width - icon.width()) / 2, (height / 2) - icon.height(), markerSize); }; paintMarker(st::historyMapPoint); paintMarker(st::historyMapPointInner); return _prepared; } void CachedPageMapDynamicImage::subscribeToUpdates(Fn callback) { _subscription.destroy(); if (!callback) { _view = nullptr; _prepared = QImage(); return; } _view = _data->createView(); _data->load(_session, _origin); if (!_view->isNull()) { return; } _subscription = _session->downloaderTaskFinished( ) | rpl::filter([=] { return !_view->isNull(); }) | rpl::take(1) | rpl::on_next([=] { _prepared = QImage(); callback(); }); } class CachedPageMapRuntime final : public Markdown::MapRuntime { public: CachedPageMapRuntime( not_null session, ::Data::FileOrigin origin, double latitude, double longitude, uint64 accessHash, QSize size, int zoom); [[nodiscard]] std::shared_ptr thumbnail( QSize size) const override; [[nodiscard]] std::shared_ptr full( QSize size) const override; [[nodiscard]] bool loaded() const override; [[nodiscard]] bool loading() const override; [[nodiscard]] double progress() const override; private: void ensureLoaded() const; const not_null _session; const ::Data::FileOrigin _origin; mutable ::Data::CloudImage _image; }; CachedPageMapRuntime::CachedPageMapRuntime( not_null session, ::Data::FileOrigin origin, double latitude, double longitude, uint64 accessHash, QSize size, int zoom) : _session(session) , _origin(std::move(origin)) , _image(session, CachedPageMapImageData( latitude, longitude, accessHash, size, zoom)) { } std::shared_ptr CachedPageMapRuntime::thumbnail( QSize size) const { ensureLoaded(); return std::make_shared( &_image, _session, _origin); } std::shared_ptr CachedPageMapRuntime::full( QSize size) const { ensureLoaded(); return std::make_shared( &_image, _session, _origin); } bool CachedPageMapRuntime::loaded() const { ensureLoaded(); return _image.loadedOnce(); } bool CachedPageMapRuntime::loading() const { ensureLoaded(); return _image.loading(); } double CachedPageMapRuntime::progress() const { ensureLoaded(); return _image.loadedOnce() ? 1. : 0.; } void CachedPageMapRuntime::ensureLoaded() const { _image.load(_session, _origin); } class CachedPageChannelRuntime final : public Markdown::ChannelRuntime { public: CachedPageChannelRuntime( not_null channel, QString context, Fn openChannel, Fn joinChannel); [[nodiscard]] bool joinVisible() const override; void open(Qt::MouseButton button) const override; void join(Qt::MouseButton button) const override; private: const not_null _channel; const QString _context; const Fn _openChannel; const Fn _joinChannel; }; CachedPageChannelRuntime::CachedPageChannelRuntime( not_null channel, QString context, Fn openChannel, Fn joinChannel) : _channel(channel) , _context(std::move(context)) , _openChannel(std::move(openChannel)) , _joinChannel(std::move(joinChannel)) { } bool CachedPageChannelRuntime::joinVisible() const { return !_channel->amIn(); } void CachedPageChannelRuntime::open(Qt::MouseButton button) const { if ((button == Qt::LeftButton || button == Qt::MiddleButton) && _openChannel) { _openChannel(_context); } } void CachedPageChannelRuntime::join(Qt::MouseButton button) const { if ((button == Qt::LeftButton || button == Qt::MiddleButton) && _joinChannel) { _joinChannel(_context); } } class CachedPageMediaRuntime final : public Markdown::MediaRuntime { public: CachedPageMediaRuntime( not_null session, not_null page, Fn openChannel, Fn joinChannel); [[nodiscard]] std::shared_ptr resolveInlineImage( uint64 documentId, QSize size) const override; [[nodiscard]] std::shared_ptr resolvePhoto( uint64 photoId) const override; [[nodiscard]] std::shared_ptr resolveDocument( uint64 documentId) const override; [[nodiscard]] std::shared_ptr resolveMap( double latitude, double longitude, uint64 accessHash, QSize size, int zoom) const override; [[nodiscard]] std::shared_ptr resolveChannel( uint64 channelId, const QString &username) const override; [[nodiscard]] rpl::producer channelJoinedChanges() const override; [[nodiscard]] std::shared_ptr hostedMediaHost( not_null controller, not_null history) const; [[nodiscard]] std::shared_ptr hostedMediaBlockFactory() const override; private: void subscribeToChannel( uint64 channelId, not_null channel) const; [[nodiscard]] ::Data::FileOrigin fileOrigin() const; const not_null _session; const not_null _page; const Fn _openChannel; const Fn _joinChannel; mutable std::shared_ptr _hostedMediaHost; mutable base::flat_map _channelJoinedSubscriptions; mutable rpl::event_stream _channelJoinedChanges; }; CachedPageMediaRuntime::CachedPageMediaRuntime( not_null session, not_null page, Fn openChannel, Fn joinChannel) : _session(session) , _page(page) , _openChannel(std::move(openChannel)) , _joinChannel(std::move(joinChannel)) { } std::shared_ptr CachedPageMediaRuntime::resolveInlineImage( uint64 documentId, QSize size) const { const auto document = _session->data().document(DocumentId(documentId)); if (document->isNull()) { return nullptr; } return std::make_shared( document, fileOrigin(), size); } std::shared_ptr CachedPageMediaRuntime::resolvePhoto( uint64 photoId) const { const auto photo = _session->data().photo(PhotoId(photoId)); if (photo->isNull()) { return nullptr; } return std::make_shared( _session, photo, fileOrigin()); } std::shared_ptr CachedPageMediaRuntime::resolveDocument( uint64 documentId) const { const auto document = _session->data().document(DocumentId(documentId)); if (document->isNull()) { return nullptr; } return std::make_shared( _session, document, fileOrigin()); } std::shared_ptr CachedPageMediaRuntime::resolveMap( double latitude, double longitude, uint64 accessHash, QSize size, int zoom) const { return std::make_shared( _session, fileOrigin(), latitude, longitude, accessHash, size, zoom); } std::shared_ptr CachedPageMediaRuntime::resolveChannel( uint64 channelId, const QString &username) const { const auto channel = _session->data().channel(ChannelId(channelId)); subscribeToChannel(channelId, channel); return std::make_shared( channel, SerializeNativeIvChannelContext(channelId, username), _openChannel, _joinChannel); } rpl::producer CachedPageMediaRuntime::channelJoinedChanges() const { return _channelJoinedChanges.events(); } auto CachedPageMediaRuntime::hostedMediaHost( not_null controller, not_null history) const -> std::shared_ptr { if (!_hostedMediaHost) { _hostedMediaHost = std::make_shared( controller, history, _page->url); } return _hostedMediaHost; } auto CachedPageMediaRuntime::hostedMediaBlockFactory() const -> std::shared_ptr { const auto controller = CurrentSessionController(_session); if (!controller || !_session->data().peerLoaded( PeerData::kServiceNotificationsId)) { return nullptr; } const auto history = _session->data().history( PeerData::kServiceNotificationsId); if (!history->peer->isUser()) { return nullptr; } const auto host = hostedMediaHost(not_null{ controller }, history); return std::make_shared( base::make_weak(controller), [session = _session, host]( Window::SessionController *controller, const Markdown::PreparedPhotoBlockData &prepared) { if (!controller || !prepared.viewerOpen || !prepared.urlOverride.isEmpty()) { return std::shared_ptr(); } const auto photo = session->data().photo(PhotoId(prepared.photoId)); if (photo->isNull()) { return std::shared_ptr(); } host->registerPhoto(photo); auto descriptor = Markdown::IvHistoryViewMediaDescriptor(); descriptor.stableId = prepared.id.value; descriptor.kind = Markdown::IvHistoryViewMediaKind::Photo; descriptor.copyText = u"Photo"_q; descriptor.layoutHint = QSize(prepared.width, prepared.height); descriptor.host = host; descriptor.mediaFactory = [photo]( not_null view) { return std::make_unique( view, view->data(), photo, false); }; const auto &pageUrl = host->pageUrl(); descriptor.photo = std::make_shared( session, photo, ::Data::FileOriginWebPage{ pageUrl }); return Markdown::CreateIvHistoryViewMediaBlock( controller, std::move(descriptor)); }, [session = _session, host]( Window::SessionController *controller, const Markdown::PreparedVideoBlockData &prepared) { if (!controller || prepared.media.kind != Markdown::PreparedMediaItemKind::Document) { return std::shared_ptr(); } const auto document = session->data().document( DocumentId(prepared.media.id)); if (document->isNull() || !CanHostNativeIvVideoDocument(document)) { return std::shared_ptr(); } host->registerDocument(document); auto media = std::make_shared<::Data::MediaFile>( host->item(), document, CachedPageVideoMediaArgs(session, document)); auto descriptor = Markdown::IvHistoryViewMediaDescriptor(); descriptor.stableId = prepared.id.value; descriptor.kind = Markdown::IvHistoryViewMediaKind::Document; descriptor.copyText = tr::lng_in_dlg_video(tr::now); descriptor.layoutHint = QSize( prepared.media.width, prepared.media.height); descriptor.host = host; descriptor.mediaFactory = [media]( not_null view) { return media->createView( view, view->data()); }; descriptor.keepAlive.push_back(base::take(media)); const auto &pageUrl = host->pageUrl(); descriptor.document = std::make_shared( session, document, ::Data::FileOriginWebPage{ pageUrl }); return Markdown::CreateIvHistoryViewMediaBlock( controller, std::move(descriptor)); }, Markdown::IvHistoryViewMediaBlockFactory::AudioFactory(), [session = _session, host]( Window::SessionController *controller, const Markdown::PreparedMapBlockData &prepared) { if (!controller) { return std::shared_ptr(); } const auto point = CachedPageMapPoint( prepared.latitude, prepared.longitude, prepared.accessHash); const auto mapImage = std::make_shared<::Data::CloudImage>( session, CachedPageMapImageData( prepared.latitude, prepared.longitude, prepared.accessHash, QSize(prepared.width, prepared.height), prepared.zoom)); const auto mapImagePtr = mapImage.get(); auto descriptor = Markdown::IvHistoryViewMediaDescriptor(); descriptor.stableId = prepared.id.value; descriptor.kind = Markdown::IvHistoryViewMediaKind::Map; descriptor.copyText = tr::lng_maps_point(tr::now); descriptor.layoutHint = QSize(prepared.width, prepared.height); descriptor.host = host; descriptor.mediaFactory = [mapImagePtr, point]( not_null view) { return std::make_unique( view, not_null{ mapImagePtr }, point); }; descriptor.keepAlive.push_back(mapImage); return Markdown::CreateIvHistoryViewMediaBlock( controller, std::move(descriptor)); }); } void CachedPageMediaRuntime::subscribeToChannel( uint64 channelId, not_null channel) const { if (_channelJoinedSubscriptions.find(channelId) != end(_channelJoinedSubscriptions)) { return; } Info::Profile::AmInChannelValue(channel) | rpl::on_next([=](bool) { _channelJoinedChanges.fire_copy(channelId); }, _channelJoinedSubscriptions[channelId]); } ::Data::FileOrigin CachedPageMediaRuntime::fileOrigin() const { return ::Data::FileOriginWebPage{ _page->url }; } } // namespace auto CreateCachedPageMediaRuntime( not_null session, not_null page, Fn openChannel, Fn joinChannel) -> std::shared_ptr { return std::make_shared( session, page, std::move(openChannel), std::move(joinChannel)); } } // namespace Iv