/* 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 "chat_helpers/ttl_media_layer_widget.h" #include "base/event_filter.h" #include "data/data_document.h" #include "data/data_session.h" #include "editor/editor_layer_widget.h" #include "history/history.h" #include "history/history_item.h" #include "history/view/history_view_element.h" #include "history/view/media/history_view_document.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "mainwidget.h" #include "media/audio/media_audio.h" #include "media/player/media_player_instance.h" #include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" #include "ui/effects/path_shift_gradient.h" #include "ui/painter.h" #include "ui/rect.h" #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/widgets/tooltip.h" #include "window/section_widget.h" // Window::ChatThemeValueFromPeer. #include "window/themes/window_theme.h" #include "window/window_controller.h" #include "window/window_session_controller.h" #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" #include "styles/style_dialogs.h" // AyuGram includes #include "ayu/ayu_settings.h" namespace ChatHelpers { namespace { class PreviewDelegate final : public HistoryView::DefaultElementDelegate { public: PreviewDelegate( not_null parent, not_null st, rpl::producer chatWideValue, Fn update); bool elementAnimationsPaused() override; not_null elementPathShiftGradient() override; HistoryView::Context elementContext() override; bool elementIsChatWide() override; private: const not_null _parent; const std::unique_ptr _pathGradient; rpl::variable _chatWide; }; PreviewDelegate::PreviewDelegate( not_null parent, not_null st, rpl::producer chatWideValue, Fn update) : _parent(parent) , _pathGradient(HistoryView::MakePathShiftGradient(st, update)) , _chatWide(std::move(chatWideValue)) { } bool PreviewDelegate::elementAnimationsPaused() { return _parent->window()->isActiveWindow(); } not_null PreviewDelegate::elementPathShiftGradient() { return _pathGradient.get(); } HistoryView::Context PreviewDelegate::elementContext() { return HistoryView::Context::TTLViewer; } bool PreviewDelegate::elementIsChatWide() { return _chatWide.current(); } class PreviewWrap final : public Ui::RpWidget { public: PreviewWrap( not_null parent, not_null item, rpl::producer viewportValue, rpl::producer chatWideValue, rpl::producer> theme); ~PreviewWrap(); [[nodiscard]] rpl::producer<> closeRequests() const; private: void paintEvent(QPaintEvent *e) override; void createView(); [[nodiscard]] bool goodItem() const; void clear(); const not_null _item; const std::unique_ptr _style; const std::unique_ptr _delegate; rpl::variable _globalViewport; rpl::variable _chatWide; std::shared_ptr _theme; std::unique_ptr _element; QRect _viewport; QRect _elementGeometry; rpl::variable _elementInner; rpl::lifetime _elementLifetime; QImage _lastFrameCache; rpl::event_stream<> _closeRequests; }; PreviewWrap::PreviewWrap( not_null parent, not_null item, rpl::producer viewportValue, rpl::producer chatWideValue, rpl::producer> theme) : RpWidget(parent) , _item(item) , _style(std::make_unique( item->history()->session().colorIndicesValue())) , _delegate(std::make_unique( parent, _style.get(), std::move(chatWideValue), [=] { update(_elementGeometry); })) , _globalViewport(std::move(viewportValue)) { const auto closeCallback = [=] { _closeRequests.fire({}); }; HistoryView::TTLVoiceStops( item->fullId() ) | rpl::start_with_next([=] { _lastFrameCache = Ui::GrabWidgetToImage(this, _elementGeometry); closeCallback(); }, lifetime()); const auto isRound = _item && _item->media() && _item->media()->document() && _item->media()->document()->isVideoMessage(); std::move( theme ) | rpl::start_with_next([=](std::shared_ptr theme) { _theme = std::move(theme); _style->apply(_theme.get()); }, lifetime()); const auto session = &_item->history()->session(); session->data().viewRepaintRequest( ) | rpl::start_with_next([=](not_null view) { if (view == _element.get()) { update(_elementGeometry); } }, lifetime()); session->data().itemViewRefreshRequest( ) | rpl::start_with_next([=](not_null item) { if (item == _item) { if (goodItem()) { createView(); update(); } else { clear(); _closeRequests.fire({}); } } }, lifetime()); session->data().itemDataChanges( ) | rpl::start_with_next([=](not_null item) { if (item == _item) { _element->itemDataChanged(); } }, lifetime()); session->data().itemRemoved( ) | rpl::start_with_next([=](not_null item) { if (item == _item) { _closeRequests.fire({}); } }, lifetime()); const auto settings = &AyuSettings::getInstance(); { const auto close = Ui::CreateChild( this, item->out() || settings->saveDeletedMessages ? tr::lng_close() : tr::lng_ttl_voice_close_in(), st::ttlMediaButton); close->setFullRadius(true); close->setClickedCallback(closeCallback); close->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); rpl::combine( sizeValue(), _elementInner.value() ) | rpl::start_with_next([=](QSize size, QRect inner) { close->moveToLeft( inner.x() + (inner.width() - close->width()) / 2, (size.height() - close->height() - st::ttlMediaButtonBottomSkip)); }, close->lifetime()); } QWidget::setAttribute(Qt::WA_OpaquePaintEvent, false); createView(); { auto text = item->out() ? (isRound ? tr::lng_ttl_round_tooltip_out : tr::lng_ttl_voice_tooltip_out)( lt_user, rpl::single( item->history()->peer->shortName() ) | Ui::Text::ToRichLangValue(), Ui::Text::RichLangValue) : (isRound ? settings->saveDeletedMessages ? tr::ayu_ExpiringVideoMessageNote : tr::lng_ttl_round_tooltip_in : settings->saveDeletedMessages ? tr::ayu_ExpiringVoiceMessageNote : tr::lng_ttl_voice_tooltip_in)(Ui::Text::RichLangValue); const auto tooltip = Ui::CreateChild( this, object_ptr>( this, Ui::MakeNiceTooltipLabel( parent, std::move(text), st::dialogsStoriesTooltipMaxWidth, st::ttlMediaImportantTooltipLabel), st::defaultImportantTooltip.padding), st::dialogsStoriesTooltip); tooltip->toggleFast(true); _elementInner.value( ) | rpl::filter([](const QRect &inner) { return !inner.isEmpty(); }) | rpl::start_with_next([=](const QRect &inner) { tooltip->pointAt(inner, RectPart::Top, [=](QSize size) { return QPoint{ inner.x() + (inner.width() - size.width()) / 2, (inner.y() - st::normalFont->height - size.height() - st::defaultImportantTooltip.padding.top()), }; }); }, tooltip->lifetime()); } } rpl::producer<> PreviewWrap::closeRequests() const { return _closeRequests.events(); } bool PreviewWrap::goodItem() const { const auto media = _item->media(); if (!media || !media->ttlSeconds()) { return false; } const auto document = media->document(); return document && (document->isVoiceMessage() || document->isVideoMessage()); } void PreviewWrap::createView() { clear(); _element = _item->createView(_delegate.get()); _element->initDimensions(); rpl::combine( sizeValue(), _globalViewport.value() ) | rpl::start_with_next([=](QSize outer, QRect globalViewport) { _viewport = globalViewport.isEmpty() ? rect() : mapFromGlobal(globalViewport); if (_viewport.width() < st::msgMinWidth) { return; } _element->resizeGetHeight(_viewport.width()); _elementGeometry = QRect( (_viewport.width() - _element->width()) / 2, (_viewport.height() - _element->height()) / 2, _element->width(), _element->height() ).translated(_viewport.topLeft()); _elementInner = _element->innerGeometry().translated( _elementGeometry.topLeft()); update(); }, _elementLifetime); } void PreviewWrap::clear() { _elementLifetime.destroy(); _element = nullptr; } PreviewWrap::~PreviewWrap() { clear(); } void PreviewWrap::paintEvent(QPaintEvent *e) { if (!_element || _elementGeometry.isEmpty()) { return; } auto p = Painter(this); p.translate(_elementGeometry.topLeft()); if (!_lastFrameCache.isNull()) { p.drawImage(0, 0, _lastFrameCache); } else { auto context = _theme->preparePaintContext( _style.get(), Rect(_element->currentSize()), Rect(_element->currentSize()), !window()->isActiveWindow()); context.outbg = _element->hasOutLayout(); _element->draw(p, context); } } rpl::producer GlobalViewportForWindow( not_null controller) { const auto delegate = controller->window().floatPlayerDelegate(); return rpl::single(rpl::empty) | rpl::then( delegate->floatPlayerAreaUpdates() ) | rpl::map([=] { auto section = (Media::Player::FloatSectionDelegate*)nullptr; delegate->floatPlayerEnumerateSections([&]( not_null check, Window::Column column) { if ((column == Window::Column::First && !section) || column == Window::Column::Second) { section = check; } }); if (section) { const auto rect = section->floatPlayerAvailableRect(); if (rect.width() >= st::msgMinWidth) { return rect; } } return QRect(); }); } } // namespace void ShowTTLMediaLayerWidget( not_null controller, not_null item) { const auto parent = controller->content(); const auto show = controller->uiShow(); auto preview = base::make_unique_q( parent, item, GlobalViewportForWindow(controller), controller->adaptive().chatWideValue(), Window::ChatThemeValueFromPeer( controller, item->history()->peer)); preview->closeRequests( ) | rpl::start_with_next([=] { show->hideLayer(); }, preview->lifetime()); auto layer = std::make_unique( parent, std::move(preview)); layer->lifetime().add([] { ::Media::Player::instance()->stop(); }); base::install_event_filter(layer.get(), [=](not_null e) { if (e->type() == QEvent::KeyPress) { const auto k = static_cast(e.get()); if (k->key() == Qt::Key_Escape) { show->hideLayer(); } return base::EventFilterResult::Cancel; } return base::EventFilterResult::Continue; }); controller->showLayer(std::move(layer), Ui::LayerOption::KeepOther); } } // namespace ChatHelpers