Add privacy badge to stories userpic.

This commit is contained in:
John Preston 2023-07-19 17:31:52 +04:00
parent ffd691e556
commit 99926be57a
12 changed files with 217 additions and 18 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 B

View File

@ -16,7 +16,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/layers/box_content.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/painter.h"
#include "ui/rp_widget.h"
#include "styles/style_media_view.h"
@ -32,6 +34,155 @@ struct Timestamp {
TimeId changes = 0;
};
struct PrivacyBadge {
const style::icon *icon = nullptr;
const style::color *bg1 = nullptr;
const style::color *bg2 = nullptr;
};
class UserpicBadge final : public Ui::RpWidget {
public:
UserpicBadge(
not_null<QWidget*> userpic,
PrivacyBadge badge,
Fn<void()> clicked);
private:
bool eventFilter(QObject *o, QEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void updateGeometry();
const not_null<QWidget*> _userpic;
const PrivacyBadge _badgeData;
const std::unique_ptr<Ui::AbstractButton> _clickable;
QRect _badge;
QImage _layer;
bool _grabbing = false;
};
[[nodiscard]] PrivacyBadge LookupPrivacyBadge(Data::StoryPrivacy privacy) {
using namespace Data;
static const auto badges = base::flat_map<StoryPrivacy, PrivacyBadge>{
{ StoryPrivacy::CloseFriends, PrivacyBadge{
&st::storiesBadgeCloseFriends,
&st::historyPeer2UserpicBg,
&st::historyPeer2UserpicBg2,
} },
{ StoryPrivacy::Contacts, PrivacyBadge{
&st::storiesBadgeContacts,
&st::historyPeer5UserpicBg,
&st::historyPeer5UserpicBg2,
} },
{ StoryPrivacy::SelectedContacts, PrivacyBadge{
&st::storiesBadgeSelectedContacts,
&st::historyPeer8UserpicBg,
&st::historyPeer8UserpicBg2,
} },
};
if (const auto i = badges.find(privacy); i != end(badges)) {
return i->second;
}
return {};
}
UserpicBadge::UserpicBadge(
not_null<QWidget*> userpic,
PrivacyBadge badge,
Fn<void()> clicked)
: RpWidget(userpic->parentWidget())
, _userpic(userpic)
, _badgeData(badge)
, _clickable(std::make_unique<Ui::AbstractButton>(parentWidget())) {
_clickable->setClickedCallback(std::move(clicked));
userpic->installEventFilter(this);
updateGeometry();
setAttribute(Qt::WA_TransparentForMouseEvents);
Ui::PostponeCall(this, [=] {
_userpic->raise();
});
show();
}
bool UserpicBadge::eventFilter(QObject *o, QEvent *e) {
if (o != _userpic) {
return false;
}
const auto type = e->type();
switch (type) {
case QEvent::Move:
case QEvent::Resize:
updateGeometry();
return false;
case QEvent::Paint:
return !_grabbing;
}
return false;
}
void UserpicBadge::paintEvent(QPaintEvent *e) {
const auto ratio = style::DevicePixelRatio();
const auto layerSize = size() * ratio;
if (_layer.size() != layerSize) {
_layer = QImage(layerSize, QImage::Format_ARGB32_Premultiplied);
_layer.setDevicePixelRatio(ratio);
}
_layer.fill(Qt::transparent);
auto q = QPainter(&_layer);
_grabbing = true;
Ui::RenderWidget(q, _userpic);
_grabbing = false;
auto hq = PainterHighQualityEnabler(q);
auto pen = st::transparent->p;
pen.setWidthF(st::storiesBadgeOutline);
const auto half = st::storiesBadgeOutline / 2.;
auto outer = QRectF(_badge).marginsAdded({ half, half, half, half });
auto gradient = QLinearGradient(outer.topLeft(), outer.bottomLeft());
gradient.setStops({
{ 0., (*_badgeData.bg1)->c },
{ 1., (*_badgeData.bg2)->c },
});
q.setPen(pen);
q.setBrush(gradient);
q.setCompositionMode(QPainter::CompositionMode_Source);
q.drawEllipse(outer);
q.setCompositionMode(QPainter::CompositionMode_SourceOver);
_badgeData.icon->paintInCenter(q, _badge);
q.end();
QPainter(this).drawImage(0, 0, _layer);
}
void UserpicBadge::updateGeometry() {
const auto width = _userpic->width() + st::storiesBadgeShift.x();
const auto height = _userpic->height() + st::storiesBadgeShift.y();
setGeometry(QRect(_userpic->pos(), QSize{ width, height }));
const auto inner = QRect(QPoint(), _badgeData.icon->size());
const auto badge = inner.marginsAdded(st::storiesBadgePadding).size();
_badge = QRect(
QPoint(width - badge.width(), height - badge.height()),
badge);
_clickable->setGeometry(_badge.translated(pos()));
update();
}
[[nodiscard]] std::unique_ptr<Ui::RpWidget> MakePrivacyBadge(
not_null<QWidget*> userpic,
Data::StoryPrivacy privacy,
Fn<void()> clicked) {
const auto badge = LookupPrivacyBadge(privacy);
if (!badge.icon) {
return nullptr;
}
return std::make_unique<UserpicBadge>(
userpic,
badge,
std::move(clicked));
}
[[nodiscard]] Timestamp ComposeTimestamp(TimeId when, TimeId now) {
const auto minutes = (now - when) / 60;
if (!minutes) {
@ -102,42 +253,41 @@ Header::Header(not_null<Controller*> controller)
, _dateUpdateTimer([=] { updateDateText(); }) {
}
Header::~Header() {
}
Header::~Header() = default;
void Header::show(HeaderData data) {
if (_data == data) {
return;
}
const auto nameDataChanged = !_data
|| (_data->user != data.user)
const auto userChanged = !_data
|| (_data->user != data.user);
const auto nameDataChanged = userChanged
|| !_name
|| (_data->fullCount != data.fullCount)
|| (data.fullCount && _data->fullIndex != data.fullIndex);
_data = data;
if (nameDataChanged) {
if (userChanged) {
_date = nullptr;
_name = nullptr;
_userpic = nullptr;
_info = nullptr;
_privacy = nullptr;
const auto parent = _controller->wrap();
auto widget = std::make_unique<Ui::AbstractButton>(parent);
auto widget = std::make_unique<Ui::RpWidget>(parent);
const auto raw = widget.get();
raw->setClickedCallback([=] {
_info = std::make_unique<Ui::AbstractButton>(raw);
_info->setClickedCallback([=] {
_controller->uiShow()->show(PrepareShortInfoBox(_data->user));
});
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
_userpic = std::make_unique<Ui::UserpicButton>(
raw,
data.user,
st::storiesHeaderPhoto);
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
userpic->show();
userpic->move(
_userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
_userpic->show();
_userpic->move(
st::storiesHeaderMargin.left(),
st::storiesHeaderMargin.top());
const auto name = Ui::CreateChild<Ui::FlatLabel>(
raw,
rpl::single(ComposeName(data)),
st::storiesHeaderName);
name->setAttribute(Qt::WA_TransparentForMouseEvents);
name->setOpacity(kNameOpacity);
name->move(st::storiesHeaderNamePosition);
raw->show();
_widget = std::move(widget);
@ -146,6 +296,26 @@ void Header::show(HeaderData data) {
raw->setGeometry(layout.header);
}, raw->lifetime());
}
if (nameDataChanged) {
_name = std::make_unique<Ui::FlatLabel>(
_widget.get(),
rpl::single(ComposeName(data)),
st::storiesHeaderName);
_name->setAttribute(Qt::WA_TransparentForMouseEvents);
_name->setOpacity(kNameOpacity);
_name->move(st::storiesHeaderNamePosition);
_name->show();
rpl::combine(
_name->widthValue(),
_widget->heightValue()
) | rpl::start_with_next([=](int width, int height) {
if (_date) {
_info->setGeometry(
{ 0, 0, std::max(width, _date->width()), height });
}
}, _name->lifetime());
}
auto timestamp = ComposeDetails(data, base::unixtime::now());
_date = std::make_unique<Ui::FlatLabel>(
_widget.get(),
@ -156,6 +326,16 @@ void Header::show(HeaderData data) {
_date->show();
_date->move(st::storiesHeaderDatePosition);
_date->widthValue(
) | rpl::start_with_next([=](int width) {
_info->setGeometry(
{ 0, 0, std::max(width, _name->width()), _widget->height() });
}, _name->lifetime());
_privacy = MakePrivacyBadge(_userpic.get(), data.privacy, [=] {
});
if (timestamp.changes > 0) {
_dateUpdateTimer.callOnce(timestamp.changes * crl::time(1000));
}

View File

@ -17,6 +17,11 @@ enum class StoryPrivacy : uchar;
namespace Ui {
class RpWidget;
class FlatLabel;
class IconButton;
class AbstractButton;
class UserpicButton;
template <typename Widget>
class FadeWrap;
} // namespace Ui
namespace Media::Stories {
@ -51,7 +56,14 @@ private:
const not_null<Controller*> _controller;
std::unique_ptr<Ui::RpWidget> _widget;
std::unique_ptr<Ui::AbstractButton> _info;
std::unique_ptr<Ui::UserpicButton> _userpic;
std::unique_ptr<Ui::FlatLabel> _name;
std::unique_ptr<Ui::FlatLabel> _date;
std::unique_ptr<Ui::IconButton> _playPause;
std::unique_ptr<Ui::IconButton> _volumeToggle;
std::unique_ptr<Ui::FadeWrap<Ui::RpWidget>> _volume;
std::unique_ptr<Ui::RpWidget> _privacy;
std::optional<HeaderData> _data;
base::Timer _dateUpdateTimer;

View File

@ -844,3 +844,10 @@ storiesReportBox: ReportBox(defaultReportBox) {
storiesActionToast: Toast(defaultToast) {
maxWidth: 320px;
}
storiesBadgeCloseFriends: icon{{ "mediaview/mini_close_friends", historyPeerUserpicFg }};
storiesBadgeContacts: icon{{ "mediaview/mini_contacts", historyPeerUserpicFg }};
storiesBadgeSelectedContacts: icon{{ "mediaview/mini_selected_contacts", historyPeerUserpicFg }};
storiesBadgePadding: margins(1px, 1px, 1px, 1px);
storiesBadgeOutline: 2px;
storiesBadgeShift: point(5px, 4px);