mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Show recent viewers in self stories.
This commit is contained in:
parent
e90642f3a0
commit
d76c80bf0e
15 changed files with 342 additions and 50 deletions
|
@ -973,6 +973,8 @@ PRIVATE
|
||||||
media/stories/media_stories_delegate.h
|
media/stories/media_stories_delegate.h
|
||||||
media/stories/media_stories_header.cpp
|
media/stories/media_stories_header.cpp
|
||||||
media/stories/media_stories_header.h
|
media/stories/media_stories_header.h
|
||||||
|
media/stories/media_stories_recent_views.cpp
|
||||||
|
media/stories/media_stories_recent_views.h
|
||||||
media/stories/media_stories_reply.cpp
|
media/stories/media_stories_reply.cpp
|
||||||
media/stories/media_stories_reply.h
|
media/stories/media_stories_reply.h
|
||||||
media/stories/media_stories_sibling.cpp
|
media/stories/media_stories_sibling.cpp
|
||||||
|
|
|
@ -3793,6 +3793,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_stories_row_count#other" = "{count} Stories";
|
"lng_stories_row_count#other" = "{count} Stories";
|
||||||
"lng_stories_row_unread_and_one" = "{accumulated}, {user}";
|
"lng_stories_row_unread_and_one" = "{accumulated}, {user}";
|
||||||
"lng_stories_row_unread_and_last" = "{accumulated} and {user}";
|
"lng_stories_row_unread_and_last" = "{accumulated} and {user}";
|
||||||
|
"lng_stories_views#one" = "{count} view";
|
||||||
|
"lng_stories_views#other" = "{count} views";
|
||||||
|
"lng_stories_no_views" = "No views";
|
||||||
|
|
||||||
// Wnd specific
|
// Wnd specific
|
||||||
|
|
||||||
|
|
|
@ -229,4 +229,15 @@ QList<QUrl> ReadMimeUrls(not_null<const QMimeData*> data) {
|
||||||
: QList<QUrl>();
|
: QList<QUrl>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CanSendFiles(not_null<const QMimeData*> data) {
|
||||||
|
if (data->hasImage()) {
|
||||||
|
return true;
|
||||||
|
} else if (const auto urls = ReadMimeUrls(data); !urls.empty()) {
|
||||||
|
if (ranges::all_of(urls, &QUrl::isLocalFile)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -67,5 +67,6 @@ struct MimeImageData {
|
||||||
[[nodiscard]] MimeImageData ReadMimeImage(not_null<const QMimeData*> data);
|
[[nodiscard]] MimeImageData ReadMimeImage(not_null<const QMimeData*> data);
|
||||||
[[nodiscard]] QString ReadMimeText(not_null<const QMimeData*> data);
|
[[nodiscard]] QString ReadMimeText(not_null<const QMimeData*> data);
|
||||||
[[nodiscard]] QList<QUrl> ReadMimeUrls(not_null<const QMimeData*> data);
|
[[nodiscard]] QList<QUrl> ReadMimeUrls(not_null<const QMimeData*> data);
|
||||||
|
[[nodiscard]] bool CanSendFiles(not_null<const QMimeData*> data);
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -36,10 +36,10 @@ constexpr auto kMarkAsReadDelay = 3 * crl::time(1000);
|
||||||
using UpdateFlag = StoryUpdate::Flag;
|
using UpdateFlag = StoryUpdate::Flag;
|
||||||
|
|
||||||
std::optional<StoryMedia> ParseMedia(
|
std::optional<StoryMedia> ParseMedia(
|
||||||
not_null<Session*> owner,
|
not_null<Session*> owner,
|
||||||
const MTPMessageMedia &media) {
|
const MTPMessageMedia &media) {
|
||||||
return media.match([&](const MTPDmessageMediaPhoto &data)
|
return media.match([&](const MTPDmessageMediaPhoto &data)
|
||||||
-> std::optional<StoryMedia> {
|
-> std::optional<StoryMedia> {
|
||||||
if (const auto photo = data.vphoto()) {
|
if (const auto photo = data.vphoto()) {
|
||||||
const auto result = owner->processPhoto(*photo);
|
const auto result = owner->processPhoto(*photo);
|
||||||
if (!result->isNull()) {
|
if (!result->isNull()) {
|
||||||
|
@ -48,7 +48,7 @@ std::optional<StoryMedia> ParseMedia(
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}, [&](const MTPDmessageMediaDocument &data)
|
}, [&](const MTPDmessageMediaDocument &data)
|
||||||
-> std::optional<StoryMedia> {
|
-> std::optional<StoryMedia> {
|
||||||
if (const auto document = data.vdocument()) {
|
if (const auto document = data.vdocument()) {
|
||||||
const auto result = owner->processDocument(*document);
|
const auto result = owner->processDocument(*document);
|
||||||
if (!result->isNull()
|
if (!result->isNull()
|
||||||
|
@ -71,10 +71,10 @@ Story::Story(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
StoryMedia media,
|
StoryMedia media,
|
||||||
TimeId date)
|
TimeId date)
|
||||||
: _id(id)
|
: _id(id)
|
||||||
, _peer(peer)
|
, _peer(peer)
|
||||||
, _media(std::move(media))
|
, _media(std::move(media))
|
||||||
, _date(date) {
|
, _date(date) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Session &Story::owner() const {
|
Session &Story::owner() const {
|
||||||
|
@ -170,6 +170,21 @@ const TextWithEntities &Story::caption() const {
|
||||||
return _caption;
|
return _caption;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Story::setViewsData(
|
||||||
|
std::vector<not_null<PeerData*>> recent,
|
||||||
|
int total) {
|
||||||
|
_recentViewers = std::move(recent);
|
||||||
|
_views = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<not_null<PeerData*>> &Story::recentViewers() const {
|
||||||
|
return _recentViewers;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Story::views() const {
|
||||||
|
return _views;
|
||||||
|
}
|
||||||
|
|
||||||
bool Story::applyChanges(StoryMedia media, const MTPDstoryItem &data) {
|
bool Story::applyChanges(StoryMedia media, const MTPDstoryItem &data) {
|
||||||
const auto pinned = data.is_pinned();
|
const auto pinned = data.is_pinned();
|
||||||
auto caption = TextWithEntities{
|
auto caption = TextWithEntities{
|
||||||
|
@ -178,15 +193,32 @@ bool Story::applyChanges(StoryMedia media, const MTPDstoryItem &data) {
|
||||||
&owner().session(),
|
&owner().session(),
|
||||||
data.ventities().value_or_empty()),
|
data.ventities().value_or_empty()),
|
||||||
};
|
};
|
||||||
|
auto views = 0;
|
||||||
|
auto recent = std::vector<not_null<PeerData*>>();
|
||||||
|
if (const auto info = data.vviews()) {
|
||||||
|
views = info->data().vviews_count().v;
|
||||||
|
if (const auto list = info->data().vrecent_viewers()) {
|
||||||
|
recent.reserve(list->v.size());
|
||||||
|
auto &owner = _peer->owner();
|
||||||
|
for (const auto &id : list->v) {
|
||||||
|
recent.push_back(owner.peer(peerFromUser(id)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const auto changed = (_media != media)
|
const auto changed = (_media != media)
|
||||||
|| (_pinned != pinned)
|
|| (_pinned != pinned)
|
||||||
|| (_caption != caption);
|
|| (_caption != caption)
|
||||||
|
|| (_views != views)
|
||||||
|
|| (_recentViewers != recent);
|
||||||
if (!changed) {
|
if (!changed) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_media = std::move(media);
|
_media = std::move(media);
|
||||||
_pinned = pinned;
|
_pinned = pinned;
|
||||||
_caption = std::move(caption);
|
_caption = std::move(caption);
|
||||||
|
_views = views;
|
||||||
|
_recentViewers = std::move(recent);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,11 @@ public:
|
||||||
void setCaption(TextWithEntities &&caption);
|
void setCaption(TextWithEntities &&caption);
|
||||||
[[nodiscard]] const TextWithEntities &caption() const;
|
[[nodiscard]] const TextWithEntities &caption() const;
|
||||||
|
|
||||||
|
void setViewsData(std::vector<not_null<PeerData*>> recent, int total);
|
||||||
|
[[nodiscard]] auto recentViewers() const
|
||||||
|
-> const std::vector<not_null<PeerData*>> &;
|
||||||
|
[[nodiscard]] int views() const;
|
||||||
|
|
||||||
bool applyChanges(StoryMedia media, const MTPDstoryItem &data);
|
bool applyChanges(StoryMedia media, const MTPDstoryItem &data);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -64,6 +69,8 @@ private:
|
||||||
const not_null<PeerData*> _peer;
|
const not_null<PeerData*> _peer;
|
||||||
StoryMedia _media;
|
StoryMedia _media;
|
||||||
TextWithEntities _caption;
|
TextWithEntities _caption;
|
||||||
|
std::vector<not_null<PeerData*>> _recentViewers;
|
||||||
|
int _views = 0;
|
||||||
const TimeId _date = 0;
|
const TimeId _date = 0;
|
||||||
bool _pinned = false;
|
bool _pinned = false;
|
||||||
|
|
||||||
|
|
|
@ -97,17 +97,6 @@ namespace {
|
||||||
|
|
||||||
constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200);
|
constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200);
|
||||||
|
|
||||||
bool CanSendFiles(not_null<const QMimeData*> data) {
|
|
||||||
if (data->hasImage()) {
|
|
||||||
return true;
|
|
||||||
} else if (const auto urls = Core::ReadMimeUrls(data); !urls.empty()) {
|
|
||||||
if (ranges::all_of(urls, &QUrl::isLocalFile)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<Ui::MessageBarContent> RootViewContent(
|
rpl::producer<Ui::MessageBarContent> RootViewContent(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
MsgId rootId,
|
MsgId rootId,
|
||||||
|
@ -819,7 +808,7 @@ void RepliesWidget::setupComposeControls() {
|
||||||
not_null<const QMimeData*> data,
|
not_null<const QMimeData*> data,
|
||||||
Ui::InputField::MimeAction action) {
|
Ui::InputField::MimeAction action) {
|
||||||
if (action == Ui::InputField::MimeAction::Check) {
|
if (action == Ui::InputField::MimeAction::Check) {
|
||||||
return CanSendFiles(data);
|
return Core::CanSendFiles(data);
|
||||||
} else if (action == Ui::InputField::MimeAction::Insert) {
|
} else if (action == Ui::InputField::MimeAction::Insert) {
|
||||||
return confirmSendingFiles(
|
return confirmSendingFiles(
|
||||||
data,
|
data,
|
||||||
|
|
|
@ -65,20 +65,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include <QtCore/QMimeData>
|
#include <QtCore/QMimeData>
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
namespace {
|
|
||||||
|
|
||||||
bool CanSendFiles(not_null<const QMimeData*> data) {
|
|
||||||
if (data->hasImage()) {
|
|
||||||
return true;
|
|
||||||
} else if (const auto urls = Core::ReadMimeUrls(data); !urls.empty()) {
|
|
||||||
if (ranges::all_of(urls, &QUrl::isLocalFile)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
object_ptr<Window::SectionWidget> ScheduledMemento::createWidget(
|
object_ptr<Window::SectionWidget> ScheduledMemento::createWidget(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
|
@ -308,7 +294,7 @@ void ScheduledWidget::setupComposeControls() {
|
||||||
not_null<const QMimeData*> data,
|
not_null<const QMimeData*> data,
|
||||||
Ui::InputField::MimeAction action) {
|
Ui::InputField::MimeAction action) {
|
||||||
if (action == Ui::InputField::MimeAction::Check) {
|
if (action == Ui::InputField::MimeAction::Check) {
|
||||||
return CanSendFiles(data);
|
return Core::CanSendFiles(data);
|
||||||
} else if (action == Ui::InputField::MimeAction::Insert) {
|
} else if (action == Ui::InputField::MimeAction::Insert) {
|
||||||
return confirmSendingFiles(
|
return confirmSendingFiles(
|
||||||
data,
|
data,
|
||||||
|
|
|
@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "media/stories/media_stories_header.h"
|
#include "media/stories/media_stories_header.h"
|
||||||
#include "media/stories/media_stories_sibling.h"
|
#include "media/stories/media_stories_sibling.h"
|
||||||
#include "media/stories/media_stories_slider.h"
|
#include "media/stories/media_stories_slider.h"
|
||||||
|
#include "media/stories/media_stories_recent_views.h"
|
||||||
#include "media/stories/media_stories_reply.h"
|
#include "media/stories/media_stories_reply.h"
|
||||||
#include "media/stories/media_stories_view.h"
|
#include "media/stories/media_stories_view.h"
|
||||||
#include "media/audio/media_audio.h"
|
#include "media/audio/media_audio.h"
|
||||||
|
@ -123,7 +124,8 @@ Controller::Controller(not_null<Delegate*> delegate)
|
||||||
, _wrap(_delegate->storiesWrap())
|
, _wrap(_delegate->storiesWrap())
|
||||||
, _header(std::make_unique<Header>(this))
|
, _header(std::make_unique<Header>(this))
|
||||||
, _slider(std::make_unique<Slider>(this))
|
, _slider(std::make_unique<Slider>(this))
|
||||||
, _replyArea(std::make_unique<ReplyArea>(this)) {
|
, _replyArea(std::make_unique<ReplyArea>(this))
|
||||||
|
, _recentViews(std::make_unique<RecentViews>(this)) {
|
||||||
initLayout();
|
initLayout();
|
||||||
|
|
||||||
_replyArea->activeValue(
|
_replyArea->activeValue(
|
||||||
|
@ -249,6 +251,9 @@ void Controller::initLayout() {
|
||||||
+ layout.content.height()
|
+ layout.content.height()
|
||||||
+ fieldMinHeight
|
+ fieldMinHeight
|
||||||
- st::storiesFieldMargin.bottom()));
|
- st::storiesFieldMargin.bottom()));
|
||||||
|
layout.views = QRect(
|
||||||
|
layout.controlsBottomPosition - QPoint(0, fieldMinHeight),
|
||||||
|
QSize(layout.controlsWidth, fieldMinHeight));
|
||||||
layout.autocompleteRect = QRect(
|
layout.autocompleteRect = QRect(
|
||||||
layout.controlsBottomPosition.x(),
|
layout.controlsBottomPosition.x(),
|
||||||
0,
|
0,
|
||||||
|
@ -422,9 +427,18 @@ void Controller::show(
|
||||||
_captionText = story->caption();
|
_captionText = story->caption();
|
||||||
_captionFullView = nullptr;
|
_captionFullView = nullptr;
|
||||||
|
|
||||||
|
if (_replyFocused) {
|
||||||
|
unfocusReply();
|
||||||
|
}
|
||||||
|
|
||||||
_header->show({ .user = list.user, .date = story->date() });
|
_header->show({ .user = list.user, .date = story->date() });
|
||||||
_slider->show({ .index = _index, .total = list.total });
|
_slider->show({ .index = _index, .total = list.total });
|
||||||
_replyArea->show({ .user = list.user, .id = id });
|
_replyArea->show({ .user = list.user, .id = id });
|
||||||
|
_recentViews->show({
|
||||||
|
.list = story->recentViewers(),
|
||||||
|
.total = story->views(),
|
||||||
|
.valid = list.user->isSelf(),
|
||||||
|
});
|
||||||
|
|
||||||
const auto session = &list.user->session();
|
const auto session = &list.user->session();
|
||||||
if (_session != session) {
|
if (_session != session) {
|
||||||
|
@ -450,11 +464,7 @@ void Controller::show(
|
||||||
}
|
}
|
||||||
stories.loadAround(storyId);
|
stories.loadAround(storyId);
|
||||||
|
|
||||||
if (_replyFocused) {
|
|
||||||
unfocusReply();
|
|
||||||
}
|
|
||||||
updatePlayingAllowed();
|
updatePlayingAllowed();
|
||||||
|
|
||||||
list.user->updateFull();
|
list.user->updateFull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ namespace Media::Stories {
|
||||||
class Header;
|
class Header;
|
||||||
class Slider;
|
class Slider;
|
||||||
class ReplyArea;
|
class ReplyArea;
|
||||||
|
class RecentViews;
|
||||||
class Sibling;
|
class Sibling;
|
||||||
class Delegate;
|
class Delegate;
|
||||||
struct SiblingView;
|
struct SiblingView;
|
||||||
|
@ -68,6 +69,7 @@ struct Layout {
|
||||||
QRect slider;
|
QRect slider;
|
||||||
int controlsWidth = 0;
|
int controlsWidth = 0;
|
||||||
QPoint controlsBottomPosition;
|
QPoint controlsBottomPosition;
|
||||||
|
QRect views;
|
||||||
QRect autocompleteRect;
|
QRect autocompleteRect;
|
||||||
HeaderLayout headerLayout = HeaderLayout::Normal;
|
HeaderLayout headerLayout = HeaderLayout::Normal;
|
||||||
SiblingLayout siblingLeft;
|
SiblingLayout siblingLeft;
|
||||||
|
@ -148,6 +150,7 @@ private:
|
||||||
const std::unique_ptr<Header> _header;
|
const std::unique_ptr<Header> _header;
|
||||||
const std::unique_ptr<Slider> _slider;
|
const std::unique_ptr<Slider> _slider;
|
||||||
const std::unique_ptr<ReplyArea> _replyArea;
|
const std::unique_ptr<ReplyArea> _replyArea;
|
||||||
|
const std::unique_ptr<RecentViews> _recentViews;
|
||||||
std::unique_ptr<PhotoPlayback> _photoPlayback;
|
std::unique_ptr<PhotoPlayback> _photoPlayback;
|
||||||
std::unique_ptr<CaptionFullView> _captionFullView;
|
std::unique_ptr<CaptionFullView> _captionFullView;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
/*
|
||||||
|
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/stories/media_stories_recent_views.h"
|
||||||
|
|
||||||
|
#include "data/data_peer.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "media/stories/media_stories_controller.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "ui/chat/group_call_userpics.h"
|
||||||
|
#include "ui/painter.h"
|
||||||
|
#include "ui/rp_widget.h"
|
||||||
|
#include "ui/userpic_view.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
|
#include "styles/style_media_view.h"
|
||||||
|
|
||||||
|
namespace Media::Stories {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<std::vector<Ui::GroupCallUser>> ContentByUsers(
|
||||||
|
const std::vector<not_null<PeerData*>> &list) {
|
||||||
|
struct Userpic {
|
||||||
|
not_null<PeerData*> peer;
|
||||||
|
mutable Ui::PeerUserpicView view;
|
||||||
|
mutable InMemoryKey uniqueKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
std::vector<Userpic> userpics;
|
||||||
|
std::vector<Ui::GroupCallUser> current;
|
||||||
|
base::has_weak_ptr guard;
|
||||||
|
bool someUserpicsNotLoaded = false;
|
||||||
|
bool scheduled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const auto size = st::storiesRecentViewsUserpics.size;
|
||||||
|
|
||||||
|
static const auto GenerateUserpic = [](Userpic &userpic) {
|
||||||
|
auto result = userpic.peer->generateUserpicImage(
|
||||||
|
userpic.view,
|
||||||
|
size * style::DevicePixelRatio());
|
||||||
|
result.setDevicePixelRatio(style::DevicePixelRatio());
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const auto RegenerateUserpics = [](not_null<State*> state) {
|
||||||
|
Expects(state->userpics.size() == state->current.size());
|
||||||
|
|
||||||
|
state->someUserpicsNotLoaded = false;
|
||||||
|
const auto count = int(state->userpics.size());
|
||||||
|
for (auto i = 0; i != count; ++i) {
|
||||||
|
auto &userpic = state->userpics[i];
|
||||||
|
auto &participant = state->current[i];
|
||||||
|
const auto peer = userpic.peer;
|
||||||
|
const auto key = peer->userpicUniqueKey(userpic.view);
|
||||||
|
if (peer->hasUserpic() && peer->useEmptyUserpic(userpic.view)) {
|
||||||
|
state->someUserpicsNotLoaded = true;
|
||||||
|
}
|
||||||
|
if (userpic.uniqueKey == key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
participant.userpicKey = userpic.uniqueKey = key;
|
||||||
|
participant.userpic = GenerateUserpic(userpic);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return [=](auto consumer) {
|
||||||
|
auto lifetime = rpl::lifetime();
|
||||||
|
|
||||||
|
const auto state = lifetime.make_state<State>();
|
||||||
|
const auto pushNext = [=] {
|
||||||
|
RegenerateUserpics(state);
|
||||||
|
consumer.put_next_copy(state->current);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &peer : list) {
|
||||||
|
state->userpics.push_back(Userpic{
|
||||||
|
.peer = peer,
|
||||||
|
});
|
||||||
|
state->current.push_back(Ui::GroupCallUser{
|
||||||
|
.id = uint64(peer->id.value),
|
||||||
|
});
|
||||||
|
peer->loadUserpic();
|
||||||
|
}
|
||||||
|
pushNext();
|
||||||
|
|
||||||
|
if (!list.empty()) {
|
||||||
|
list.front()->session().downloaderTaskFinished(
|
||||||
|
) | rpl::filter([=] {
|
||||||
|
return state->someUserpicsNotLoaded && !state->scheduled;
|
||||||
|
}) | rpl::start_with_next([=] {
|
||||||
|
for (const auto &userpic : state->userpics) {
|
||||||
|
if (userpic.peer->userpicUniqueKey(userpic.view)
|
||||||
|
!= userpic.uniqueKey) {
|
||||||
|
state->scheduled = true;
|
||||||
|
crl::on_main(&state->guard, [=] {
|
||||||
|
state->scheduled = false;
|
||||||
|
pushNext();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, lifetime);
|
||||||
|
}
|
||||||
|
return lifetime;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
RecentViews::RecentViews(not_null<Controller*> controller)
|
||||||
|
: _controller(controller) {
|
||||||
|
}
|
||||||
|
|
||||||
|
RecentViews::~RecentViews() = default;
|
||||||
|
|
||||||
|
void RecentViews::show(RecentViewsData data) {
|
||||||
|
if (_data == data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto totalChanged = _text.isEmpty() || (_data.total != data.total);
|
||||||
|
const auto usersChanged = !_userpics || (_data.list != data.list);
|
||||||
|
_data = data;
|
||||||
|
if (!_data.valid) {
|
||||||
|
_text = {};
|
||||||
|
_userpics = nullptr;
|
||||||
|
_widget = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!_widget) {
|
||||||
|
const auto parent = _controller->wrap();
|
||||||
|
auto widget = std::make_unique<Ui::RpWidget>(parent);
|
||||||
|
const auto raw = widget.get();
|
||||||
|
raw->show();
|
||||||
|
|
||||||
|
_controller->layoutValue(
|
||||||
|
) | rpl::start_with_next([=](const Layout &layout) {
|
||||||
|
raw->setGeometry(layout.views);
|
||||||
|
}, raw->lifetime());
|
||||||
|
|
||||||
|
raw->paintRequest(
|
||||||
|
) | rpl::start_with_next([=](QRect clip) {
|
||||||
|
auto p = Painter(raw);
|
||||||
|
const auto skip = st::storiesRecentViewsSkip;
|
||||||
|
const auto full = _userpicsWidth + skip + _text.maxWidth();
|
||||||
|
const auto use = std::min(full, raw->width());
|
||||||
|
const auto ux = (raw->width() - use) / 2;
|
||||||
|
const auto height = st::storiesRecentViewsUserpics.size;
|
||||||
|
const auto uy = (raw->height() - height) / 2;
|
||||||
|
const auto tx = ux + _userpicsWidth + skip;
|
||||||
|
const auto ty = (raw->height() - st::normalFont->height) / 2;
|
||||||
|
_userpics->paint(p, ux, uy, height);
|
||||||
|
p.setPen(st::storiesComposeWhiteText);
|
||||||
|
_text.drawElided(p, tx, ty, use - _userpicsWidth - skip);
|
||||||
|
}, raw->lifetime());
|
||||||
|
|
||||||
|
_widget = std::move(widget);
|
||||||
|
}
|
||||||
|
if (totalChanged) {
|
||||||
|
_text.setText(st::defaultTextStyle, data.total
|
||||||
|
? tr::lng_stories_views(tr::now, lt_count, data.total)
|
||||||
|
: tr::lng_stories_no_views(tr::now));
|
||||||
|
}
|
||||||
|
if (!_userpics) {
|
||||||
|
_userpics = std::make_unique<Ui::GroupCallUserpics>(
|
||||||
|
st::storiesRecentViewsUserpics,
|
||||||
|
rpl::single(true),
|
||||||
|
[=] { _widget->update(); });
|
||||||
|
|
||||||
|
_userpics->widthValue() | rpl::start_with_next([=](int width) {
|
||||||
|
_userpicsWidth = width;
|
||||||
|
}, _widget->lifetime());
|
||||||
|
}
|
||||||
|
if (usersChanged) {
|
||||||
|
_userpicsLifetime = ContentByUsers(
|
||||||
|
data.list
|
||||||
|
) | rpl::start_with_next([=](
|
||||||
|
const std::vector<Ui::GroupCallUser> &list) {
|
||||||
|
_userpics->update(list, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Media::Stories
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/text/text.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class RpWidget;
|
||||||
|
class GroupCallUserpics;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Media::Stories {
|
||||||
|
|
||||||
|
class Controller;
|
||||||
|
|
||||||
|
struct RecentViewsData {
|
||||||
|
std::vector<not_null<PeerData*>> list;
|
||||||
|
int total = 0;
|
||||||
|
bool valid = false;
|
||||||
|
|
||||||
|
friend inline auto operator<=>(
|
||||||
|
const RecentViewsData &,
|
||||||
|
const RecentViewsData &) = default;
|
||||||
|
friend inline bool operator==(
|
||||||
|
const RecentViewsData &,
|
||||||
|
const RecentViewsData &) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RecentViews final {
|
||||||
|
public:
|
||||||
|
explicit RecentViews(not_null<Controller*> controller);
|
||||||
|
~RecentViews();
|
||||||
|
|
||||||
|
void show(RecentViewsData data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const not_null<Controller*> _controller;
|
||||||
|
|
||||||
|
std::unique_ptr<Ui::RpWidget> _widget;
|
||||||
|
std::unique_ptr<Ui::GroupCallUserpics> _userpics;
|
||||||
|
Ui::Text::String _text;
|
||||||
|
RecentViewsData _data;
|
||||||
|
rpl::lifetime _userpicsLifetime;
|
||||||
|
int _userpicsWidth = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Media::Stories
|
|
@ -524,12 +524,12 @@ void ReplyArea::initActions() {
|
||||||
not_null<const QMimeData*> data,
|
not_null<const QMimeData*> data,
|
||||||
Ui::InputField::MimeAction action) {
|
Ui::InputField::MimeAction action) {
|
||||||
if (action == Ui::InputField::MimeAction::Check) {
|
if (action == Ui::InputField::MimeAction::Check) {
|
||||||
return false;// checkSendingFiles(data);
|
return Core::CanSendFiles(data);
|
||||||
} else if (action == Ui::InputField::MimeAction::Insert) {
|
} else if (action == Ui::InputField::MimeAction::Insert) {
|
||||||
return false;/* confirmSendingFiles(
|
return confirmSendingFiles(
|
||||||
data,
|
data,
|
||||||
std::nullopt,
|
std::nullopt,
|
||||||
Core::ReadMimeText(data));*/
|
Core::ReadMimeText(data));
|
||||||
}
|
}
|
||||||
Unexpected("action in MimeData hook.");
|
Unexpected("action in MimeData hook.");
|
||||||
});
|
});
|
||||||
|
@ -562,6 +562,11 @@ void ReplyArea::show(ReplyAreaData data) {
|
||||||
.history = history,
|
.history = history,
|
||||||
});
|
});
|
||||||
_controls->clear();
|
_controls->clear();
|
||||||
|
if (!user || user->isSelf()) {
|
||||||
|
_controls->hide();
|
||||||
|
} else {
|
||||||
|
_controls->show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Main::Session &ReplyArea::session() const {
|
Main::Session &ReplyArea::session() const {
|
||||||
|
|
|
@ -66,18 +66,12 @@ private:
|
||||||
[[nodiscard]] Main::Session &session() const;
|
[[nodiscard]] Main::Session &session() const;
|
||||||
[[nodiscard]] not_null<History*> history() const;
|
[[nodiscard]] not_null<History*> history() const;
|
||||||
|
|
||||||
bool confirmSendingFiles(const QStringList &files);
|
|
||||||
bool confirmSendingFiles(not_null<const QMimeData*> data);
|
|
||||||
|
|
||||||
void uploadFile(const QByteArray &fileContent, SendMediaType type);
|
void uploadFile(const QByteArray &fileContent, SendMediaType type);
|
||||||
bool confirmSendingFiles(
|
bool confirmSendingFiles(
|
||||||
QImage &&image,
|
QImage &&image,
|
||||||
QByteArray &&content,
|
QByteArray &&content,
|
||||||
std::optional<bool> overrideSendImagesAsPhotos = std::nullopt,
|
std::optional<bool> overrideSendImagesAsPhotos = std::nullopt,
|
||||||
const QString &insertTextOnCancel = QString());
|
const QString &insertTextOnCancel = QString());
|
||||||
bool confirmSendingFiles(
|
|
||||||
const QStringList &files,
|
|
||||||
const QString &insertTextOnCancel);
|
|
||||||
bool confirmSendingFiles(
|
bool confirmSendingFiles(
|
||||||
Ui::PreparedList &&list,
|
Ui::PreparedList &&list,
|
||||||
const QString &insertTextOnCancel = QString());
|
const QString &insertTextOnCancel = QString());
|
||||||
|
|
|
@ -729,3 +729,11 @@ storiesComposeControls: ComposeControls(defaultComposeControls) {
|
||||||
}
|
}
|
||||||
premium: storiesComposePremium;
|
premium: storiesComposePremium;
|
||||||
}
|
}
|
||||||
|
storiesRecentViewsUserpics: GroupCallUserpics {
|
||||||
|
size: 24px;
|
||||||
|
shift: 9px;
|
||||||
|
stroke: 4px;
|
||||||
|
align: align(left);
|
||||||
|
}
|
||||||
|
storiesRecentViewsSkip: 8px;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue