2021-10-18 17:21:49 +04:00
|
|
|
/*
|
|
|
|
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 "boxes/peers/peer_short_info_box.h"
|
|
|
|
|
|
|
|
#include "ui/widgets/labels.h"
|
2021-10-18 19:53:28 +04:00
|
|
|
#include "ui/widgets/scroll_area.h"
|
|
|
|
#include "ui/wrap/vertical_layout.h"
|
|
|
|
#include "ui/wrap/slide_wrap.h"
|
|
|
|
#include "ui/wrap/wrap.h"
|
2021-10-18 17:21:49 +04:00
|
|
|
#include "ui/image/image_prepare.h"
|
2021-10-18 19:53:28 +04:00
|
|
|
#include "ui/text/text_utilities.h"
|
|
|
|
#include "info/profile/info_profile_text.h"
|
2021-10-18 17:21:49 +04:00
|
|
|
#include "media/streaming/media_streaming_instance.h"
|
2021-10-18 18:17:09 +04:00
|
|
|
#include "media/streaming/media_streaming_player.h"
|
2021-10-18 17:21:49 +04:00
|
|
|
#include "lang/lang_keys.h"
|
|
|
|
#include "styles/style_layers.h"
|
|
|
|
#include "styles/style_info.h"
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
2021-10-18 19:53:28 +04:00
|
|
|
constexpr auto kShadowMaxAlpha = 80;
|
2021-10-19 18:48:10 +04:00
|
|
|
constexpr auto kInactiveBarOpacity = 0.5;
|
2021-10-18 19:53:28 +04:00
|
|
|
|
2021-10-18 17:21:49 +04:00
|
|
|
} // namespace
|
|
|
|
|
2021-10-20 17:31:26 +04:00
|
|
|
struct PeerShortInfoBox::CustomLabelStyle {
|
|
|
|
explicit CustomLabelStyle(const style::FlatLabel &original);
|
|
|
|
|
|
|
|
style::complex_color textFg;
|
|
|
|
style::FlatLabel st;
|
|
|
|
float64 opacity = 1.;
|
|
|
|
};
|
|
|
|
|
|
|
|
PeerShortInfoBox::CustomLabelStyle::CustomLabelStyle(
|
|
|
|
const style::FlatLabel &original)
|
|
|
|
: textFg([=, c = original.textFg]{
|
|
|
|
auto result = c->c;
|
|
|
|
result.setAlphaF(result.alphaF() * opacity);
|
|
|
|
return result;
|
|
|
|
})
|
|
|
|
, st(original) {
|
|
|
|
st.textFg = textFg.color();
|
|
|
|
}
|
|
|
|
|
2021-10-18 17:21:49 +04:00
|
|
|
PeerShortInfoBox::PeerShortInfoBox(
|
|
|
|
QWidget*,
|
|
|
|
PeerShortInfoType type,
|
|
|
|
rpl::producer<PeerShortInfoFields> fields,
|
|
|
|
rpl::producer<QString> status,
|
2021-10-18 18:17:09 +04:00
|
|
|
rpl::producer<PeerShortInfoUserpic> userpic,
|
|
|
|
Fn<bool()> videoPaused)
|
2021-10-18 17:21:49 +04:00
|
|
|
: _type(type)
|
|
|
|
, _fields(std::move(fields))
|
2021-10-20 17:31:26 +04:00
|
|
|
, _topRoundBackground(this)
|
|
|
|
, _scroll(this, st::shortInfoScroll)
|
2021-10-18 19:53:28 +04:00
|
|
|
, _rows(
|
2021-10-20 17:31:26 +04:00
|
|
|
_scroll->setOwnedWidget(
|
|
|
|
object_ptr<Ui::VerticalLayout>(
|
|
|
|
_scroll.data())))
|
|
|
|
, _cover(_rows->add(object_ptr<Ui::RpWidget>(_rows.get())))
|
|
|
|
, _nameStyle(std::make_unique<CustomLabelStyle>(st::shortInfoName))
|
|
|
|
, _name(_cover.get(), nameValue(), _nameStyle->st)
|
|
|
|
, _statusStyle(std::make_unique<CustomLabelStyle>(st::shortInfoStatus))
|
|
|
|
, _status(_cover.get(), std::move(status), _statusStyle->st)
|
2021-10-18 18:17:09 +04:00
|
|
|
, _videoPaused(std::move(videoPaused)) {
|
2021-10-18 17:21:49 +04:00
|
|
|
std::move(
|
|
|
|
userpic
|
|
|
|
) | rpl::start_with_next([=](PeerShortInfoUserpic &&value) {
|
|
|
|
applyUserpic(std::move(value));
|
|
|
|
}, lifetime());
|
2021-10-19 18:48:10 +04:00
|
|
|
|
|
|
|
style::PaletteChanged(
|
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
refreshBarImages();
|
|
|
|
}, lifetime());
|
2021-10-18 17:21:49 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
PeerShortInfoBox::~PeerShortInfoBox() = default;
|
|
|
|
|
|
|
|
rpl::producer<> PeerShortInfoBox::openRequests() const {
|
|
|
|
return _openRequests.events();
|
|
|
|
}
|
|
|
|
|
2021-10-19 18:48:10 +04:00
|
|
|
rpl::producer<int> PeerShortInfoBox::moveRequests() const {
|
|
|
|
return _moveRequests.events();
|
|
|
|
}
|
|
|
|
|
2021-10-18 17:21:49 +04:00
|
|
|
void PeerShortInfoBox::prepare() {
|
|
|
|
addButton(tr::lng_close(), [=] { closeBox(); });
|
|
|
|
|
|
|
|
// Perhaps a new lang key should be added for opening a group.
|
|
|
|
addLeftButton((_type == PeerShortInfoType::User)
|
|
|
|
? tr::lng_profile_send_message()
|
|
|
|
: (_type == PeerShortInfoType::Group)
|
|
|
|
? tr::lng_view_button_group()
|
|
|
|
: tr::lng_profile_view_channel(), [=] { _openRequests.fire({}); });
|
|
|
|
|
2021-10-18 19:53:28 +04:00
|
|
|
prepareRows();
|
|
|
|
|
2021-10-18 17:21:49 +04:00
|
|
|
setNoContentMargin(true);
|
2021-10-18 19:53:28 +04:00
|
|
|
|
2021-10-20 17:31:26 +04:00
|
|
|
_cover->resize(st::shortInfoWidth, st::shortInfoWidth);
|
|
|
|
_cover->paintRequest(
|
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
auto p = QPainter(_cover.get());
|
|
|
|
paintCover(p);
|
|
|
|
}, _cover->lifetime());
|
|
|
|
_cover->events(
|
|
|
|
) | rpl::filter([=](not_null<QEvent*> e) {
|
|
|
|
return (e->type() == QEvent::MouseButtonPress)
|
|
|
|
|| (e->type() == QEvent::MouseButtonDblClick);
|
|
|
|
}) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
|
|
|
const auto mouse = static_cast<QMouseEvent*>(e.get());
|
|
|
|
const auto x = mouse->pos().x();
|
|
|
|
if (mouse->button() != Qt::LeftButton) {
|
|
|
|
return;
|
|
|
|
} else if (/*_index > 0 && */x < st::shortInfoWidth / 3) {
|
|
|
|
_moveRequests.fire(-1);
|
|
|
|
} else if (/*_index + 1 < _count && */x >= st::shortInfoWidth / 3) {
|
|
|
|
_moveRequests.fire(1);
|
|
|
|
}
|
|
|
|
}, _cover->lifetime());
|
|
|
|
|
|
|
|
_topRoundBackground->resize(st::shortInfoWidth, st::boxRadius);
|
|
|
|
_topRoundBackground->paintRequest(
|
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
if (const auto use = fillRoundedTopHeight()) {
|
|
|
|
const auto width = _topRoundBackground->width();
|
|
|
|
const auto top = _topRoundBackground->height() - use;
|
|
|
|
const auto factor = style::DevicePixelRatio();
|
|
|
|
QPainter(_topRoundBackground.data()).drawImage(
|
|
|
|
QRect(0, top, width, use),
|
|
|
|
_roundedTop,
|
|
|
|
QRect(0, top * factor, width * factor, use * factor));
|
|
|
|
}
|
|
|
|
}, _topRoundBackground->lifetime());
|
|
|
|
|
|
|
|
_roundedTop = QImage(
|
|
|
|
_topRoundBackground->size() * style::DevicePixelRatio(),
|
|
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
|
|
_roundedTop.setDevicePixelRatio(style::DevicePixelRatio());
|
|
|
|
_roundedTopImage = _roundedTop;
|
|
|
|
_roundedTopImage.fill(Qt::transparent);
|
|
|
|
refreshRoundedTopImage(getDelegate()->style().bg->c);
|
2021-10-19 18:48:10 +04:00
|
|
|
|
2021-10-20 17:31:26 +04:00
|
|
|
setDimensionsToContent(st::shortInfoWidth, _rows);
|
2021-10-18 19:53:28 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void PeerShortInfoBox::prepareRows() {
|
|
|
|
using namespace Info::Profile;
|
|
|
|
|
|
|
|
auto addInfoLineGeneric = [&](
|
|
|
|
rpl::producer<QString> &&label,
|
|
|
|
rpl::producer<TextWithEntities> &&text,
|
|
|
|
const style::FlatLabel &textSt = st::infoLabeled) {
|
|
|
|
auto line = CreateTextWithLabel(
|
|
|
|
_rows,
|
|
|
|
rpl::duplicate(label) | Ui::Text::ToWithEntities(),
|
|
|
|
rpl::duplicate(text),
|
|
|
|
textSt,
|
|
|
|
st::shortInfoLabeledPadding);
|
2021-10-20 17:31:26 +04:00
|
|
|
_rows->add(object_ptr<Ui::OverrideMargins>(
|
|
|
|
_rows.get(),
|
|
|
|
std::move(line.wrap)));
|
2021-10-18 19:53:28 +04:00
|
|
|
|
|
|
|
rpl::combine(
|
|
|
|
std::move(label),
|
|
|
|
std::move(text)
|
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
_rows->resizeToWidth(st::shortInfoWidth);
|
|
|
|
}, _rows->lifetime());
|
|
|
|
|
|
|
|
//line.text->setClickHandlerFilter(infoClickFilter);
|
|
|
|
return line.text;
|
|
|
|
};
|
|
|
|
auto addInfoLine = [&](
|
|
|
|
rpl::producer<QString> &&label,
|
|
|
|
rpl::producer<TextWithEntities> &&text,
|
|
|
|
const style::FlatLabel &textSt = st::infoLabeled) {
|
|
|
|
return addInfoLineGeneric(
|
|
|
|
std::move(label),
|
|
|
|
std::move(text),
|
|
|
|
textSt);
|
|
|
|
};
|
|
|
|
auto addInfoOneLine = [&](
|
|
|
|
rpl::producer<QString> &&label,
|
|
|
|
rpl::producer<TextWithEntities> &&text,
|
|
|
|
const QString &contextCopyText) {
|
|
|
|
auto result = addInfoLine(
|
|
|
|
std::move(label),
|
|
|
|
std::move(text),
|
|
|
|
st::infoLabeledOneLine);
|
|
|
|
result->setDoubleClickSelectsParagraph(true);
|
|
|
|
result->setContextCopyText(contextCopyText);
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
addInfoOneLine(
|
|
|
|
tr::lng_info_link_label(),
|
|
|
|
linkValue(),
|
|
|
|
tr::lng_context_copy_link(tr::now));
|
|
|
|
addInfoOneLine(
|
|
|
|
tr::lng_info_mobile_label(),
|
|
|
|
phoneValue() | Ui::Text::ToWithEntities(),
|
|
|
|
tr::lng_profile_copy_phone(tr::now));
|
|
|
|
auto label = _fields.current().isBio
|
|
|
|
? tr::lng_info_bio_label()
|
|
|
|
: tr::lng_info_about_label();
|
|
|
|
addInfoLine(std::move(label), aboutValue());
|
|
|
|
addInfoOneLine(
|
|
|
|
tr::lng_info_username_label(),
|
|
|
|
usernameValue() | Ui::Text::ToWithEntities(),
|
|
|
|
tr::lng_context_copy_mention(tr::now));
|
2021-10-18 17:21:49 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
RectParts PeerShortInfoBox::customCornersFilling() {
|
|
|
|
return RectPart::FullTop;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerShortInfoBox::resizeEvent(QResizeEvent *e) {
|
|
|
|
BoxContent::resizeEvent(e);
|
2021-10-18 19:53:28 +04:00
|
|
|
|
|
|
|
_name->moveToLeft(
|
|
|
|
st::shortInfoNamePosition.x(),
|
|
|
|
st::shortInfoWidth - st::shortInfoNamePosition.y() - _name->height(),
|
|
|
|
width());
|
|
|
|
_status->moveToLeft(
|
|
|
|
st::shortInfoStatusPosition.x(),
|
|
|
|
(st::shortInfoWidth
|
|
|
|
- st::shortInfoStatusPosition.y()
|
|
|
|
- _status->height()),
|
|
|
|
height());
|
|
|
|
_rows->resizeToWidth(st::shortInfoWidth);
|
2021-10-20 17:31:26 +04:00
|
|
|
_scroll->resize(st::shortInfoWidth, height());
|
|
|
|
_scroll->move(0, 0);
|
|
|
|
_topRoundBackground->move(0, 0);
|
2021-10-18 17:21:49 +04:00
|
|
|
}
|
|
|
|
|
2021-10-20 17:31:26 +04:00
|
|
|
void PeerShortInfoBox::paintCover(QPainter &p) {
|
2021-10-18 18:17:09 +04:00
|
|
|
checkStreamedIsStarted();
|
|
|
|
const auto frame = currentVideoFrame();
|
|
|
|
auto paused = _videoPaused && _videoPaused();
|
|
|
|
if (frame.isNull() && _userpicImage.isNull()) {
|
|
|
|
auto image = QImage(
|
2021-10-20 17:31:26 +04:00
|
|
|
_cover->size() * style::DevicePixelRatio(),
|
2021-10-18 18:17:09 +04:00
|
|
|
QImage::Format_ARGB32_Premultiplied);
|
2021-10-18 17:21:49 +04:00
|
|
|
image.fill(Qt::black);
|
|
|
|
Images::prepareRound(
|
|
|
|
image,
|
|
|
|
ImageRoundRadius::Small,
|
|
|
|
RectPart::TopLeft | RectPart::TopRight);
|
2021-10-18 18:17:09 +04:00
|
|
|
_userpicImage = std::move(image);
|
|
|
|
}
|
2021-10-18 19:53:28 +04:00
|
|
|
|
2021-10-20 17:31:26 +04:00
|
|
|
paintCoverImage(p, frame.isNull() ? _userpicImage : frame);
|
2021-10-19 18:48:10 +04:00
|
|
|
paintBars(p);
|
|
|
|
paintShadow(p);
|
2021-10-20 17:31:26 +04:00
|
|
|
if (_videoInstance && _videoInstance->ready() && !paused) {
|
|
|
|
_videoInstance->markFrameShown();
|
|
|
|
}
|
2021-10-19 18:48:10 +04:00
|
|
|
}
|
|
|
|
|
2021-10-20 17:31:26 +04:00
|
|
|
void PeerShortInfoBox::paintCoverImage(QPainter &p, const QImage &image) {
|
|
|
|
const auto roundedWidth = _topRoundBackground->width();
|
|
|
|
const auto roundedHeight = _topRoundBackground->height();
|
|
|
|
const auto scrollTop = _scroll->scrollTop();
|
|
|
|
const auto covered = (st::shortInfoWidth - scrollTop);
|
|
|
|
if (covered <= 0) {
|
|
|
|
return;
|
|
|
|
} else if (!scrollTop) {
|
|
|
|
p.drawImage(_cover->rect(), image);
|
|
|
|
return;
|
2021-10-19 18:48:10 +04:00
|
|
|
}
|
2021-10-20 17:31:26 +04:00
|
|
|
const auto fill = covered - roundedHeight;
|
|
|
|
const auto top = _cover->height() - fill;
|
|
|
|
const auto factor = style::DevicePixelRatio();
|
|
|
|
if (fill > 0) {
|
|
|
|
p.drawImage(
|
|
|
|
QRect(0, top, roundedWidth, fill),
|
|
|
|
image,
|
|
|
|
QRect(0, top * factor, roundedWidth * factor, fill * factor));
|
|
|
|
}
|
|
|
|
if (covered <= 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto rounded = std::min(covered, roundedHeight);
|
|
|
|
const auto from = top - rounded;
|
|
|
|
auto q = QPainter(&_roundedTopImage);
|
|
|
|
q.drawImage(
|
|
|
|
QRect(0, 0, roundedWidth, rounded),
|
|
|
|
image,
|
|
|
|
QRect(0, from * factor, roundedWidth * factor, rounded * factor));
|
|
|
|
q.end();
|
|
|
|
Images::prepareRound(
|
|
|
|
_roundedTopImage,
|
|
|
|
ImageRoundRadius::Small,
|
|
|
|
RectPart::TopLeft | RectPart::TopRight);
|
|
|
|
p.drawImage(
|
|
|
|
QRect(0, from, roundedWidth, rounded),
|
|
|
|
_roundedTopImage,
|
|
|
|
QRect(0, 0, roundedWidth * factor, rounded * factor));
|
2021-10-19 18:48:10 +04:00
|
|
|
}
|
|
|
|
|
2021-10-20 17:31:26 +04:00
|
|
|
int PeerShortInfoBox::fillRoundedTopHeight() {
|
|
|
|
const auto roundedWidth = _topRoundBackground->width();
|
|
|
|
const auto roundedHeight = _topRoundBackground->height();
|
|
|
|
const auto scrollTop = _scroll->scrollTop();
|
|
|
|
const auto covered = (st::shortInfoWidth - scrollTop);
|
|
|
|
if (covered >= roundedHeight) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
const auto &color = getDelegate()->style().bg->c;
|
|
|
|
if (_roundedTopColor != color) {
|
|
|
|
refreshRoundedTopImage(color);
|
2021-10-19 18:48:10 +04:00
|
|
|
}
|
2021-10-20 17:31:26 +04:00
|
|
|
return roundedHeight - covered;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerShortInfoBox::refreshRoundedTopImage(const QColor &color) {
|
|
|
|
_roundedTopColor = color;
|
|
|
|
_roundedTop.fill(color);
|
|
|
|
Images::prepareRound(
|
|
|
|
_roundedTop,
|
|
|
|
ImageRoundRadius::Small,
|
|
|
|
RectPart::TopLeft | RectPart::TopRight);
|
2021-10-19 18:48:10 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void PeerShortInfoBox::paintBars(QPainter &p) {
|
|
|
|
const auto height = st::shortInfoLinePadding * 2 + st::shortInfoLine;
|
2021-10-20 17:31:26 +04:00
|
|
|
const auto factor = style::DevicePixelRatio();
|
2021-10-19 18:48:10 +04:00
|
|
|
if (_shadowTop.isNull()) {
|
|
|
|
_shadowTop = Images::GenerateShadow(height, kShadowMaxAlpha, 0);
|
2021-10-20 17:31:26 +04:00
|
|
|
_shadowTop = _shadowTop.scaled(
|
|
|
|
QSize(st::shortInfoWidth, height) * factor);
|
|
|
|
Images::prepareRound(
|
|
|
|
_shadowTop,
|
|
|
|
ImageRoundRadius::Small,
|
|
|
|
RectPart::TopLeft | RectPart::TopRight);
|
2021-10-19 18:48:10 +04:00
|
|
|
}
|
2021-10-20 17:31:26 +04:00
|
|
|
const auto scrollTop = _scroll->scrollTop();
|
|
|
|
const auto shadowRect = QRect(0, scrollTop, st::shortInfoWidth, height);
|
2021-10-19 18:48:10 +04:00
|
|
|
p.drawImage(
|
|
|
|
shadowRect,
|
|
|
|
_shadowTop,
|
|
|
|
QRect(0, 0, _shadowTop.width(), height * factor));
|
2021-10-20 17:31:26 +04:00
|
|
|
const auto hiddenAt = st::shortInfoWidth - st::shortInfoNamePosition.y();
|
|
|
|
if (!_smallWidth || scrollTop >= hiddenAt) {
|
2021-10-19 18:48:10 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto top = st::shortInfoLinePadding;
|
|
|
|
const auto skip = st::shortInfoLineSkip;
|
|
|
|
const auto full = (st::shortInfoWidth - 2 * top - (_count - 1) * skip);
|
|
|
|
const auto width = full / float64(_count);
|
2021-10-20 17:31:26 +04:00
|
|
|
const auto masterOpacity = 1. - (scrollTop / float64(hiddenAt));
|
|
|
|
const auto inactiveOpacity = masterOpacity * kInactiveBarOpacity;
|
2021-10-19 18:48:10 +04:00
|
|
|
for (auto i = 0; i != _count; ++i) {
|
|
|
|
const auto left = top + i * (width + skip);
|
|
|
|
const auto right = left + width;
|
2021-10-20 17:31:26 +04:00
|
|
|
p.setOpacity((i == _index) ? masterOpacity : inactiveOpacity);
|
2021-10-19 18:48:10 +04:00
|
|
|
p.drawImage(
|
|
|
|
qRound(left),
|
2021-10-20 17:31:26 +04:00
|
|
|
scrollTop + top,
|
2021-10-19 18:48:10 +04:00
|
|
|
((qRound(right) == qRound(left) + _smallWidth)
|
|
|
|
? _barSmall
|
|
|
|
: _barLarge));
|
|
|
|
}
|
|
|
|
p.setOpacity(1.);
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerShortInfoBox::paintShadow(QPainter &p) {
|
|
|
|
if (_shadowBottom.isNull()) {
|
|
|
|
_shadowBottom = Images::GenerateShadow(
|
2021-10-18 19:53:28 +04:00
|
|
|
st::shortInfoShadowHeight,
|
|
|
|
0,
|
|
|
|
kShadowMaxAlpha);
|
|
|
|
}
|
2021-10-20 17:31:26 +04:00
|
|
|
const auto scrollTop = _scroll->scrollTop();
|
|
|
|
const auto shadowTop = st::shortInfoWidth - st::shortInfoShadowHeight;
|
|
|
|
if (scrollTop >= shadowTop) {
|
|
|
|
_name->hide();
|
|
|
|
_status->hide();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto opacity = 1. - (scrollTop / float64(shadowTop));
|
|
|
|
_nameStyle->opacity = opacity;
|
|
|
|
_nameStyle->textFg.refresh();
|
|
|
|
_name->show();
|
|
|
|
_statusStyle->opacity = opacity;
|
|
|
|
_statusStyle->textFg.refresh();
|
|
|
|
_status->show();
|
|
|
|
p.setOpacity(opacity);
|
2021-10-18 19:53:28 +04:00
|
|
|
const auto shadowRect = QRect(
|
|
|
|
0,
|
2021-10-20 17:31:26 +04:00
|
|
|
shadowTop,
|
2021-10-18 19:53:28 +04:00
|
|
|
st::shortInfoWidth,
|
|
|
|
st::shortInfoShadowHeight);
|
|
|
|
const auto factor = style::DevicePixelRatio();
|
|
|
|
p.drawImage(
|
|
|
|
shadowRect,
|
2021-10-19 18:48:10 +04:00
|
|
|
_shadowBottom,
|
2021-10-18 19:53:28 +04:00
|
|
|
QRect(
|
|
|
|
0,
|
|
|
|
0,
|
2021-10-19 18:48:10 +04:00
|
|
|
_shadowBottom.width(),
|
2021-10-18 19:53:28 +04:00
|
|
|
st::shortInfoShadowHeight * factor));
|
2021-10-20 17:31:26 +04:00
|
|
|
p.setOpacity(1.);
|
2021-10-18 18:17:09 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
QImage PeerShortInfoBox::currentVideoFrame() const {
|
|
|
|
const auto coverSize = st::shortInfoWidth;
|
|
|
|
const auto size = QSize(coverSize, coverSize);
|
|
|
|
const auto request = Media::Streaming::FrameRequest{
|
|
|
|
.resize = size * style::DevicePixelRatio(),
|
|
|
|
.outer = size,
|
|
|
|
.radius = ImageRoundRadius::Small,
|
|
|
|
.corners = RectPart::TopLeft | RectPart::TopRight,
|
|
|
|
};
|
|
|
|
return (_videoInstance
|
|
|
|
&& _videoInstance->player().ready()
|
|
|
|
&& !_videoInstance->player().videoSize().isEmpty())
|
|
|
|
? _videoInstance->frame(request)
|
|
|
|
: QImage();
|
2021-10-18 17:21:49 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
rpl::producer<QString> PeerShortInfoBox::nameValue() const {
|
|
|
|
return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
|
|
|
|
return fields.name;
|
|
|
|
}) | rpl::distinct_until_changed();
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:53:28 +04:00
|
|
|
rpl::producer<TextWithEntities> PeerShortInfoBox::linkValue() const {
|
|
|
|
return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
|
|
|
|
return Ui::Text::Link(fields.link, fields.link);
|
|
|
|
}) | rpl::distinct_until_changed();
|
|
|
|
}
|
|
|
|
|
|
|
|
rpl::producer<QString> PeerShortInfoBox::phoneValue() const {
|
|
|
|
return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
|
|
|
|
return fields.phone;
|
|
|
|
}) | rpl::distinct_until_changed();
|
|
|
|
}
|
|
|
|
|
|
|
|
rpl::producer<QString> PeerShortInfoBox::usernameValue() const {
|
|
|
|
return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
|
|
|
|
return fields.username;
|
|
|
|
}) | rpl::distinct_until_changed();
|
|
|
|
}
|
|
|
|
|
|
|
|
rpl::producer<TextWithEntities> PeerShortInfoBox::aboutValue() const {
|
|
|
|
return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
|
|
|
|
return fields.about;
|
|
|
|
}) | rpl::distinct_until_changed();
|
|
|
|
}
|
|
|
|
|
2021-10-18 17:21:49 +04:00
|
|
|
void PeerShortInfoBox::applyUserpic(PeerShortInfoUserpic &&value) {
|
2021-10-19 18:48:10 +04:00
|
|
|
if (_index != value.index) {
|
|
|
|
_index = value.index;
|
|
|
|
update();
|
|
|
|
}
|
|
|
|
if (_count != value.count) {
|
|
|
|
_count = value.count;
|
2021-10-20 17:31:26 +04:00
|
|
|
refreshCoverCursor();
|
2021-10-19 18:48:10 +04:00
|
|
|
refreshBarImages();
|
|
|
|
update();
|
|
|
|
}
|
2021-10-19 19:07:01 +04:00
|
|
|
if (value.photo.isNull()) {
|
|
|
|
const auto videoChanged = _videoInstance
|
|
|
|
? (_videoInstance->shared() != value.videoDocument)
|
|
|
|
: (value.videoDocument != nullptr);
|
|
|
|
const auto frame = videoChanged ? currentVideoFrame() : QImage();
|
|
|
|
if (!frame.isNull()) {
|
|
|
|
_userpicImage = frame;
|
|
|
|
}
|
|
|
|
} else if (_userpicImage.cacheKey() != value.photo.cacheKey()) {
|
2021-10-18 17:21:49 +04:00
|
|
|
_userpicImage = std::move(value.photo);
|
|
|
|
update();
|
|
|
|
}
|
2021-10-19 18:48:10 +04:00
|
|
|
if (!value.videoDocument) {
|
|
|
|
_videoInstance = nullptr;
|
|
|
|
} else if (!_videoInstance
|
|
|
|
|| _videoInstance->shared() != value.videoDocument) {
|
2021-10-18 18:17:09 +04:00
|
|
|
using namespace Media::Streaming;
|
|
|
|
_videoInstance = std::make_unique<Instance>(
|
|
|
|
std::move(value.videoDocument),
|
|
|
|
[=] { videoWaiting(); });
|
|
|
|
_videoStartPosition = value.videoStartPosition;
|
|
|
|
_videoInstance->lockPlayer();
|
|
|
|
_videoInstance->player().updates(
|
|
|
|
) | rpl::start_with_next_error([=](Update &&update) {
|
|
|
|
handleStreamingUpdate(std::move(update));
|
|
|
|
}, [=](Error &&error) {
|
|
|
|
handleStreamingError(std::move(error));
|
|
|
|
}, _videoInstance->lifetime());
|
|
|
|
if (_videoInstance->ready()) {
|
|
|
|
streamingReady(base::duplicate(_videoInstance->info()));
|
|
|
|
}
|
|
|
|
if (!_videoInstance->valid()) {
|
|
|
|
_videoInstance = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerShortInfoBox::checkStreamedIsStarted() {
|
|
|
|
if (!_videoInstance) {
|
|
|
|
return;
|
|
|
|
} else if (_videoInstance->paused()) {
|
|
|
|
_videoInstance->resume();
|
|
|
|
}
|
|
|
|
if (!_videoInstance
|
|
|
|
|| _videoInstance->active()
|
|
|
|
|| _videoInstance->failed()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto options = Media::Streaming::PlaybackOptions();
|
|
|
|
options.position = _videoStartPosition;
|
|
|
|
options.mode = Media::Streaming::Mode::Video;
|
|
|
|
options.loop = true;
|
|
|
|
_videoInstance->play(options);
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerShortInfoBox::handleStreamingUpdate(
|
|
|
|
Media::Streaming::Update &&update) {
|
|
|
|
using namespace Media::Streaming;
|
|
|
|
|
|
|
|
v::match(update.data, [&](Information &update) {
|
|
|
|
streamingReady(std::move(update));
|
|
|
|
}, [&](const PreloadedVideo &update) {
|
|
|
|
}, [&](const UpdateVideo &update) {
|
2021-10-20 17:31:26 +04:00
|
|
|
_cover->update();
|
2021-10-18 18:17:09 +04:00
|
|
|
}, [&](const PreloadedAudio &update) {
|
|
|
|
}, [&](const UpdateAudio &update) {
|
|
|
|
}, [&](const WaitingForData &update) {
|
|
|
|
}, [&](MutedByOther) {
|
|
|
|
}, [&](Finished) {
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerShortInfoBox::handleStreamingError(
|
|
|
|
Media::Streaming::Error &&error) {
|
|
|
|
//_streamedPhoto->setVideoPlaybackFailed();
|
|
|
|
//_streamedPhoto = nullptr;
|
|
|
|
_videoInstance = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerShortInfoBox::streamingReady(Media::Streaming::Information &&info) {
|
2021-10-20 17:31:26 +04:00
|
|
|
_cover->update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerShortInfoBox::refreshCoverCursor() {
|
|
|
|
const auto cursor = (_count > 1)
|
|
|
|
? style::cur_pointer
|
|
|
|
: style::cur_default;
|
|
|
|
if (_cursor != cursor) {
|
|
|
|
_cursor = cursor;
|
|
|
|
_cover->setCursor(_cursor);
|
|
|
|
}
|
2021-10-18 18:17:09 +04:00
|
|
|
}
|
|
|
|
|
2021-10-19 18:48:10 +04:00
|
|
|
void PeerShortInfoBox::refreshBarImages() {
|
|
|
|
if (_count < 2) {
|
|
|
|
_smallWidth = _largeWidth = 0;
|
|
|
|
_barSmall = _barLarge = QImage();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto width = st::shortInfoWidth - 2 * st::shortInfoLinePadding;
|
|
|
|
_smallWidth = (width - (_count - 1) * st::shortInfoLineSkip) / _count;
|
|
|
|
if (_smallWidth < st::shortInfoLine) {
|
|
|
|
_smallWidth = _largeWidth = 0;
|
|
|
|
_barSmall = _barLarge = QImage();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_largeWidth = _smallWidth + 1;
|
|
|
|
const auto makeBar = [](int size) {
|
|
|
|
const auto radius = st::shortInfoLine / 2.;
|
|
|
|
auto result = QImage(
|
|
|
|
QSize(size, st::shortInfoLine) * style::DevicePixelRatio(),
|
|
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
|
|
result.setDevicePixelRatio(style::DevicePixelRatio());
|
|
|
|
result.fill(Qt::transparent);
|
|
|
|
auto p = QPainter(&result);
|
|
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
|
|
p.setPen(Qt::NoPen);
|
|
|
|
p.setBrush(st::groupCallVideoTextFg);
|
|
|
|
p.drawRoundedRect(0, 0, size, st::shortInfoLine, radius, radius);
|
|
|
|
p.end();
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
_barSmall = makeBar(_smallWidth);
|
|
|
|
_barLarge = makeBar(_largeWidth);
|
|
|
|
}
|
|
|
|
|
2021-10-18 18:17:09 +04:00
|
|
|
QRect PeerShortInfoBox::radialRect() const {
|
2021-10-20 17:31:26 +04:00
|
|
|
const auto cover = _cover->rect();
|
2021-10-18 18:17:09 +04:00
|
|
|
return cover;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PeerShortInfoBox::videoWaiting() {
|
|
|
|
if (!anim::Disabled()) {
|
|
|
|
update(radialRect());
|
|
|
|
}
|
2021-10-18 17:21:49 +04:00
|
|
|
}
|