mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-07-21 21:22:50 +02:00
767 lines
20 KiB
C++
767 lines
20 KiB
C++
/*
|
|
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 "media/view/media_view_playback_sponsored.h"
|
|
|
|
#include "boxes/premium_preview_box.h"
|
|
#include "data/components/sponsored_messages.h"
|
|
#include "data/data_file_origin.h"
|
|
#include "data/data_photo.h"
|
|
#include "data/data_photo_media.h"
|
|
#include "data/data_session.h"
|
|
#include "history/history.h"
|
|
#include "history/history_item.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "main/main_session.h"
|
|
#include "menu/menu_sponsored.h"
|
|
#include "ui/effects/numbers_animation.h"
|
|
#include "ui/effects/ripple_animation.h"
|
|
#include "ui/widgets/menu/menu_add_action_callback.h"
|
|
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
|
#include "ui/widgets/buttons.h"
|
|
#include "ui/widgets/popup_menu.h"
|
|
#include "ui/basic_click_handlers.h"
|
|
#include "ui/painter.h"
|
|
#include "ui/ui_utility.h"
|
|
#include "ui/cached_round_corners.h"
|
|
#include "styles/style_chat.h"
|
|
#include "styles/style_media_view.h"
|
|
|
|
namespace Media::View {
|
|
namespace {
|
|
|
|
constexpr auto kStartDelayMin = crl::time(1000);
|
|
constexpr auto kDurationMin = 5 * crl::time(1000);
|
|
|
|
enum class Action {
|
|
Close,
|
|
PromotePremium,
|
|
Pause,
|
|
Unpause,
|
|
};
|
|
|
|
class Close final : public Ui::RippleButton {
|
|
public:
|
|
Close(
|
|
not_null<QWidget*> parent,
|
|
const style::RippleAnimation &st,
|
|
rpl::producer<crl::time> allowCloseAt);
|
|
|
|
[[nodiscard]] rpl::producer<Action> actions() const;
|
|
|
|
private:
|
|
QPoint prepareRippleStartPosition() const override;
|
|
QImage prepareRippleMask() const override;
|
|
void paintEvent(QPaintEvent *e) override;
|
|
|
|
void updateProgress(crl::time now);
|
|
|
|
rpl::event_stream<Action> _actions;
|
|
|
|
Ui::NumbersAnimation _countdown;
|
|
Ui::Animations::Basic _progress;
|
|
base::Timer _noAnimationTimer;
|
|
crl::time _allowCloseAt = 0;
|
|
crl::time _startedAt = 0;
|
|
crl::time _pausedAt = 0;
|
|
int _secondsTill = 0;
|
|
int _rippleSize = 0;
|
|
QPoint _rippleOrigin;
|
|
bool _allowClose = false;
|
|
|
|
};
|
|
|
|
Close::Close(
|
|
not_null<QWidget*> parent,
|
|
const style::RippleAnimation &st,
|
|
rpl::producer<crl::time> allowCloseAt)
|
|
: RippleButton(parent, st)
|
|
, _countdown(st::mediaSponsoredCloseFont, [=] { update(); })
|
|
, _progress([=](crl::time now) { updateProgress(now); })
|
|
, _noAnimationTimer([=] { updateProgress(crl::now()); })
|
|
, _startedAt(crl::now()) {
|
|
resize(st::mediaSponsoredCloseFull, st::mediaSponsoredCloseFull);
|
|
|
|
const auto size = st::mediaSponsoredCloseRipple;
|
|
const auto cut = int(base::SafeRound((width() - size) / 2.));
|
|
_rippleSize = std::min(width() - 2 * cut, height() - 2 * cut);
|
|
_rippleOrigin = QPoint(
|
|
(width() - _rippleSize) / 2,
|
|
(height() - _rippleSize) / 2);
|
|
|
|
std::move(
|
|
allowCloseAt
|
|
) | rpl::start_with_next([=](crl::time at) {
|
|
const auto now = crl::now();
|
|
if (!at) {
|
|
updateProgress(now);
|
|
_pausedAt = now;
|
|
_progress.stop();
|
|
} else {
|
|
if (_pausedAt) {
|
|
_startedAt += now - base::take(_pausedAt);
|
|
}
|
|
_allowCloseAt = at;
|
|
updateProgress(now);
|
|
if (!anim::Disabled()) {
|
|
_progress.start();
|
|
} else if (!_allowClose) {
|
|
_noAnimationTimer.callEach(crl::time(200));
|
|
}
|
|
}
|
|
}, lifetime());
|
|
updateProgress(_startedAt);
|
|
|
|
setClickedCallback([=] {
|
|
_actions.fire(_allowClose ? Action::Close : Action::PromotePremium);
|
|
});
|
|
}
|
|
|
|
rpl::producer<Action> Close::actions() const {
|
|
return _actions.events();
|
|
}
|
|
|
|
void Close::updateProgress(crl::time now) {
|
|
update();
|
|
}
|
|
|
|
QPoint Close::prepareRippleStartPosition() const {
|
|
return mapFromGlobal(QCursor::pos()) - _rippleOrigin;
|
|
}
|
|
|
|
QImage Close::prepareRippleMask() const {
|
|
return Ui::RippleAnimation::EllipseMask({ _rippleSize, _rippleSize });
|
|
}
|
|
|
|
void Close::paintEvent(QPaintEvent *e) {
|
|
auto p = QPainter(this);
|
|
|
|
paintRipple(p, _rippleOrigin);
|
|
|
|
const auto now = crl::now();
|
|
if (!_pausedAt) {
|
|
_allowClose = (now >= _allowCloseAt);
|
|
}
|
|
const auto msTill = _allowCloseAt - (_pausedAt ? _pausedAt : now);
|
|
const auto msFull = _allowCloseAt - _startedAt;
|
|
const auto secondsTill = (std::max(msTill, crl::time()) + 999) / 1000;
|
|
const auto secondsFull = (std::max(msFull, crl::time()) + 999) / 1000;
|
|
const auto allowCloseLeft = anim::Disabled()
|
|
? (secondsFull ? (secondsTill / float64(secondsFull)) : 0)
|
|
: std::max(msFull ? (msTill / float64(msFull)) : 0., 0.);
|
|
const auto duration = crl::time(st::fadeWrapDuration);
|
|
const auto allowedProgress = anim::Disabled()
|
|
? (secondsTill ? 0. : 1.)
|
|
: std::clamp(-msTill, crl::time(), duration) / float64(duration);
|
|
|
|
if (_secondsTill != secondsTill) {
|
|
const auto initial = !_secondsTill;
|
|
_secondsTill = secondsTill;
|
|
_countdown.setText(QString::number(_secondsTill), _secondsTill);
|
|
if (initial) {
|
|
_countdown.finishAnimating();
|
|
}
|
|
}
|
|
|
|
auto pen = st::mediaviewTextLinkFg->p;
|
|
if (allowedProgress < 1.) {
|
|
if (allowedProgress > 0.) {
|
|
p.setOpacity(1. - allowedProgress);
|
|
}
|
|
p.setPen(pen);
|
|
|
|
const auto inner = QRect(
|
|
(width() - st::mediaSponsoredCloseDiameter) / 2,
|
|
(height() - st::mediaSponsoredCloseDiameter) / 2,
|
|
st::mediaSponsoredCloseDiameter,
|
|
st::mediaSponsoredCloseDiameter);
|
|
p.setFont(st::mediaSponsoredCloseFont);
|
|
_countdown.paint(
|
|
p,
|
|
inner.x() + (inner.width() - _countdown.countWidth()) / 2,
|
|
(inner.y()
|
|
+ (inner.height()
|
|
- st::mediaSponsoredCloseFont->height) / 2),
|
|
width());
|
|
|
|
const auto skip = 0.23;
|
|
const auto len = int(base::SafeRound(
|
|
arc::kFullLength * (1. - skip) * allowCloseLeft));
|
|
if (len > 0) {
|
|
const auto from = arc::kFullLength / 4;
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
pen.setWidthF(st::mediaSponsoredCloseStroke);
|
|
pen.setCapStyle(Qt::RoundCap);
|
|
p.setPen(pen);
|
|
p.drawArc(inner, from, len);
|
|
}
|
|
|
|
p.setOpacity(1.);
|
|
}
|
|
|
|
const auto sizeFinal = st::mediaSponsoredCloseSize;
|
|
const auto sizeSmall = st::mediaSponsoredCloseCorner;
|
|
const auto twiceFinal = st::mediaSponsoredCloseTwice;
|
|
const auto twiceSmall = st::mediaSponsoredCloseSmall;
|
|
const auto size = sizeSmall + allowedProgress * (sizeFinal - sizeSmall);
|
|
const auto twice = twiceSmall
|
|
+ allowedProgress * (twiceFinal - twiceSmall);
|
|
const auto leftFinal = (width() - size) / 2.;
|
|
const auto leftSmall = (width() + st::mediaSponsoredCloseDiameter) / 2.
|
|
- (st::mediaSponsoredCloseStroke / 2.)
|
|
- sizeSmall;
|
|
const auto topFinal = (height() - size) / 2.;
|
|
const auto topSmall = (height() - st::mediaSponsoredCloseDiameter) / 2.;
|
|
const auto left = leftSmall + allowedProgress * (leftFinal - leftSmall);
|
|
const auto top = topSmall + allowedProgress * (topFinal - topSmall);
|
|
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
pen.setWidthF(twice / 2.);
|
|
p.setPen(pen);
|
|
p.drawLine(QPointF(left, top), QPointF(left + size, top + size));
|
|
p.drawLine(QPointF(left + size, top), QPointF(left, top + size));
|
|
}
|
|
|
|
[[nodiscard]] style::RoundButton PrepareAboutStyle() {
|
|
static auto textBg = style::complex_color([] {
|
|
auto result = st::mediaviewTextLinkFg->c;
|
|
result.setAlphaF(result.alphaF() * 0.1);
|
|
return result;
|
|
});
|
|
static auto textBgOver = style::complex_color([] {
|
|
auto result = st::mediaviewTextLinkFg->c;
|
|
result.setAlphaF(result.alphaF() * 0.15);
|
|
return result;
|
|
});
|
|
static auto rippleColor = style::complex_color([] {
|
|
auto result = st::mediaviewTextLinkFg->c;
|
|
result.setAlphaF(result.alphaF() * 0.2);
|
|
return result;
|
|
});
|
|
|
|
auto result = st::mediaSponsoredAbout;
|
|
result.textFg = st::mediaviewTextLinkFg;
|
|
result.textFgOver = st::mediaviewTextLinkFg;
|
|
result.textBg = textBg.color();
|
|
result.textBgOver = textBgOver.color();
|
|
result.ripple.color = rippleColor.color();
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class PlaybackSponsored::Message final : public Ui::RpWidget {
|
|
public:
|
|
Message(
|
|
QWidget *parent,
|
|
std::shared_ptr<ChatHelpers::Show> show,
|
|
const Data::SponsoredMessage &data,
|
|
rpl::producer<crl::time> allowCloseAt);
|
|
|
|
[[nodiscard]] rpl::producer<Action> actions() const;
|
|
|
|
void setFinalPosition(int x, int y);
|
|
|
|
void fadeIn();
|
|
void fadeOut(Fn<void()> hidden);
|
|
|
|
private:
|
|
void paintEvent(QPaintEvent *e) override;
|
|
void mouseMoveEvent(QMouseEvent *e) override;
|
|
void mousePressEvent(QMouseEvent *e) override;
|
|
void mouseReleaseEvent(QMouseEvent *e) override;
|
|
|
|
int resizeGetHeight(int newWidth) override;
|
|
|
|
void populate();
|
|
void startFadeIn();
|
|
void updateShown(Fn<void()> finished = nullptr);
|
|
void startFade(Fn<void()> finished);
|
|
|
|
const not_null<Main::Session*> _session;
|
|
const std::shared_ptr<ChatHelpers::Show> _show;
|
|
const Data::SponsoredMessage _data;
|
|
|
|
style::RoundButton _aboutSt;
|
|
std::unique_ptr<Ui::RoundButton> _about;
|
|
std::unique_ptr<Close> _close;
|
|
|
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
|
rpl::event_stream<Action> _actions;
|
|
|
|
std::shared_ptr<Data::PhotoMedia> _photo;
|
|
Ui::Text::String _title;
|
|
Ui::Text::String _text;
|
|
|
|
QPoint _finalPosition;
|
|
int _left = 0;
|
|
int _top = 0;
|
|
int _titleHeight = 0;
|
|
int _textHeight = 0;
|
|
|
|
QImage _cache;
|
|
Ui::Animations::Simple _showAnimation;
|
|
bool _shown = false;
|
|
bool _over = false;
|
|
bool _pressed = false;
|
|
|
|
rpl::lifetime _photoLifetime;
|
|
|
|
};
|
|
|
|
PlaybackSponsored::Message::Message(
|
|
QWidget *parent,
|
|
std::shared_ptr<ChatHelpers::Show> show,
|
|
const Data::SponsoredMessage &data,
|
|
rpl::producer<crl::time> allowCloseAt)
|
|
: RpWidget(parent)
|
|
, _session(&data.history->session())
|
|
, _show(std::move(show))
|
|
, _data(data)
|
|
, _aboutSt(PrepareAboutStyle())
|
|
, _about(std::make_unique<Ui::RoundButton>(
|
|
this,
|
|
tr::lng_search_sponsored_button(),
|
|
_aboutSt))
|
|
, _close(
|
|
std::make_unique<Close>(
|
|
this,
|
|
_aboutSt.ripple,
|
|
std::move(allowCloseAt))) {
|
|
_about->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
|
setMouseTracking(true);
|
|
populate();
|
|
hide();
|
|
}
|
|
|
|
rpl::producer<Action> PlaybackSponsored::Message::actions() const {
|
|
return rpl::merge(_actions.events(), _close->actions());
|
|
}
|
|
|
|
void PlaybackSponsored::Message::setFinalPosition(int x, int y) {
|
|
_finalPosition = { x, y };
|
|
if (_shown) {
|
|
updateShown();
|
|
}
|
|
}
|
|
|
|
void PlaybackSponsored::Message::fadeIn() {
|
|
_shown = true;
|
|
if (!_photo || _photo->loaded()) {
|
|
startFadeIn();
|
|
return;
|
|
}
|
|
_photo->owner()->session().downloaderTaskFinished(
|
|
) | rpl::filter([=] {
|
|
return _photo->loaded();
|
|
}) | rpl::start_with_next([=] {
|
|
_photoLifetime.destroy();
|
|
startFadeIn();
|
|
}, _photoLifetime);
|
|
}
|
|
|
|
void PlaybackSponsored::Message::startFadeIn() {
|
|
if (!_shown) {
|
|
return;
|
|
}
|
|
startFade([=] {
|
|
_session->sponsoredMessages().view(_data.randomId);
|
|
});
|
|
show();
|
|
}
|
|
|
|
void PlaybackSponsored::Message::fadeOut(Fn<void()> hidden) {
|
|
if (!_shown) {
|
|
if (const auto onstack = hidden) {
|
|
onstack();
|
|
}
|
|
return;
|
|
}
|
|
_shown = false;
|
|
startFade(std::move(hidden));
|
|
}
|
|
|
|
void PlaybackSponsored::Message::startFade(Fn<void()> finished) {
|
|
_cache = Ui::GrabWidgetToImage(this);
|
|
_about->hide();
|
|
_close->hide();
|
|
const auto from = _shown ? 0. : 1.;
|
|
const auto till = _shown ? 1. : 0.;
|
|
_showAnimation.start([=] {
|
|
updateShown(finished);
|
|
}, from, till, st::fadeWrapDuration);
|
|
}
|
|
|
|
void PlaybackSponsored::Message::updateShown(Fn<void()> finished) {
|
|
const auto shown = _showAnimation.value(_shown ? 1. : 0.);
|
|
const auto shift = anim::interpolate(st::mediaSponsoredShift, 0, shown);
|
|
move(_finalPosition.x(), _finalPosition.y() + shift);
|
|
update();
|
|
if (!_showAnimation.animating()) {
|
|
_cache = QImage();
|
|
_close->show();
|
|
_about->show();
|
|
if (const auto onstack = finished) {
|
|
onstack();
|
|
}
|
|
}
|
|
}
|
|
|
|
void PlaybackSponsored::Message::paintEvent(QPaintEvent *e) {
|
|
auto p = QPainter(this);
|
|
|
|
const auto shown = _showAnimation.value(_shown ? 1. : 0.);
|
|
if (!_cache.isNull()) {
|
|
p.setOpacity(shown);
|
|
p.drawImage(0, 0, _cache);
|
|
return;
|
|
}
|
|
|
|
Ui::FillRoundRect(
|
|
p,
|
|
rect(),
|
|
st::mediaviewSaveMsgBg,
|
|
Ui::MediaviewSaveCorners);
|
|
|
|
const auto &padding = st::mediaSponsoredPadding;
|
|
if (_photo) {
|
|
if (const auto image = _photo->image(Data::PhotoSize::Large)) {
|
|
const auto size = st::mediaSponsoredThumb;
|
|
const auto x = padding.left();
|
|
const auto y = (height() - size) / 2;
|
|
p.drawPixmap(
|
|
x,
|
|
y,
|
|
image->pixSingle(
|
|
size,
|
|
size,
|
|
{ .options = Images::Option::RoundCircle }));
|
|
}
|
|
}
|
|
|
|
p.setPen(st::mediaviewControlFg);
|
|
|
|
_title.draw(p, {
|
|
.position = { _left, _top },
|
|
.availableWidth = _about->x() - _left,
|
|
.palette = &st::mediaviewTextPalette,
|
|
});
|
|
|
|
_text.draw(p, {
|
|
.position = { _left, _top + _titleHeight },
|
|
.availableWidth = _close->x() - _left,
|
|
.palette = &st::mediaviewTextPalette,
|
|
});
|
|
}
|
|
|
|
void PlaybackSponsored::Message::mouseMoveEvent(QMouseEvent *e) {
|
|
const auto &padding = st::mediaSponsoredPadding;
|
|
const auto point = e->pos();
|
|
const auto about = _about->geometry();
|
|
const auto close = _close->geometry();
|
|
const auto over = !about.marginsAdded(padding).contains(point)
|
|
&& !close.marginsAdded(padding).contains(point);
|
|
if (_over != over) {
|
|
_over = over;
|
|
setCursor(_over ? style::cur_pointer : style::cur_default);
|
|
}
|
|
}
|
|
|
|
void PlaybackSponsored::Message::mousePressEvent(QMouseEvent *e) {
|
|
if (_over) {
|
|
_pressed = true;
|
|
}
|
|
}
|
|
|
|
void PlaybackSponsored::Message::mouseReleaseEvent(QMouseEvent *e) {
|
|
if (base::take(_pressed) && _over) {
|
|
_session->sponsoredMessages().clicked(_data.randomId, false, false);
|
|
UrlClickHandler::Open(_data.link);
|
|
}
|
|
}
|
|
|
|
int PlaybackSponsored::Message::resizeGetHeight(int newWidth) {
|
|
const auto &padding = st::mediaSponsoredPadding;
|
|
const auto userpic = st::mediaSponsoredThumb;
|
|
const auto innerWidth = newWidth - _left - _close->width();
|
|
const auto titleWidth = innerWidth - _about->width() - padding.right();
|
|
_titleHeight = _title.countHeight(titleWidth);
|
|
_textHeight = _text.countHeight(innerWidth);
|
|
|
|
const auto use = std::max(_titleHeight + _textHeight, userpic);
|
|
|
|
const auto height = padding.top() + use + padding.bottom();
|
|
_left = padding.left() + (_photo ? (userpic + padding.left()) : 0);
|
|
_top = padding.top() + (use - _titleHeight - _textHeight) / 2;
|
|
|
|
_about->move(
|
|
_left + std::min(titleWidth, _title.maxWidth()) + padding.right(),
|
|
_top);
|
|
_close->move(
|
|
newWidth - _close->width(),
|
|
(height - _close->height()) / 2);
|
|
|
|
return height;
|
|
}
|
|
|
|
void PlaybackSponsored::Message::populate() {
|
|
const auto &from = _data.from;
|
|
const auto photo = from.photoId
|
|
? _data.history->owner().photo(from.photoId).get()
|
|
: nullptr;
|
|
if (photo) {
|
|
_photo = photo->createMediaView();
|
|
photo->load({}, LoadFromCloudOrLocal, true);
|
|
}
|
|
_title = Ui::Text::String(
|
|
st::semiboldTextStyle,
|
|
from.title,
|
|
kDefaultTextOptions,
|
|
st::msgMinWidth);
|
|
_text = Ui::Text::String(
|
|
st::defaultTextStyle,
|
|
_data.textWithEntities,
|
|
kMarkupTextOptions,
|
|
st::msgMinWidth);
|
|
|
|
_about->setClickedCallback([=] {
|
|
_menu = nullptr;
|
|
const auto parent = parentWidget();
|
|
_menu = base::make_unique_q<Ui::PopupMenu>(
|
|
parent,
|
|
st::mediaviewPopupMenu);
|
|
const auto raw = _menu.get();
|
|
const auto addAction = Ui::Menu::CreateAddActionCallback(raw);
|
|
Menu::FillSponsored(
|
|
addAction,
|
|
_show,
|
|
Menu::SponsoredPhrases::Channel,
|
|
_session->sponsoredMessages().lookupDetails(_data),
|
|
_session->sponsoredMessages().createReportCallback(
|
|
_data.randomId,
|
|
crl::guard(this, [=] { _actions.fire(Action::Close); })),
|
|
{ .dark = true });
|
|
_actions.fire(Action::Pause);
|
|
Ui::Connect(raw, &QObject::destroyed, this, [=] {
|
|
_actions.fire(Action::Unpause);
|
|
});
|
|
raw->popup(QCursor::pos());
|
|
});
|
|
}
|
|
|
|
PlaybackSponsored::PlaybackSponsored(
|
|
not_null<Ui::RpWidget*> controls,
|
|
std::shared_ptr<ChatHelpers::Show> show,
|
|
not_null<HistoryItem*> item)
|
|
: _parent(controls->parentWidget())
|
|
, _session(&item->history()->session())
|
|
, _show(std::move(show))
|
|
, _itemId(item->fullId())
|
|
, _controlsGeometry(controls->geometryValue())
|
|
, _timer([=] { update(); }) {
|
|
_session->sponsoredMessages().requestForVideo(item, crl::guard(this, [=](
|
|
Data::SponsoredForVideo data) {
|
|
if (data.list.empty()) {
|
|
return;
|
|
}
|
|
_data = std::move(data);
|
|
if (_data->state.initial()
|
|
|| (_data->state.itemIndex > _data->list.size())
|
|
|| (_data->state.itemIndex == _data->list.size()
|
|
&& _data->state.leftTillShow <= 0)) {
|
|
_data->state.itemIndex = 0;
|
|
_data->state.leftTillShow = std::max(
|
|
_data->startDelay,
|
|
kStartDelayMin);
|
|
}
|
|
update();
|
|
}));
|
|
}
|
|
|
|
PlaybackSponsored::~PlaybackSponsored() {
|
|
saveState();
|
|
}
|
|
|
|
void PlaybackSponsored::start() {
|
|
_started = true;
|
|
if (!_paused) {
|
|
_start = crl::now();
|
|
update();
|
|
}
|
|
}
|
|
|
|
void PlaybackSponsored::setPaused(bool paused) {
|
|
setPausedOutside(paused);
|
|
}
|
|
|
|
void PlaybackSponsored::updatePaused() {
|
|
const auto paused = _pausedInside || _pausedOutside;
|
|
if (_paused == paused) {
|
|
return;
|
|
} else if (_started && paused) {
|
|
update();
|
|
}
|
|
_paused = paused;
|
|
if (!_started) {
|
|
return;
|
|
} else if (_paused) {
|
|
_start = 0;
|
|
_timer.cancel();
|
|
_allowCloseAt = 0;
|
|
} else {
|
|
_start = crl::now();
|
|
update();
|
|
}
|
|
}
|
|
|
|
void PlaybackSponsored::setPausedInside(bool paused) {
|
|
if (_pausedInside == paused) {
|
|
return;
|
|
}
|
|
_pausedInside = paused;
|
|
updatePaused();
|
|
}
|
|
|
|
void PlaybackSponsored::setPausedOutside(bool paused) {
|
|
if (_pausedOutside == paused) {
|
|
return;
|
|
}
|
|
_pausedOutside = paused;
|
|
updatePaused();
|
|
}
|
|
|
|
void PlaybackSponsored::finish() {
|
|
_timer.cancel();
|
|
if (_data) {
|
|
saveState();
|
|
_data = std::nullopt;
|
|
}
|
|
}
|
|
|
|
void PlaybackSponsored::update() {
|
|
if (!_data || !_start) {
|
|
return;
|
|
}
|
|
|
|
const auto [now, state] = computeState();
|
|
const auto message = (_data->state.itemIndex < _data->list.size())
|
|
? &_data->list[state.itemIndex]
|
|
: nullptr;
|
|
const auto duration = message
|
|
? std::max(
|
|
message->durationMin + kDurationMin,
|
|
message->durationMax)
|
|
: crl::time(0);
|
|
if (_data->state.leftTillShow > 0 && state.leftTillShow <= 0) {
|
|
_data->state.leftTillShow = 0;
|
|
if (duration) {
|
|
_allowCloseAt = now + message->durationMin;
|
|
show(*message);
|
|
|
|
_start = now;
|
|
_timer.callOnce(duration);
|
|
saveState();
|
|
} else {
|
|
finish();
|
|
}
|
|
} else if (_data->state.leftTillShow <= 0
|
|
&& state.leftTillShow <= -duration) {
|
|
hide(now);
|
|
} else {
|
|
if (state.leftTillShow <= 0 && duration) {
|
|
_allowCloseAt = now + state.leftTillShow + message->durationMin;
|
|
if (!_widget) {
|
|
show(*message);
|
|
}
|
|
}
|
|
_data->state = state;
|
|
_timer.callOnce((state.leftTillShow > 0)
|
|
? state.leftTillShow
|
|
: (state.leftTillShow + duration));
|
|
}
|
|
}
|
|
|
|
|
|
void PlaybackSponsored::show(const Data::SponsoredMessage &data) {
|
|
_widget = std::make_unique<Message>(
|
|
_parent,
|
|
_show,
|
|
data,
|
|
_allowCloseAt.value());
|
|
const auto raw = _widget.get();
|
|
|
|
_controlsGeometry.value() | rpl::start_with_next([=](QRect controls) {
|
|
raw->resizeToWidth(controls.width());
|
|
raw->setFinalPosition(
|
|
controls.x(),
|
|
controls.y() - st::mediaSponsoredSkip - raw->height());
|
|
}, raw->lifetime());
|
|
|
|
raw->actions() | rpl::start_with_next([=](Action action) {
|
|
switch (action) {
|
|
case Action::Close: hide(crl::now()); break;
|
|
case Action::PromotePremium: showPremiumPromo(); break;
|
|
case Action::Pause: setPausedInside(true); break;
|
|
case Action::Unpause: setPausedInside(false); break;
|
|
}
|
|
}, raw->lifetime());
|
|
|
|
raw->fadeIn();
|
|
}
|
|
|
|
void PlaybackSponsored::showPremiumPromo() {
|
|
ShowPremiumPreviewBox(_show, PremiumFeature::NoAds);
|
|
}
|
|
|
|
void PlaybackSponsored::hide(crl::time now) {
|
|
Expects(_widget != nullptr);
|
|
|
|
_widget->fadeOut([this, raw = _widget.get()] {
|
|
if (_widget.get() == raw) {
|
|
_widget = nullptr;
|
|
}
|
|
});
|
|
|
|
++_data->state.itemIndex;
|
|
_data->state.leftTillShow = std::max(
|
|
_data->betweenDelay,
|
|
kStartDelayMin);
|
|
_start = now;
|
|
_timer.callOnce(_data->state.leftTillShow);
|
|
saveState();
|
|
}
|
|
|
|
void PlaybackSponsored::saveState() {
|
|
_session->sponsoredMessages().updateForVideo(
|
|
_itemId,
|
|
computeState().data);
|
|
}
|
|
|
|
PlaybackSponsored::State PlaybackSponsored::computeState() const {
|
|
auto result = State{ crl::now() };
|
|
if (!_data) {
|
|
return result;
|
|
}
|
|
result.data = _data->state;
|
|
if (!_start) {
|
|
return result;
|
|
}
|
|
const auto elapsed = result.now - _start;
|
|
result.data.leftTillShow -= elapsed;
|
|
return result;
|
|
}
|
|
|
|
rpl::lifetime &PlaybackSponsored::lifetime() {
|
|
return _lifetime;
|
|
}
|
|
|
|
bool PlaybackSponsored::Has(HistoryItem *item) {
|
|
return item
|
|
&& item->history()->session().sponsoredMessages().canHaveFor(item);
|
|
}
|
|
|
|
} // namespace Media::View
|