Show nice tooltips about story privacy / silence.

This commit is contained in:
John Preston 2023-07-24 17:01:13 +04:00
parent 320db83155
commit 2323aef899
7 changed files with 233 additions and 32 deletions

View File

@ -3833,6 +3833,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stories_no_views" = "No views";
"lng_stories_unsupported" = "This story is not supported\nby your version of Telegram.";
"lng_stories_cant_reply" = "You can't reply to this story.";
"lng_stories_about_silent" = "This video has no sound.";
"lng_stories_about_close_friends" = "You're seeing this story because {user} added you to their list of **Close Friends**.";
"lng_stories_about_contacts" = "Only {user}'s contacts can view this story.";
"lng_stories_about_selected_contacts" = "Only some contacts {user} selected can view this story.";
"lng_stories_about_close_friends_my" = "Only your list of **Close Friends** can view this story.";
"lng_stories_about_contacts_my" = "Only your contacts can view this story.";
"lng_stories_about_selected_contacts_my" = "Only some contacts you selected can view this story.";
"lng_stories_click_to_view" = "Click here to view updates from {users}.";
"lng_stories_click_to_view_and_one" = "{accumulated}, {user}";
"lng_stories_click_to_view_and_last" = "{accumulated} and {user}";
"lng_stories_my_title" = "Saved Stories";
"lng_stories_archive_button" = "Stories Archive";

View File

@ -322,8 +322,18 @@ Controller::Controller(not_null<Delegate*> delegate)
_delegate->storiesLayerShown(
) | rpl::start_with_next([=](bool shown) {
_layerShown = shown;
updatePlayingAllowed();
if (_layerShown != shown) {
_layerShown = shown;
updatePlayingAllowed();
}
}, _lifetime);
_header->tooltipShownValue(
) | rpl::start_with_next([=](bool shown) {
if (_tooltipShown != shown) {
_tooltipShown = shown;
updatePlayingAllowed();
}
}, _lifetime);
const auto window = _wrap->window()->windowHandle();
@ -961,7 +971,8 @@ void Controller::updatePlayingAllowed() {
&& !_captionFullView
&& !_captionExpanded
&& !_layerShown
&& !_menuShown);
&& !_menuShown
&& !_tooltipShown);
}
void Controller::setPlayingAllowed(bool allowed) {

View File

@ -257,6 +257,7 @@ private:
bool _hasSendText = false;
bool _layerShown = false;
bool _menuShown = false;
bool _tooltipShown = false;
bool _paused = false;
FullStoryId _shown;

View File

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/tooltip.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/painter.h"
#include "ui/rp_widget.h"
@ -50,10 +51,9 @@ struct PrivacyBadge {
class UserpicBadge final : public Ui::RpWidget {
public:
UserpicBadge(
not_null<QWidget*> userpic,
PrivacyBadge badge,
Fn<void()> clicked);
UserpicBadge(not_null<QWidget*> userpic, PrivacyBadge badge);
[[nodiscard]] QRect badgeGeometry() const;
private:
bool eventFilter(QObject *o, QEvent *e) override;
@ -63,7 +63,6 @@ private:
const not_null<QWidget*> _userpic;
const PrivacyBadge _badgeData;
const std::unique_ptr<Ui::AbstractButton> _clickable;
QRect _badge;
QImage _layer;
bool _grabbing = false;
@ -95,15 +94,10 @@ private:
return {};
}
UserpicBadge::UserpicBadge(
not_null<QWidget*> userpic,
PrivacyBadge badge,
Fn<void()> clicked)
UserpicBadge::UserpicBadge(not_null<QWidget*> userpic, PrivacyBadge badge)
: RpWidget(userpic->parentWidget())
, _userpic(userpic)
, _badgeData(badge)
, _clickable(std::make_unique<Ui::AbstractButton>(parentWidget())) {
_clickable->setClickedCallback(std::move(clicked));
, _badgeData(badge) {
userpic->installEventFilter(this);
updateGeometry();
setAttribute(Qt::WA_TransparentForMouseEvents);
@ -113,6 +107,10 @@ UserpicBadge::UserpicBadge(
show();
}
QRect UserpicBadge::badgeGeometry() const {
return _badge;
}
bool UserpicBadge::eventFilter(QObject *o, QEvent *e) {
if (o != _userpic) {
return false;
@ -173,22 +171,27 @@ void UserpicBadge::updateGeometry() {
_badge = QRect(
QPoint(width - badge.width(), height - badge.height()),
badge);
_clickable->setGeometry(_badge.translated(pos()));
update();
}
[[nodiscard]] std::unique_ptr<Ui::RpWidget> MakePrivacyBadge(
struct MadePrivacyBadge {
std::unique_ptr<Ui::RpWidget> widget;
QRect geometry;
};
[[nodiscard]] MadePrivacyBadge MakePrivacyBadge(
not_null<QWidget*> userpic,
Data::StoryPrivacy privacy,
Fn<void()> clicked) {
Data::StoryPrivacy privacy) {
const auto badge = LookupPrivacyBadge(privacy);
if (!badge.icon) {
return nullptr;
return {};
}
return std::make_unique<UserpicBadge>(
userpic,
badge,
std::move(clicked));
auto widget = std::make_unique<UserpicBadge>(userpic, badge);
const auto geometry = widget->badgeGeometry();
return {
.widget = std::move(widget),
.geometry = geometry,
};
}
[[nodiscard]] Timestamp ComposeTimestamp(TimeId when, TimeId now) {
@ -277,6 +280,8 @@ void Header::show(HeaderData data) {
_info->setGeometry({ 0, 0, r, _widget->height() });
}
};
_tooltip = nullptr;
_tooltipShown = false;
if (userChanged) {
_volume = nullptr;
_date = nullptr;
@ -328,6 +333,8 @@ void Header::show(HeaderData data) {
_controller->layoutValue(
) | rpl::start_with_next([=](const Layout &layout) {
raw->setGeometry(layout.header);
_contentGeometry = layout.content;
updateTooltipGeometry();
}, raw->lifetime());
}
auto timestamp = ComposeDetails(data, base::unixtime::now());
@ -357,8 +364,29 @@ void Header::show(HeaderData data) {
_counter = nullptr;
}
_privacy = MakePrivacyBadge(_userpic.get(), data.privacy, [=] {
});
auto made = MakePrivacyBadge(_userpic.get(), data.privacy);
_privacy = std::move(made.widget);
_privacyBadgeOver = false;
_privacyBadgeGeometry = _privacy
? Ui::MapFrom(_info.get(), _privacy.get(), made.geometry)
: QRect();
if (_privacy) {
_info->setMouseTracking(true);
_info->events(
) | rpl::filter([=](not_null<QEvent*> e) {
const auto type = e->type();
if (type != QEvent::Leave && type != QEvent::MouseMove) {
return false;
}
const auto over = (type == QEvent::MouseMove)
&& _privacyBadgeGeometry.contains(
static_cast<QMouseEvent*>(e.get())->pos());
return (_privacyBadgeOver != over);
}) | rpl::start_with_next([=] {
_privacyBadgeOver = !_privacyBadgeOver;
toggleTooltip(Tooltip::Privacy, _privacyBadgeOver);
}, _privacy->lifetime());
}
if (data.video) {
createPlayPause();
@ -369,6 +397,7 @@ void Header::show(HeaderData data) {
_playPause->moveToRight(playPause.x(), playPause.y(), width);
const auto volume = st::storiesVolumeButtonPosition;
_volumeToggle->moveToRight(volume.x(), volume.y(), width);
updateTooltipGeometry();
}, _playPause->lifetime());
_pauseState = _controller->pauseState();
@ -496,15 +525,14 @@ void Header::createVolumeToggle() {
_volumeToggle->events(
) | rpl::start_with_next([=](not_null<QEvent*> e) {
if (state->silent) {
return;
}
const auto type = e->type();
if (type == QEvent::Enter || type == QEvent::Leave) {
const auto over = (e->type() == QEvent::Enter);
if (state->over != over) {
state->over = over;
if (over) {
if (state->silent) {
toggleTooltip(Tooltip::SilentVideo, over);
} else if (over) {
state->hideTimer.cancel();
_volume->toggle(true, anim::type::normal);
} else if (!state->dropdownOver) {
@ -565,6 +593,123 @@ void Header::createVolumeToggle() {
}
}
void Header::toggleTooltip(Tooltip type, bool show) {
const auto guard = gsl::finally([&] {
_tooltipShown = (_tooltip != nullptr);
});
if (const auto was = _tooltip.release()) {
was->toggleAnimated(false);
}
if (!show) {
return;
}
const auto text = [&]() -> TextWithEntities {
using Privacy = Data::StoryPrivacy;
const auto boldName = Ui::Text::Bold(_data->user->shortName());
const auto self = _data->user->isSelf();
switch (type) {
case Tooltip::SilentVideo:
return { tr::lng_stories_about_silent(tr::now) };
case Tooltip::Privacy: switch (_data->privacy) {
case Privacy::CloseFriends:
return self
? tr::lng_stories_about_close_friends_my(
tr::now,
Ui::Text::RichLangValue)
: tr::lng_stories_about_close_friends(
tr::now,
lt_user,
boldName,
Ui::Text::RichLangValue);
case Privacy::Contacts:
return self
? tr::lng_stories_about_contacts_my(
tr::now,
Ui::Text::RichLangValue)
: tr::lng_stories_about_contacts(
tr::now,
lt_user,
boldName,
Ui::Text::RichLangValue);
case Privacy::SelectedContacts:
return self
? tr::lng_stories_about_selected_contacts_my(
tr::now,
Ui::Text::RichLangValue)
: tr::lng_stories_about_selected_contacts(
tr::now,
lt_user,
boldName,
Ui::Text::RichLangValue);
}
}
return {};
}();
if (text.empty()) {
return;
}
_tooltipType = type;
_tooltip = std::make_unique<Ui::ImportantTooltip>(
_widget->parentWidget(),
Ui::MakeNiceTooltipLabel(
_widget.get(),
rpl::single(text),
st::storiesInfoTooltipMaxWidth,
st::storiesInfoTooltipLabel),
st::storiesInfoTooltip);
const auto tooltip = _tooltip.get();
const auto weak = QPointer<QWidget>(tooltip);
const auto destroy = [=] {
delete weak.data();
};
tooltip->setAttribute(Qt::WA_TransparentForMouseEvents);
tooltip->setHiddenCallback(destroy);
updateTooltipGeometry();
tooltip->toggleAnimated(true);
}
void Header::updateTooltipGeometry() {
if (!_tooltip) {
return;
}
const auto geometry = [&] {
switch (_tooltipType) {
case Tooltip::SilentVideo:
return Ui::MapFrom(
_widget->parentWidget(),
_volumeToggle.get(),
_volumeToggle->rect());
case Tooltip::Privacy:
return Ui::MapFrom(
_widget->parentWidget(),
_info.get(),
_privacyBadgeGeometry.marginsAdded(
st::storiesInfoTooltip.padding));
}
return QRect();
}();
if (geometry.isEmpty()) {
toggleTooltip(Tooltip::None, false);
return;
}
const auto weak = QPointer<QWidget>(_tooltip.get());
const auto countPosition = [=](QSize size) {
const auto result = geometry.bottomLeft()
- QPoint(size.width() / 2, 0);
const auto inner = _contentGeometry.marginsRemoved(
st::storiesInfoTooltip.padding);
if (size.width() > inner.width()) {
return QPoint(
inner.x() + (inner.width() - size.width()) / 2,
result.y());
} else if (result.x() < inner.x()) {
return QPoint(inner.x(), result.y());
}
return result;
};
_tooltip->pointAt(geometry, RectPart::Bottom, countPosition);
}
void Header::rebuildVolumeControls(
not_null<Ui::RpWidget*> dropdown,
bool horizontal) {
@ -682,11 +827,14 @@ void Header::raise() {
}
}
bool Header::ignoreWindowMove(QPoint position) const {
return _ignoreWindowMove;
}
rpl::producer<bool> Header::tooltipShownValue() const {
return _tooltipShown.value();
}
void Header::updateDateText() {
if (!_date || !_data || !_data->date) {
return;

View File

@ -20,6 +20,7 @@ class FlatLabel;
class IconButton;
class AbstractButton;
class UserpicButton;
class ImportantTooltip;
template <typename Widget>
class FadeWrap;
} // namespace Ui
@ -55,8 +56,15 @@ public:
void raise();
[[nodiscard]] bool ignoreWindowMove(QPoint position) const;
[[nodiscard]] rpl::producer<bool> tooltipShownValue() const;
private:
enum class Tooltip {
None,
SilentVideo,
Privacy,
};
void updateDateText();
void applyPauseState();
void createPlayPause();
@ -64,6 +72,8 @@ private:
void rebuildVolumeControls(
not_null<Ui::RpWidget*> dropdown,
bool horizontal);
void toggleTooltip(Tooltip type, bool show);
void updateTooltipGeometry();
const not_null<Controller*> _controller;
@ -81,9 +91,15 @@ private:
std::unique_ptr<Ui::FadeWrap<Ui::RpWidget>> _volume;
rpl::variable<const style::icon*> _volumeIcon;
std::unique_ptr<Ui::RpWidget> _privacy;
QRect _privacyBadgeGeometry;
std::optional<HeaderData> _data;
std::unique_ptr<Ui::ImportantTooltip> _tooltip = { nullptr };
rpl::variable<bool> _tooltipShown = false;
QRect _contentGeometry;
Tooltip _tooltipType = {};
base::Timer _dateUpdateTimer;
bool _ignoreWindowMove = false;
bool _privacyBadgeOver = false;
};

View File

@ -891,3 +891,18 @@ storiesVolumeSlider: MediaSlider {
seekSize: size(12px, 12px);
duration: mediaviewOverDuration;
}
storiesInfoTooltipLabel: FlatLabel(defaultImportantTooltipLabel) {
style: TextStyle(defaultTextStyle) {
font: font(11px);
linkFont: font(11px);
linkFontOver: font(11px underline);
}
minWidth: 36px;
}
storiesInfoTooltip: ImportantTooltip(defaultImportantTooltip) {
bg: importantTooltipBg;
padding: margins(10px, 3px, 10px, 5px);
radius: 4px;
arrow: 4px;
}
storiesInfoTooltipMaxWidth: 360px;

@ -1 +1 @@
Subproject commit ad852f0f4ab271de4db799d01fa8b7032eb33b11
Subproject commit bd1e8f7c47c3e99493adf9653d684c86a0a51941