mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Implement channel stories views / reactions.
This commit is contained in:
parent
1c2951598b
commit
f3db7e636b
15 changed files with 247 additions and 53 deletions
BIN
Telegram/Resources/icons/mediaview/views.png
Normal file
BIN
Telegram/Resources/icons/mediaview/views.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 476 B |
BIN
Telegram/Resources/icons/mediaview/views@2x.png
Normal file
BIN
Telegram/Resources/icons/mediaview/views@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 884 B |
BIN
Telegram/Resources/icons/mediaview/views@3x.png
Normal file
BIN
Telegram/Resources/icons/mediaview/views@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -221,14 +221,14 @@ struct StoryUpdate {
|
||||||
enum class Flag : uint32 {
|
enum class Flag : uint32 {
|
||||||
None = 0,
|
None = 0,
|
||||||
|
|
||||||
Edited = (1U << 0),
|
Edited = (1U << 0),
|
||||||
Destroyed = (1U << 1),
|
Destroyed = (1U << 1),
|
||||||
NewAdded = (1U << 2),
|
NewAdded = (1U << 2),
|
||||||
ViewsAdded = (1U << 3),
|
ViewsChanged = (1U << 3),
|
||||||
MarkRead = (1U << 4),
|
MarkRead = (1U << 4),
|
||||||
Reaction = (1U << 5),
|
Reaction = (1U << 5),
|
||||||
|
|
||||||
LastUsedBit = (1U << 5),
|
LastUsedBit = (1U << 5),
|
||||||
};
|
};
|
||||||
using Flags = base::flags<Flag>;
|
using Flags = base::flags<Flag>;
|
||||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||||
|
|
|
@ -410,8 +410,17 @@ Data::ReactionId Story::sentReactionId() const {
|
||||||
|
|
||||||
void Story::setReactionId(Data::ReactionId id) {
|
void Story::setReactionId(Data::ReactionId id) {
|
||||||
if (_sentReactionId != id) {
|
if (_sentReactionId != id) {
|
||||||
|
const auto wasEmpty = _sentReactionId.empty();
|
||||||
_sentReactionId = id;
|
_sentReactionId = id;
|
||||||
session().changes().storyUpdated(this, UpdateFlag::Reaction);
|
auto flags = UpdateFlag::Reaction | UpdateFlag();
|
||||||
|
if (_views.known && _sentReactionId.empty() != wasEmpty) {
|
||||||
|
const auto delta = wasEmpty ? 1 : -1;
|
||||||
|
if (_views.reactions + delta >= 0) {
|
||||||
|
_views.reactions += delta;
|
||||||
|
flags |= UpdateFlag::ViewsChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session().changes().storyUpdated(this, flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,6 +447,7 @@ void Story::applyViewsSlice(
|
||||||
|| (_views.total != slice.total);
|
|| (_views.total != slice.total);
|
||||||
_views.reactions = slice.reactions;
|
_views.reactions = slice.reactions;
|
||||||
_views.total = slice.total;
|
_views.total = slice.total;
|
||||||
|
_views.known = true;
|
||||||
if (offset.isEmpty()) {
|
if (offset.isEmpty()) {
|
||||||
_views = slice;
|
_views = slice;
|
||||||
} else if (_views.nextOffset == offset) {
|
} else if (_views.nextOffset == offset) {
|
||||||
|
@ -468,14 +478,14 @@ void Story::applyViewsSlice(
|
||||||
// Count not changed, but list of recent viewers changed.
|
// Count not changed, but list of recent viewers changed.
|
||||||
_peer->session().changes().storyUpdated(
|
_peer->session().changes().storyUpdated(
|
||||||
this,
|
this,
|
||||||
UpdateFlag::ViewsAdded);
|
UpdateFlag::ViewsChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (changed) {
|
if (changed) {
|
||||||
_peer->session().changes().storyUpdated(
|
_peer->session().changes().storyUpdated(
|
||||||
this,
|
this,
|
||||||
UpdateFlag::ViewsAdded);
|
UpdateFlag::ViewsChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,9 +538,11 @@ void Story::applyFields(
|
||||||
auto views = _views.total;
|
auto views = _views.total;
|
||||||
auto reactions = _views.reactions;
|
auto reactions = _views.reactions;
|
||||||
auto viewers = std::vector<not_null<PeerData*>>();
|
auto viewers = std::vector<not_null<PeerData*>>();
|
||||||
|
auto viewsKnown = _views.known;
|
||||||
if (const auto info = data.vviews()) {
|
if (const auto info = data.vviews()) {
|
||||||
views = info->data().vviews_count().v;
|
views = info->data().vviews_count().v;
|
||||||
reactions = info->data().vreactions_count().value_or_empty();
|
reactions = info->data().vreactions_count().value_or_empty();
|
||||||
|
viewsKnown = true;
|
||||||
if (const auto list = info->data().vrecent_viewers()) {
|
if (const auto list = info->data().vrecent_viewers()) {
|
||||||
viewers.reserve(list->v.size());
|
viewers.reserve(list->v.size());
|
||||||
auto &owner = _peer->owner();
|
auto &owner = _peer->owner();
|
||||||
|
@ -577,8 +589,14 @@ void Story::applyFields(
|
||||||
_edited = edited;
|
_edited = edited;
|
||||||
_pinned = pinned;
|
_pinned = pinned;
|
||||||
_noForwards = noForwards;
|
_noForwards = noForwards;
|
||||||
if (_views.reactions != reactions || _views.total != views) {
|
if (_views.reactions != reactions
|
||||||
_views = StoryViews{ .reactions = reactions, .total = views };
|
|| _views.total != views
|
||||||
|
|| _views.known != viewsKnown) {
|
||||||
|
_views = StoryViews{
|
||||||
|
.reactions = reactions,
|
||||||
|
.total = views,
|
||||||
|
.known = viewsKnown,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (viewsChanged) {
|
if (viewsChanged) {
|
||||||
_recentViewers = std::move(viewers);
|
_recentViewers = std::move(viewers);
|
||||||
|
@ -607,7 +625,7 @@ void Story::applyFields(
|
||||||
if (!initial && (changed || viewsChanged || reactionChanged)) {
|
if (!initial && (changed || viewsChanged || reactionChanged)) {
|
||||||
_peer->session().changes().storyUpdated(this, UpdateFlag()
|
_peer->session().changes().storyUpdated(this, UpdateFlag()
|
||||||
| (changed ? UpdateFlag::Edited : UpdateFlag())
|
| (changed ? UpdateFlag::Edited : UpdateFlag())
|
||||||
| (viewsChanged ? UpdateFlag::ViewsAdded : UpdateFlag())
|
| (viewsChanged ? UpdateFlag::ViewsChanged : UpdateFlag())
|
||||||
| (reactionChanged ? UpdateFlag::Reaction : UpdateFlag()));
|
| (reactionChanged ? UpdateFlag::Reaction : UpdateFlag()));
|
||||||
}
|
}
|
||||||
if (!initial && (captionChanged || mediaChanged)) {
|
if (!initial && (captionChanged || mediaChanged)) {
|
||||||
|
|
|
@ -71,6 +71,7 @@ struct StoryViews {
|
||||||
QString nextOffset;
|
QString nextOffset;
|
||||||
int reactions = 0;
|
int reactions = 0;
|
||||||
int total = 0;
|
int total = 0;
|
||||||
|
bool known = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StoryArea {
|
struct StoryArea {
|
||||||
|
|
|
@ -937,6 +937,10 @@ ShortenedCount FormatCountToShort(int64 number) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString FormatCountDecimal(int64 number) {
|
||||||
|
return QString("%L1").arg(number);
|
||||||
|
}
|
||||||
|
|
||||||
PluralResult Plural(
|
PluralResult Plural(
|
||||||
ushort keyBase,
|
ushort keyBase,
|
||||||
float64 value,
|
float64 value,
|
||||||
|
@ -973,7 +977,7 @@ PluralResult Plural(
|
||||||
if (type == lt_count_short) {
|
if (type == lt_count_short) {
|
||||||
return { shift, shortened.string };
|
return { shift, shortened.string };
|
||||||
} else if (type == lt_count_decimal) {
|
} else if (type == lt_count_decimal) {
|
||||||
return { shift, QString("%L1").arg(round) };
|
return { shift, FormatCountDecimal(round) };
|
||||||
}
|
}
|
||||||
return { shift, QString::number(round) };
|
return { shift, QString::number(round) };
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,16 @@ namespace Lang {
|
||||||
inline constexpr auto kTextCommandLangTag = 0x20;
|
inline constexpr auto kTextCommandLangTag = 0x20;
|
||||||
constexpr auto kTagReplacementSize = 4;
|
constexpr auto kTagReplacementSize = 4;
|
||||||
|
|
||||||
int FindTagReplacementPosition(const QString &original, ushort tag);
|
[[nodiscard]] int FindTagReplacementPosition(
|
||||||
|
const QString &original,
|
||||||
|
ushort tag);
|
||||||
|
|
||||||
struct ShortenedCount {
|
struct ShortenedCount {
|
||||||
int64 number = 0;
|
int64 number = 0;
|
||||||
QString string;
|
QString string;
|
||||||
};
|
};
|
||||||
ShortenedCount FormatCountToShort(int64 number);
|
[[nodiscard]] ShortenedCount FormatCountToShort(int64 number);
|
||||||
|
[[nodiscard]] QString FormatCountDecimal(int64 number);
|
||||||
|
|
||||||
struct PluralResult {
|
struct PluralResult {
|
||||||
int keyShift = 0;
|
int keyShift = 0;
|
||||||
|
|
|
@ -855,24 +855,32 @@ void Controller::show(
|
||||||
if (!changeShown(story)) {
|
if (!changeShown(story)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_viewed = false;
|
|
||||||
invalidate_weak_ptrs(&_viewsLoadGuard);
|
|
||||||
_reactions->hide();
|
|
||||||
if (_replyArea->focused()) {
|
|
||||||
unfocusReply();
|
|
||||||
}
|
|
||||||
|
|
||||||
_replyArea->show({
|
_replyArea->show({
|
||||||
.peer = unsupported ? nullptr : peer.get(),
|
.peer = unsupported ? nullptr : peer.get(),
|
||||||
.id = story->id(),
|
.id = story->id(),
|
||||||
}, _reactions->likedValue());
|
}, _reactions->likedValue());
|
||||||
|
|
||||||
|
const auto wasLikeButton = QPointer(_recentViews->likeButton());
|
||||||
_recentViews->show({
|
_recentViews->show({
|
||||||
.list = story->recentViewers(),
|
.list = story->recentViewers(),
|
||||||
.reactions = story->reactions(),
|
.reactions = story->reactions(),
|
||||||
.total = story->views(),
|
.total = story->views(),
|
||||||
.valid = peer->isSelf(),
|
.self = peer->isSelf(),
|
||||||
});
|
.channel = peer->isChannel(),
|
||||||
|
}, _reactions->likedValue());
|
||||||
|
if (const auto nowLikeButton = _recentViews->likeButton()) {
|
||||||
|
if (wasLikeButton != nowLikeButton) {
|
||||||
|
_reactions->attachToReactionButton(nowLikeButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (peer->isSelf() || peer->isChannel()) {
|
||||||
|
_reactions->setReactionIconWidget(_recentViews->likeIconWidget());
|
||||||
|
} else if (const auto like = _replyArea->likeAnimationTarget()) {
|
||||||
|
_reactions->setReactionIconWidget(like);
|
||||||
|
}
|
||||||
|
_reactions->showLikeFrom(story);
|
||||||
|
|
||||||
stories.loadAround(storyId, context);
|
stories.loadAround(storyId, context);
|
||||||
|
|
||||||
|
@ -906,7 +914,6 @@ bool Controller::changeShown(Data::Story *story) {
|
||||||
story,
|
story,
|
||||||
Data::Stories::Polling::Viewer);
|
Data::Stories::Polling::Viewer);
|
||||||
}
|
}
|
||||||
_reactions->showLikeFrom(story);
|
|
||||||
|
|
||||||
const auto &locations = story
|
const auto &locations = story
|
||||||
? story->locations()
|
? story->locations()
|
||||||
|
@ -923,6 +930,14 @@ bool Controller::changeShown(Data::Story *story) {
|
||||||
_areas.clear();
|
_areas.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_viewed = false;
|
||||||
|
invalidate_weak_ptrs(&_viewsLoadGuard);
|
||||||
|
_reactions->hide();
|
||||||
|
_reactions->setReactionIconWidget(nullptr);
|
||||||
|
if (_replyArea->focused()) {
|
||||||
|
unfocusReply();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -947,7 +962,7 @@ void Controller::subscribeToSession() {
|
||||||
}, _sessionLifetime);
|
}, _sessionLifetime);
|
||||||
_session->changes().storyUpdates(
|
_session->changes().storyUpdates(
|
||||||
Data::StoryUpdate::Flag::Edited
|
Data::StoryUpdate::Flag::Edited
|
||||||
| Data::StoryUpdate::Flag::ViewsAdded
|
| Data::StoryUpdate::Flag::ViewsChanged
|
||||||
) | rpl::filter([=](const Data::StoryUpdate &update) {
|
) | rpl::filter([=](const Data::StoryUpdate &update) {
|
||||||
return (update.story == this->story());
|
return (update.story == this->story());
|
||||||
}) | rpl::start_with_next([=](const Data::StoryUpdate &update) {
|
}) | rpl::start_with_next([=](const Data::StoryUpdate &update) {
|
||||||
|
@ -959,7 +974,8 @@ void Controller::subscribeToSession() {
|
||||||
.list = update.story->recentViewers(),
|
.list = update.story->recentViewers(),
|
||||||
.reactions = update.story->reactions(),
|
.reactions = update.story->reactions(),
|
||||||
.total = update.story->views(),
|
.total = update.story->views(),
|
||||||
.valid = update.story->peer()->isSelf(),
|
.self = update.story->peer()->isSelf(),
|
||||||
|
.channel = update.story->peer()->isChannel(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, _sessionLifetime);
|
}, _sessionLifetime);
|
||||||
|
|
|
@ -445,7 +445,8 @@ void Reactions::Panel::collapse(Mode mode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Reactions::Panel::attachToReactionButton(not_null<Ui::RpWidget*> button) {
|
void Reactions::Panel::attachToReactionButton(
|
||||||
|
not_null<Ui::RpWidget*> button) {
|
||||||
base::install_event_filter(button, [=](not_null<QEvent*> e) {
|
base::install_event_filter(button, [=](not_null<QEvent*> e) {
|
||||||
if (e->type() == QEvent::ContextMenu && !button->isHidden()) {
|
if (e->type() == QEvent::ContextMenu && !button->isHidden()) {
|
||||||
show(Reactions::Mode::Reaction);
|
show(Reactions::Mode::Reaction);
|
||||||
|
@ -662,10 +663,17 @@ void Reactions::setReplyFieldState(
|
||||||
}
|
}
|
||||||
|
|
||||||
void Reactions::attachToReactionButton(not_null<Ui::RpWidget*> button) {
|
void Reactions::attachToReactionButton(not_null<Ui::RpWidget*> button) {
|
||||||
_likeButton = button;
|
|
||||||
_panel->attachToReactionButton(button);
|
_panel->attachToReactionButton(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Reactions::setReactionIconWidget(Ui::RpWidget *widget) {
|
||||||
|
if (_likeIconWidget != widget) {
|
||||||
|
assignLikedId({});
|
||||||
|
_likeIconWidget = widget;
|
||||||
|
_reactionAnimation = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto Reactions::attachToMenu(
|
auto Reactions::attachToMenu(
|
||||||
not_null<Ui::PopupMenu*> menu,
|
not_null<Ui::PopupMenu*> menu,
|
||||||
QPoint desiredPosition)
|
QPoint desiredPosition)
|
||||||
|
@ -751,7 +759,7 @@ void Reactions::ready() {
|
||||||
void Reactions::animateAndProcess(Chosen &&chosen) {
|
void Reactions::animateAndProcess(Chosen &&chosen) {
|
||||||
const auto like = (chosen.mode == Mode::Reaction);
|
const auto like = (chosen.mode == Mode::Reaction);
|
||||||
const auto wrap = _controller->wrap();
|
const auto wrap = _controller->wrap();
|
||||||
const auto target = like ? _likeButton : wrap.get();
|
const auto target = like ? _likeIconWidget : wrap.get();
|
||||||
const auto story = _controller->story();
|
const auto story = _controller->story();
|
||||||
if (!story || !target) {
|
if (!story || !target) {
|
||||||
return;
|
return;
|
||||||
|
@ -796,7 +804,7 @@ Fn<void(Ui::ReactionFlyCenter)> Reactions::setLikedIdIconInit(
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
assignLikedId(id);
|
assignLikedId(id);
|
||||||
if (id.empty() || !_likeButton) {
|
if (id.empty() || !_likeIconWidget) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return crl::guard(&_likeIconGuard, [=](Ui::ReactionFlyCenter center) {
|
return crl::guard(&_likeIconGuard, [=](Ui::ReactionFlyCenter center) {
|
||||||
|
@ -812,12 +820,12 @@ void Reactions::initLikeIcon(
|
||||||
not_null<Data::Session*> owner,
|
not_null<Data::Session*> owner,
|
||||||
Data::ReactionId id,
|
Data::ReactionId id,
|
||||||
Ui::ReactionFlyCenter center) {
|
Ui::ReactionFlyCenter center) {
|
||||||
Expects(_likeButton != nullptr);
|
Expects(_likeIconWidget != nullptr);
|
||||||
|
|
||||||
_likeIcon = std::make_unique<Ui::RpWidget>(_likeButton);
|
_likeIcon = std::make_unique<Ui::RpWidget>(_likeIconWidget);
|
||||||
const auto icon = _likeIcon.get();
|
const auto icon = _likeIcon.get();
|
||||||
icon->show();
|
icon->show();
|
||||||
_likeButton->sizeValue() | rpl::start_with_next([=](QSize size) {
|
_likeIconWidget->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||||
icon->setGeometry(QRect(QPoint(), size));
|
icon->setGeometry(QRect(QPoint(), size));
|
||||||
}, icon->lifetime());
|
}, icon->lifetime());
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,7 @@ public:
|
||||||
rpl::producer<bool> focused,
|
rpl::producer<bool> focused,
|
||||||
rpl::producer<bool> hasSendText);
|
rpl::producer<bool> hasSendText);
|
||||||
void attachToReactionButton(not_null<Ui::RpWidget*> button);
|
void attachToReactionButton(not_null<Ui::RpWidget*> button);
|
||||||
|
void setReactionIconWidget(Ui::RpWidget *widget);
|
||||||
|
|
||||||
using AttachStripResult = HistoryView::Reactions::AttachSelectorResult;
|
using AttachStripResult = HistoryView::Reactions::AttachSelectorResult;
|
||||||
[[nodiscard]] AttachStripResult attachToMenu(
|
[[nodiscard]] AttachStripResult attachToMenu(
|
||||||
|
@ -123,7 +124,7 @@ private:
|
||||||
bool _replyFocused = false;
|
bool _replyFocused = false;
|
||||||
bool _hasSendText = false;
|
bool _hasSendText = false;
|
||||||
|
|
||||||
Ui::RpWidget *_likeButton = nullptr;
|
Ui::RpWidget *_likeIconWidget = nullptr;
|
||||||
rpl::variable<Data::ReactionId> _liked;
|
rpl::variable<Data::ReactionId> _liked;
|
||||||
base::has_weak_ptr _likeIconGuard;
|
base::has_weak_ptr _likeIconGuard;
|
||||||
std::unique_ptr<Ui::RpWidget> _likeIcon;
|
std::unique_ptr<Ui::RpWidget> _likeIcon;
|
||||||
|
|
|
@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/chat/group_call_userpics.h"
|
#include "ui/chat/group_call_userpics.h"
|
||||||
#include "ui/controls/who_reacted_context_action.h"
|
#include "ui/controls/who_reacted_context_action.h"
|
||||||
#include "ui/layers/box_content.h"
|
#include "ui/layers/box_content.h"
|
||||||
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
|
@ -128,7 +129,24 @@ RecentViews::RecentViews(not_null<Controller*> controller)
|
||||||
|
|
||||||
RecentViews::~RecentViews() = default;
|
RecentViews::~RecentViews() = default;
|
||||||
|
|
||||||
void RecentViews::show(RecentViewsData data) {
|
void RecentViews::show(
|
||||||
|
RecentViewsData data,
|
||||||
|
rpl::producer<Data::ReactionId> likedValue) {
|
||||||
|
const auto guard = gsl::finally([&] {
|
||||||
|
if (_likeIcon && likedValue) {
|
||||||
|
std::move(
|
||||||
|
likedValue
|
||||||
|
) | rpl::map([](const Data::ReactionId &id) {
|
||||||
|
return !id.empty();
|
||||||
|
}) | rpl::start_with_next([=](bool liked) {
|
||||||
|
const auto icon = liked
|
||||||
|
? &st::storiesComposeControls.liked
|
||||||
|
: &st::storiesLikesIcon;
|
||||||
|
_likeIcon->setIconOverride(icon, icon);
|
||||||
|
}, _likeIcon->lifetime());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (_data == data) {
|
if (_data == data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -137,27 +155,49 @@ void RecentViews::show(RecentViewsData data) {
|
||||||
|| (_data.reactions != data.reactions);
|
|| (_data.reactions != data.reactions);
|
||||||
const auto usersChanged = !_userpics || (_data.list != data.list);
|
const auto usersChanged = !_userpics || (_data.list != data.list);
|
||||||
_data = data;
|
_data = data;
|
||||||
if (!_data.valid) {
|
if (!_data.self) {
|
||||||
_text = {};
|
_text = {};
|
||||||
_clickHandlerLifetime.destroy();
|
_clickHandlerLifetime.destroy();
|
||||||
_userpicsLifetime.destroy();
|
_userpicsLifetime.destroy();
|
||||||
_userpics = nullptr;
|
_userpics = nullptr;
|
||||||
_widget = nullptr;
|
_widget = nullptr;
|
||||||
return;
|
} else {
|
||||||
|
if (!_widget) {
|
||||||
|
setupWidget();
|
||||||
|
}
|
||||||
|
if (!_userpics) {
|
||||||
|
setupUserpics();
|
||||||
|
}
|
||||||
|
if (countersChanged) {
|
||||||
|
updateText();
|
||||||
|
}
|
||||||
|
if (usersChanged) {
|
||||||
|
updateUserpics();
|
||||||
|
}
|
||||||
|
refreshClickHandler();
|
||||||
}
|
}
|
||||||
if (!_widget) {
|
|
||||||
setupWidget();
|
if (!_data.channel) {
|
||||||
|
_likeIcon = nullptr;
|
||||||
|
_likeWrap = nullptr;
|
||||||
|
_viewsWrap = nullptr;
|
||||||
|
} else {
|
||||||
|
_viewsCounter = Lang::FormatCountDecimal(std::max(_data.total, 1));
|
||||||
|
_likesCounter = _data.reactions
|
||||||
|
? Lang::FormatCountDecimal(_data.reactions)
|
||||||
|
: QString();
|
||||||
|
if (!_likeWrap || !_likeIcon || !_viewsWrap) {
|
||||||
|
setupViewsReactions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!_userpics) {
|
}
|
||||||
setupUserpics();
|
|
||||||
}
|
Ui::RpWidget *RecentViews::likeButton() const {
|
||||||
if (countersChanged) {
|
return _likeWrap.get();
|
||||||
updateText();
|
}
|
||||||
}
|
|
||||||
if (usersChanged) {
|
Ui::RpWidget *RecentViews::likeIconWidget() const {
|
||||||
updateUserpics();
|
return _likeIcon.get();
|
||||||
}
|
|
||||||
refreshClickHandler();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RecentViews::refreshClickHandler() {
|
void RecentViews::refreshClickHandler() {
|
||||||
|
@ -236,6 +276,74 @@ void RecentViews::setupWidget() {
|
||||||
}, raw->lifetime());
|
}, raw->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RecentViews::setupViewsReactions() {
|
||||||
|
_viewsWrap = std::make_unique<Ui::RpWidget>(_controller->wrap());
|
||||||
|
_likeWrap = std::make_unique<Ui::AbstractButton>(_controller->wrap());
|
||||||
|
_likeIcon = std::make_unique<Ui::IconButton>(
|
||||||
|
_likeWrap.get(),
|
||||||
|
st::storiesComposeControls.like);
|
||||||
|
_likeIcon->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
|
||||||
|
_controller->layoutValue(
|
||||||
|
) | rpl::start_with_next([=](const Layout &layout) {
|
||||||
|
_outer = layout.views;
|
||||||
|
updateViewsReactionsGeometry();
|
||||||
|
}, _likeWrap->lifetime());
|
||||||
|
|
||||||
|
const auto views = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
_viewsWrap.get(),
|
||||||
|
_viewsCounter.value(),
|
||||||
|
st::storiesViewsText);
|
||||||
|
views->show();
|
||||||
|
views->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
views->move(st::storiesViewsTextPosition);
|
||||||
|
|
||||||
|
views->widthValue(
|
||||||
|
) | rpl::start_with_next([=](int width) {
|
||||||
|
_viewsWrap->resize(views->x() + width, _likeIcon->height());
|
||||||
|
updateViewsReactionsGeometry();
|
||||||
|
}, _viewsWrap->lifetime());
|
||||||
|
_viewsWrap->paintRequest() | rpl::start_with_next([=] {
|
||||||
|
auto p = QPainter(_viewsWrap.get());
|
||||||
|
const auto &icon = st::storiesViewsIcon;
|
||||||
|
const auto top = (_viewsWrap->height() - icon.height()) / 2;
|
||||||
|
icon.paint(p, 0, top, _viewsWrap->width());
|
||||||
|
}, _viewsWrap->lifetime());
|
||||||
|
|
||||||
|
_likeIcon->move(0, 0);
|
||||||
|
const auto likes = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
_likeWrap.get(),
|
||||||
|
_likesCounter.value(),
|
||||||
|
st::storiesLikesText);
|
||||||
|
likes->show();
|
||||||
|
likes->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
likes->move(st::storiesLikesTextPosition);
|
||||||
|
|
||||||
|
likes->widthValue(
|
||||||
|
) | rpl::start_with_next([=](int width) {
|
||||||
|
width += width
|
||||||
|
? st::storiesLikesTextRightSkip
|
||||||
|
: st::storiesLieksEmptyRightSkip;
|
||||||
|
_likeWrap->resize(likes->x() + width, _likeIcon->height());
|
||||||
|
updateViewsReactionsGeometry();
|
||||||
|
}, _likeWrap->lifetime());
|
||||||
|
|
||||||
|
_viewsWrap->show();
|
||||||
|
_likeIcon->show();
|
||||||
|
_likeWrap->show();
|
||||||
|
|
||||||
|
_likeWrap->setClickedCallback([=] {
|
||||||
|
_controller->toggleLiked();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecentViews::updateViewsReactionsGeometry() {
|
||||||
|
_viewsWrap->move(_outer.topLeft() + st::storiesViewsPosition);
|
||||||
|
_likeWrap->move(_outer.topLeft()
|
||||||
|
+ QPoint(_outer.width() - _likeWrap->width(), 0)
|
||||||
|
+ st::storiesLikesPosition);
|
||||||
|
}
|
||||||
|
|
||||||
void RecentViews::updatePartsGeometry() {
|
void RecentViews::updatePartsGeometry() {
|
||||||
const auto skip = st::storiesRecentViewsSkip;
|
const auto skip = st::storiesRecentViewsSkip;
|
||||||
const auto full = _userpicsWidth + skip + _text.maxWidth();
|
const auto full = _userpicsWidth + skip + _text.maxWidth();
|
||||||
|
|
|
@ -13,9 +13,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
struct StoryView;
|
struct StoryView;
|
||||||
|
struct ReactionId;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
class AbstractButton;
|
||||||
|
class IconButton;
|
||||||
class RpWidget;
|
class RpWidget;
|
||||||
class GroupCallUserpics;
|
class GroupCallUserpics;
|
||||||
class PopupMenu;
|
class PopupMenu;
|
||||||
|
@ -34,7 +37,8 @@ struct RecentViewsData {
|
||||||
std::vector<not_null<PeerData*>> list;
|
std::vector<not_null<PeerData*>> list;
|
||||||
int reactions = 0;
|
int reactions = 0;
|
||||||
int total = 0;
|
int total = 0;
|
||||||
bool valid = false;
|
bool self = false;
|
||||||
|
bool channel = false;
|
||||||
|
|
||||||
friend inline auto operator<=>(
|
friend inline auto operator<=>(
|
||||||
const RecentViewsData &,
|
const RecentViewsData &,
|
||||||
|
@ -49,7 +53,12 @@ public:
|
||||||
explicit RecentViews(not_null<Controller*> controller);
|
explicit RecentViews(not_null<Controller*> controller);
|
||||||
~RecentViews();
|
~RecentViews();
|
||||||
|
|
||||||
void show(RecentViewsData data);
|
void show(
|
||||||
|
RecentViewsData data,
|
||||||
|
rpl::producer<Data::ReactionId> likedValue = nullptr);
|
||||||
|
|
||||||
|
[[nodiscard]] Ui::RpWidget *likeButton() const;
|
||||||
|
[[nodiscard]] Ui::RpWidget *likeIconWidget() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct MenuEntry {
|
struct MenuEntry {
|
||||||
|
@ -69,6 +78,9 @@ private:
|
||||||
void updatePartsGeometry();
|
void updatePartsGeometry();
|
||||||
void showMenu();
|
void showMenu();
|
||||||
|
|
||||||
|
void setupViewsReactions();
|
||||||
|
void updateViewsReactionsGeometry();
|
||||||
|
|
||||||
void addMenuRow(Data::StoryView entry, const QDateTime &now);
|
void addMenuRow(Data::StoryView entry, const QDateTime &now);
|
||||||
void addMenuRowPlaceholder(not_null<Main::Session*> session);
|
void addMenuRowPlaceholder(not_null<Main::Session*> session);
|
||||||
void rebuildMenuTail();
|
void rebuildMenuTail();
|
||||||
|
@ -83,6 +95,12 @@ private:
|
||||||
RecentViewsData _data;
|
RecentViewsData _data;
|
||||||
rpl::lifetime _userpicsLifetime;
|
rpl::lifetime _userpicsLifetime;
|
||||||
|
|
||||||
|
rpl::variable<QString> _viewsCounter;
|
||||||
|
rpl::variable<QString> _likesCounter;
|
||||||
|
std::unique_ptr<Ui::RpWidget> _viewsWrap;
|
||||||
|
std::unique_ptr<Ui::AbstractButton> _likeWrap;
|
||||||
|
std::unique_ptr<Ui::IconButton> _likeIcon;
|
||||||
|
|
||||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||||
rpl::lifetime _menuShortLifetime;
|
rpl::lifetime _menuShortLifetime;
|
||||||
std::vector<MenuEntry> _menuEntries;
|
std::vector<MenuEntry> _menuEntries;
|
||||||
|
|
|
@ -991,3 +991,20 @@ storiesStealthBoxBottom: 11px;
|
||||||
storiesStealthToast: Toast(defaultMultilineToast) {
|
storiesStealthToast: Toast(defaultMultilineToast) {
|
||||||
maxWidth: 340px;
|
maxWidth: 340px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storiesViewsPosition: point(4px, 29px);
|
||||||
|
storiesViewsIcon: icon{{ "mediaview/views", storiesComposeGrayText }};
|
||||||
|
storiesViewsText: FlatLabel(defaultFlatLabel) {
|
||||||
|
textFg: storiesComposeGrayText;
|
||||||
|
}
|
||||||
|
storiesViewsTextPosition: point(26px, 14px);
|
||||||
|
|
||||||
|
storiesLikesPosition: point(0px, 29px);
|
||||||
|
storiesLikesIcon: icon {{ "chat/input_like", storiesComposeWhiteText }};
|
||||||
|
storiesLikesText: FlatLabel(defaultFlatLabel) {
|
||||||
|
textFg: storiesComposeWhiteText;
|
||||||
|
style: semiboldTextStyle;
|
||||||
|
}
|
||||||
|
storiesLikesTextPosition: point(41px, 14px);
|
||||||
|
storiesLikesTextRightSkip: 8px;
|
||||||
|
storiesLieksEmptyRightSkip: 2px;
|
||||||
|
|
|
@ -213,7 +213,7 @@ void EditInviteLinkBox(
|
||||||
? tr::lng_group_invite_usage_any(tr::now)
|
? tr::lng_group_invite_usage_any(tr::now)
|
||||||
: !limit
|
: !limit
|
||||||
? tr::lng_group_invite_usage_custom(tr::now)
|
? tr::lng_group_invite_usage_custom(tr::now)
|
||||||
: QString("%L1").arg(limit);
|
: Lang::FormatCountDecimal(limit);
|
||||||
state->usageButtons.emplace(
|
state->usageButtons.emplace(
|
||||||
limit,
|
limit,
|
||||||
addButton(usagesWrap, usageGroup, limit, text));
|
addButton(usagesWrap, usageGroup, limit, text));
|
||||||
|
|
Loading…
Add table
Reference in a new issue