Show repost info on story view.
This commit is contained in:
parent
23a0413113
commit
78897dd143
|
@ -1034,6 +1034,8 @@ PRIVATE
|
|||
media/stories/media_stories_recent_views.h
|
||||
media/stories/media_stories_reply.cpp
|
||||
media/stories/media_stories_reply.h
|
||||
media/stories/media_stories_repost_view.cpp
|
||||
media/stories/media_stories_repost_view.h
|
||||
media/stories/media_stories_share.cpp
|
||||
media/stories/media_stories_share.h
|
||||
media/stories/media_stories_sibling.cpp
|
||||
|
|
|
@ -105,6 +105,31 @@ using UpdateFlag = StoryUpdate::Flag;
|
|||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] PeerData *RepostSourcePeer(
|
||||
not_null<Session*> owner,
|
||||
const MTPDstoryItem &data) {
|
||||
if (const auto forwarded = data.vfwd_from()) {
|
||||
if (const auto from = forwarded->data().vfrom()) {
|
||||
return owner->peer(peerFromMTP(*from));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString RepostSourceName(const MTPDstoryItem &data) {
|
||||
if (const auto forwarded = data.vfwd_from()) {
|
||||
return qs(forwarded->data().vfrom_name().value_or_empty());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] StoryId RepostSourceId(const MTPDstoryItem &data) {
|
||||
if (const auto forwarded = data.vfwd_from()) {
|
||||
return forwarded->data().vstory_id().value_or_empty();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask {
|
||||
|
@ -216,6 +241,9 @@ Story::Story(
|
|||
TimeId now)
|
||||
: _id(id)
|
||||
, _peer(peer)
|
||||
, _repostSourcePeer(RepostSourcePeer(&peer->owner(), data))
|
||||
, _repostSourceName(RepostSourceName(data))
|
||||
, _repostSourceId(RepostSourceId(data))
|
||||
, _date(data.vdate().v)
|
||||
, _expires(data.vexpire_date().v) {
|
||||
applyFields(std::move(media), data, now, true);
|
||||
|
@ -730,6 +758,22 @@ TimeId Story::lastUpdateTime() const {
|
|||
return _lastUpdateTime;
|
||||
}
|
||||
|
||||
bool Story::repost() const {
|
||||
return _repostSourcePeer || !_repostSourceName.isEmpty();
|
||||
}
|
||||
|
||||
PeerData *Story::repostSourcePeer() const {
|
||||
return _repostSourcePeer;
|
||||
}
|
||||
|
||||
QString Story::repostSourceName() const {
|
||||
return _repostSourceName;
|
||||
}
|
||||
|
||||
StoryId Story::repostSourceId() const {
|
||||
return _repostSourceId;
|
||||
}
|
||||
|
||||
StoryPreload::StoryPreload(not_null<Story*> story, Fn<void()> done)
|
||||
: _story(story)
|
||||
, _done(std::move(done)) {
|
||||
|
|
|
@ -181,6 +181,11 @@ public:
|
|||
void applyViewsCounts(const MTPDstoryViews &data);
|
||||
[[nodiscard]] TimeId lastUpdateTime() const;
|
||||
|
||||
[[nodiscard]] bool repost() const;
|
||||
[[nodiscard]] PeerData *repostSourcePeer() const;
|
||||
[[nodiscard]] QString repostSourceName() const;
|
||||
[[nodiscard]] StoryId repostSourceId() const;
|
||||
|
||||
private:
|
||||
struct ViewsCounts {
|
||||
int views = 0;
|
||||
|
@ -203,6 +208,9 @@ private:
|
|||
|
||||
const StoryId _id = 0;
|
||||
const not_null<PeerData*> _peer;
|
||||
PeerData * const _repostSourcePeer = nullptr;
|
||||
const QString _repostSourceName;
|
||||
const StoryId _repostSourceId = 0;
|
||||
Data::ReactionId _sentReactionId;
|
||||
StoryMedia _media;
|
||||
TextWithEntities _caption;
|
||||
|
|
|
@ -38,30 +38,40 @@ namespace {
|
|||
|
||||
constexpr auto kNonExpandedLinesLimit = 5;
|
||||
|
||||
} // namespace
|
||||
|
||||
void ValidateBackgroundEmoji(
|
||||
DocumentId backgroundEmojiId,
|
||||
not_null<Ui::BackgroundEmojiData*> data,
|
||||
not_null<Ui::BackgroundEmojiCache*> cache,
|
||||
not_null<Ui::Text::QuotePaintCache*> quote,
|
||||
not_null<const Element*> view) {
|
||||
if (data->firstFrameMask.isNull() && !data->emoji) {
|
||||
data->emoji = CreateBackgroundEmojiInstance(
|
||||
&view->history()->owner(),
|
||||
backgroundEmojiId,
|
||||
crl::guard(view, [=] { view->repaint(); }));
|
||||
}
|
||||
ValidateBackgroundEmoji(backgroundEmojiId, data, cache, quote);
|
||||
}
|
||||
|
||||
void ValidateBackgroundEmoji(
|
||||
DocumentId backgroundEmojiId,
|
||||
not_null<Ui::BackgroundEmojiData*> data,
|
||||
not_null<Ui::BackgroundEmojiCache*> cache,
|
||||
not_null<Ui::Text::QuotePaintCache*> quote) {
|
||||
Expects(!data->firstFrameMask.isNull() || data->emoji != nullptr);
|
||||
|
||||
if (data->firstFrameMask.isNull()) {
|
||||
if (!cache->frames[0].isNull()) {
|
||||
for (auto &frame : cache->frames) {
|
||||
frame = QImage();
|
||||
}
|
||||
}
|
||||
const auto tag = Data::CustomEmojiSizeTag::Isolated;
|
||||
if (!data->emoji) {
|
||||
const auto repaint = crl::guard(view, [=] { view->repaint(); });
|
||||
const auto owner = &view->history()->owner();
|
||||
data->emoji = owner->customEmojiManager().create(
|
||||
backgroundEmojiId,
|
||||
repaint,
|
||||
tag);
|
||||
}
|
||||
if (!data->emoji->ready()) {
|
||||
return;
|
||||
}
|
||||
const auto tag = Data::CustomEmojiSizeTag::Isolated;
|
||||
const auto size = Data::FrameSizeFromTag(tag);
|
||||
data->firstFrameMask = QImage(
|
||||
QSize(size, size),
|
||||
|
@ -115,8 +125,19 @@ void ValidateBackgroundEmoji(
|
|||
cache->frames[2] = make(kSize3);
|
||||
}
|
||||
|
||||
auto CreateBackgroundEmojiInstance(
|
||||
not_null<Data::Session*> owner,
|
||||
DocumentId backgroundEmojiId,
|
||||
Fn<void()> repaint)
|
||||
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
return owner->customEmojiManager().create(
|
||||
backgroundEmojiId,
|
||||
repaint,
|
||||
Data::CustomEmojiSizeTag::Isolated);
|
||||
}
|
||||
|
||||
void FillBackgroundEmoji(
|
||||
Painter &p,
|
||||
QPainter &p,
|
||||
const QRect &rect,
|
||||
bool quote,
|
||||
const Ui::BackgroundEmojiCache &cache) {
|
||||
|
@ -157,8 +178,6 @@ void FillBackgroundEmoji(
|
|||
p.setOpacity(1.);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reply::Reply()
|
||||
: _name(st::maxSignatureSize / 2)
|
||||
, _text(st::maxSignatureSize / 2) {
|
||||
|
@ -799,6 +818,12 @@ void Reply::stopLastRipple() {
|
|||
TextWithEntities Reply::PeerEmoji(
|
||||
not_null<History*> history,
|
||||
PeerData *peer) {
|
||||
return PeerEmoji(&history->owner(), peer);
|
||||
}
|
||||
|
||||
TextWithEntities Reply::PeerEmoji(
|
||||
not_null<Data::Session*> owner,
|
||||
PeerData *peer) {
|
||||
using namespace std;
|
||||
const auto icon = !peer
|
||||
? pair(&st::historyReplyUser, st::historyReplyUserPadding)
|
||||
|
@ -807,7 +832,6 @@ TextWithEntities Reply::PeerEmoji(
|
|||
: (peer->isChannel() || peer->isChat())
|
||||
? pair(&st::historyReplyGroup, st::historyReplyGroupPadding)
|
||||
: pair(&st::historyReplyUser, st::historyReplyUserPadding);
|
||||
const auto owner = &history->owner();
|
||||
return Ui::Text::SingleCustomEmoji(
|
||||
owner->customEmojiManager().registerInternalEmoji(
|
||||
*icon.first,
|
||||
|
|
|
@ -9,12 +9,48 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "history/view/history_view_element.h"
|
||||
|
||||
namespace Data {
|
||||
class Session;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
class SpoilerAnimation;
|
||||
struct BackgroundEmojiData;
|
||||
struct BackgroundEmojiCache;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::Text {
|
||||
class CustomEmoji;
|
||||
struct QuotePaintCache;
|
||||
} // namespace Ui::Text
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
void ValidateBackgroundEmoji(
|
||||
DocumentId backgroundEmojiId,
|
||||
not_null<Ui::BackgroundEmojiData*> data,
|
||||
not_null<Ui::BackgroundEmojiCache*> cache,
|
||||
not_null<Ui::Text::QuotePaintCache*> quote,
|
||||
not_null<const Element*> view);
|
||||
|
||||
// For this one data->firstFrameMask or data->emoji must be already set.
|
||||
void ValidateBackgroundEmoji(
|
||||
DocumentId backgroundEmojiId,
|
||||
not_null<Ui::BackgroundEmojiData*> data,
|
||||
not_null<Ui::BackgroundEmojiCache*> cache,
|
||||
not_null<Ui::Text::QuotePaintCache*> quote);
|
||||
[[nodiscard]] auto CreateBackgroundEmojiInstance(
|
||||
not_null<Data::Session*> owner,
|
||||
DocumentId backgroundEmojiId,
|
||||
Fn<void()> repaint)
|
||||
-> std::unique_ptr<Ui::Text::CustomEmoji>;
|
||||
|
||||
void FillBackgroundEmoji(
|
||||
QPainter &p,
|
||||
const QRect &rect,
|
||||
bool quote,
|
||||
const Ui::BackgroundEmojiCache &cache);
|
||||
|
||||
class Reply final : public RuntimeComponent<Reply, Element> {
|
||||
public:
|
||||
Reply();
|
||||
|
@ -66,6 +102,9 @@ public:
|
|||
[[nodiscard]] static TextWithEntities PeerEmoji(
|
||||
not_null<History*> history,
|
||||
PeerData *peer);
|
||||
[[nodiscard]] static TextWithEntities PeerEmoji(
|
||||
not_null<Data::Session*> owner,
|
||||
PeerData *peer);
|
||||
[[nodiscard]] static TextWithEntities ComposePreviewName(
|
||||
not_null<History*> history,
|
||||
not_null<HistoryItem*> to,
|
||||
|
|
|
@ -26,7 +26,7 @@ CaptionFullView::CaptionFullView(not_null<Controller*> controller)
|
|||
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
|
||||
_scroll.get(),
|
||||
object_ptr<Ui::FlatLabel>(_scroll.get(), st::storiesCaptionFull),
|
||||
st::mediaviewCaptionPadding)))
|
||||
st::mediaviewCaptionPadding + _controller->repostCaptionPadding())))
|
||||
, _text(_wrap->entity()) {
|
||||
_text->setMarkedText(controller->captionText(), Core::MarkedTextContext{
|
||||
.session = &controller->uiShow()->session(),
|
||||
|
|
|
@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "media/stories/media_stories_reactions.h"
|
||||
#include "media/stories/media_stories_recent_views.h"
|
||||
#include "media/stories/media_stories_reply.h"
|
||||
#include "media/stories/media_stories_repost_view.h"
|
||||
#include "media/stories/media_stories_share.h"
|
||||
#include "media/stories/media_stories_stealth.h"
|
||||
#include "media/stories/media_stories_view.h"
|
||||
|
@ -332,6 +333,8 @@ Controller::Controller(not_null<Delegate*> delegate)
|
|||
}
|
||||
|
||||
Controller::~Controller() {
|
||||
_captionFullView = nullptr;
|
||||
_repostView = nullptr;
|
||||
changeShown(nullptr);
|
||||
}
|
||||
|
||||
|
@ -586,7 +589,34 @@ TextWithEntities Controller::captionText() const {
|
|||
}
|
||||
|
||||
bool Controller::skipCaption() const {
|
||||
return _captionFullView != nullptr;
|
||||
return (_captionFullView != nullptr)
|
||||
|| (_captionText.empty() && !repost());
|
||||
}
|
||||
|
||||
bool Controller::repost() const {
|
||||
return _repostView != nullptr;
|
||||
}
|
||||
|
||||
int Controller::repostSkipTop() const {
|
||||
return _repostView
|
||||
? (_repostView->height()
|
||||
+ (_captionText.empty() ? 0 : st::mediaviewTextSkip))
|
||||
: 0;
|
||||
}
|
||||
|
||||
QRect Controller::captionWithRepostGeometry(QRect caption) const {
|
||||
return caption.marginsAdded(st::mediaviewCaptionPadding).marginsAdded(
|
||||
{ 0, repostSkipTop(), 0, 0 });
|
||||
}
|
||||
|
||||
void Controller::drawRepostInfo(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int availableWidth) const {
|
||||
Expects(_repostView != nullptr);
|
||||
|
||||
_repostView->draw(p, x, y - repostSkipTop(), availableWidth);
|
||||
}
|
||||
|
||||
void Controller::toggleLiked() {
|
||||
|
@ -837,6 +867,7 @@ void Controller::show(
|
|||
}
|
||||
|
||||
captionClosed();
|
||||
_repostView = validateRepostView(story);
|
||||
_captionText = story->caption();
|
||||
_contentFaded = false;
|
||||
_contentFadeAnimation.stop();
|
||||
|
@ -1341,6 +1372,10 @@ void Controller::repaintSibling(not_null<Sibling*> sibling) {
|
|||
}
|
||||
}
|
||||
|
||||
void Controller::repaint() {
|
||||
_delegate->storiesRepaint();
|
||||
}
|
||||
|
||||
SiblingView Controller::sibling(SiblingType type) const {
|
||||
const auto &pointer = (type == SiblingType::Left)
|
||||
? _siblingLeft
|
||||
|
@ -1428,6 +1463,13 @@ StoryId Controller::shownId(int index) const {
|
|||
: StoryId();
|
||||
}
|
||||
|
||||
std::unique_ptr<RepostView> Controller::validateRepostView(
|
||||
not_null<Data::Story*> story) {
|
||||
return story->repost()
|
||||
? std::make_unique<RepostView>(this, story)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
void Controller::loadMoreToList() {
|
||||
Expects(shown());
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ struct SiblingView;
|
|||
enum class SiblingType;
|
||||
struct ContentLayout;
|
||||
class CaptionFullView;
|
||||
class RepostView;
|
||||
enum class ReactionsMode;
|
||||
class SuggestedReactionView;
|
||||
|
||||
|
@ -121,11 +122,15 @@ public:
|
|||
[[nodiscard]] Data::FileOrigin fileOrigin() const;
|
||||
[[nodiscard]] TextWithEntities captionText() const;
|
||||
[[nodiscard]] bool skipCaption() const;
|
||||
[[nodiscard]] bool repost() const;
|
||||
void toggleLiked();
|
||||
void showFullCaption();
|
||||
void captionClosing();
|
||||
void captionClosed();
|
||||
|
||||
[[nodiscard]] QRect captionWithRepostGeometry(QRect caption) const;
|
||||
void drawRepostInfo(Painter &p, int x, int y, int availableWidth) const;
|
||||
|
||||
[[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow() const;
|
||||
[[nodiscard]] auto stickerOrEmojiChosen() const
|
||||
-> rpl::producer<ChatHelpers::FileChosen>;
|
||||
|
@ -152,6 +157,7 @@ public:
|
|||
void changeVolume(float64 volume);
|
||||
void volumeChangeFinished();
|
||||
|
||||
void repaint();
|
||||
void repaintSibling(not_null<Sibling*> sibling);
|
||||
[[nodiscard]] SiblingView sibling(SiblingType type) const;
|
||||
|
||||
|
@ -239,6 +245,8 @@ private:
|
|||
[[nodiscard]] PeerData *shownPeer() const;
|
||||
[[nodiscard]] int shownCount() const;
|
||||
[[nodiscard]] StoryId shownId(int index) const;
|
||||
[[nodiscard]] std::unique_ptr<RepostView> validateRepostView(
|
||||
not_null<Data::Story*> story);
|
||||
void rebuildFromContext(not_null<PeerData*> peer, FullStoryId storyId);
|
||||
void checkMoveByDelta();
|
||||
void loadMoreToList();
|
||||
|
@ -247,6 +255,7 @@ private:
|
|||
const std::vector<Data::StoriesSourceInfo> &lists,
|
||||
int index);
|
||||
|
||||
[[nodiscard]] int repostSkipTop() const;
|
||||
void updateAreas(Data::Story *story);
|
||||
void reactionChosen(ReactionsMode mode, ChosenReaction chosen);
|
||||
|
||||
|
@ -263,6 +272,7 @@ private:
|
|||
std::unique_ptr<Unsupported> _unsupported;
|
||||
std::unique_ptr<PhotoPlayback> _photoPlayback;
|
||||
std::unique_ptr<CaptionFullView> _captionFullView;
|
||||
std::unique_ptr<RepostView> _repostView;
|
||||
|
||||
Ui::Animations::Simple _contentFadeAnimation;
|
||||
bool _contentFaded = false;
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
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_repost_view.h"
|
||||
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "history/view/history_view_reply.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "media/stories/media_stories_controller.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/text/text_custom_emoji.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/power_saving.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_media_view.h"
|
||||
|
||||
namespace Media::Stories {
|
||||
|
||||
RepostView::RepostView(
|
||||
not_null<Controller*> controller,
|
||||
not_null<Data::Story*> story)
|
||||
: _controller(controller)
|
||||
, _story(story) {
|
||||
Expects(_story->repost());
|
||||
|
||||
_story->session().colorIndicesValue(
|
||||
) | rpl::start_with_next([=](Ui::ColorIndicesCompressed &&indices) {
|
||||
_colorIndices = std::move(indices);
|
||||
if (_maxWidth) {
|
||||
_controller->repaint();
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
RepostView::~RepostView() = default;
|
||||
|
||||
int RepostView::height() const {
|
||||
return st::historyReplyPadding.top()
|
||||
+ st::semiboldFont->height
|
||||
+ st::normalFont->height
|
||||
+ st::historyReplyPadding.bottom();
|
||||
}
|
||||
|
||||
void RepostView::draw(Painter &p, int x, int y, int availableWidth) {
|
||||
if (!_maxWidth) {
|
||||
recountDimensions();
|
||||
}
|
||||
const auto w = std::min(_maxWidth, availableWidth);
|
||||
const auto rect = QRect(x, y, w, height());
|
||||
const auto colorPeer = _story->repostSourcePeer();
|
||||
const auto backgroundEmojiId = colorPeer
|
||||
? colorPeer->backgroundEmojiId()
|
||||
: DocumentId();
|
||||
const auto cache = &_quoteCache;
|
||||
const auto "eSt = st::messageQuoteStyle;
|
||||
const auto backgroundEmoji = backgroundEmojiId
|
||||
? &_backgroundEmojiData
|
||||
: nullptr;
|
||||
const auto backgroundEmojiCache = backgroundEmoji
|
||||
? &backgroundEmoji->caches[0]
|
||||
: nullptr;
|
||||
|
||||
auto rippleColor = cache->bg;
|
||||
cache->bg = QColor(0, 0, 0, 64);
|
||||
Ui::Text::ValidateQuotePaintCache(*cache, quoteSt);
|
||||
Ui::Text::FillQuotePaint(p, rect, *cache, quoteSt);
|
||||
if (backgroundEmoji) {
|
||||
using namespace HistoryView;
|
||||
if (backgroundEmoji->firstFrameMask.isNull()
|
||||
&& !backgroundEmoji->emoji) {
|
||||
backgroundEmoji->emoji = CreateBackgroundEmojiInstance(
|
||||
&_story->owner(),
|
||||
backgroundEmojiId,
|
||||
crl::guard(this, [=] { _controller->repaint(); }));
|
||||
}
|
||||
ValidateBackgroundEmoji(
|
||||
backgroundEmojiId,
|
||||
backgroundEmoji,
|
||||
backgroundEmojiCache,
|
||||
cache);
|
||||
if (!backgroundEmojiCache->frames[0].isNull()) {
|
||||
const auto hasQuoteIcon = false;
|
||||
FillBackgroundEmoji(
|
||||
p,
|
||||
rect,
|
||||
hasQuoteIcon,
|
||||
*backgroundEmojiCache);
|
||||
}
|
||||
}
|
||||
cache->bg = rippleColor;
|
||||
|
||||
if (_ripple) {
|
||||
_ripple->paint(p, x, y, w, &rippleColor);
|
||||
if (_ripple->empty()) {
|
||||
_ripple.reset();
|
||||
}
|
||||
}
|
||||
|
||||
const auto pausedSpoiler = On(PowerSaving::kChatSpoiler);
|
||||
auto textLeft = x + st::historyReplyPadding.left();
|
||||
auto textTop = y
|
||||
+ st::historyReplyPadding.top()
|
||||
+ st::semiboldFont->height;
|
||||
if (w > st::historyReplyPadding.left()) {
|
||||
if (_stateText.isEmpty()) {
|
||||
const auto textw = w
|
||||
- st::historyReplyPadding.left()
|
||||
- st::historyReplyPadding.right();
|
||||
const auto namew = textw;
|
||||
if (namew > 0) {
|
||||
p.setPen(cache->icon);
|
||||
_name.drawLeftElided(
|
||||
p,
|
||||
x + st::historyReplyPadding.left(),
|
||||
y + st::historyReplyPadding.top(),
|
||||
namew,
|
||||
w + 2 * x);
|
||||
_text.draw(p, {
|
||||
.position = { textLeft, textTop },
|
||||
.availableWidth = w,
|
||||
.palette = &st::mediaviewTextPalette,
|
||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||
.pausedEmoji = On(PowerSaving::kEmojiChat),
|
||||
.pausedSpoiler = On(PowerSaving::kChatSpoiler),
|
||||
.elisionLines = 1,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
p.setFont(st::msgDateFont);
|
||||
p.setPen(cache->icon);
|
||||
p.drawTextLeft(
|
||||
textLeft,
|
||||
(y
|
||||
+ st::historyReplyPadding.top()
|
||||
+ (st::msgDateFont->height / 2)),
|
||||
w + 2 * x,
|
||||
st::msgDateFont->elided(
|
||||
_stateText,
|
||||
x + w - textLeft - st::historyReplyPadding.right()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RepostView::recountDimensions() {
|
||||
const auto sender = _story->repostSourcePeer();
|
||||
const auto name = sender ? sender->name() : _story->repostSourceName();
|
||||
const auto owner = &_story->owner();
|
||||
const auto repostId = _story->repostSourceId();
|
||||
|
||||
const auto colorIndexPlusOne = sender
|
||||
? (sender->colorIndex() + 1)
|
||||
: 1;
|
||||
const auto dark = true;
|
||||
const auto colorPattern = colorIndexPlusOne
|
||||
? Ui::ColorPatternIndex(_colorIndices, colorIndexPlusOne - 1, dark)
|
||||
: 0;
|
||||
Assert(colorPattern < Ui::Text::kMaxQuoteOutlines);
|
||||
const auto values = Ui::SimpleColorIndexValues(
|
||||
QColor(255, 255, 255),
|
||||
colorPattern);
|
||||
_quoteCache.bg = values.bg;
|
||||
_quoteCache.outlines = values.outlines;
|
||||
_quoteCache.icon = values.name;
|
||||
|
||||
auto text = TextWithEntities();
|
||||
auto displaying = true;
|
||||
auto unavailable = false;
|
||||
if (sender && repostId) {
|
||||
const auto of = owner->stories().lookup({ sender->id, repostId });
|
||||
displaying = of.has_value();
|
||||
unavailable = !displaying && (of.error() == Data::NoStory::Deleted);
|
||||
if (displaying) {
|
||||
text = (*of)->caption();
|
||||
} else if (!unavailable) {
|
||||
const auto done = crl::guard(this, [=] {
|
||||
_maxWidth = 0;
|
||||
_controller->repaint();
|
||||
});
|
||||
owner->stories().resolve({ sender->id, repostId }, done);
|
||||
}
|
||||
}
|
||||
if (displaying && !unavailable && text.empty()) {
|
||||
text = { tr::lng_in_dlg_story(tr::now) };
|
||||
}
|
||||
|
||||
auto nameFull = TextWithEntities();
|
||||
nameFull.append(HistoryView::Reply::PeerEmoji(owner, sender));
|
||||
nameFull.append(name);
|
||||
auto context = Core::MarkedTextContext{
|
||||
.session = &_story->session(),
|
||||
.customEmojiRepaint = [] {},
|
||||
.customEmojiLoopLimit = 1,
|
||||
};
|
||||
_name.setMarkedText(
|
||||
st::semiboldTextStyle,
|
||||
nameFull,
|
||||
Ui::NameTextOptions(),
|
||||
context);
|
||||
context.customEmojiRepaint = crl::guard(this, [=] {
|
||||
_controller->repaint();
|
||||
}),
|
||||
_text.setMarkedText(
|
||||
st::defaultTextStyle,
|
||||
text,
|
||||
Ui::DialogTextOptions(),
|
||||
context);
|
||||
|
||||
const auto nameMaxWidth = _name.maxWidth();
|
||||
const auto optimalTextWidth = std::min(
|
||||
_text.maxWidth(),
|
||||
st::maxSignatureSize);
|
||||
_maxWidth = std::max(nameMaxWidth, optimalTextWidth);
|
||||
if (!displaying) {
|
||||
_stateText = !unavailable
|
||||
? tr::lng_profile_loading(tr::now)
|
||||
: tr::lng_deleted_story(tr::now);
|
||||
const auto phraseWidth = st::msgDateFont->width(_stateText);
|
||||
_maxWidth = unavailable
|
||||
? phraseWidth
|
||||
: std::max(_maxWidth, phraseWidth);
|
||||
} else {
|
||||
_stateText = QString();
|
||||
}
|
||||
_maxWidth = st::historyReplyPadding.left()
|
||||
+ _maxWidth
|
||||
+ st::historyReplyPadding.right();
|
||||
}
|
||||
|
||||
} // namespace Media::Stories
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
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 "base/weak_ptr.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
|
||||
class Painter;
|
||||
|
||||
namespace Data {
|
||||
class Story;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
class RippleAnimation;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Media::Stories {
|
||||
|
||||
class Controller;
|
||||
|
||||
class RepostView final : public base::has_weak_ptr {
|
||||
public:
|
||||
RepostView(
|
||||
not_null<Controller*> controller,
|
||||
not_null<Data::Story*> story);
|
||||
~RepostView();
|
||||
|
||||
[[nodiscard]] int height() const;
|
||||
void draw(Painter &p, int x, int y, int availableWidth);
|
||||
|
||||
private:
|
||||
void recountDimensions();
|
||||
|
||||
const not_null<Controller*> _controller;
|
||||
const not_null<Data::Story*> _story;
|
||||
std::unique_ptr<Ui::RippleAnimation> _ripple;
|
||||
|
||||
Ui::Text::String _name;
|
||||
Ui::Text::String _text;
|
||||
Ui::Text::QuotePaintCache _quoteCache;
|
||||
Ui::BackgroundEmojiData _backgroundEmojiData;
|
||||
QString _stateText;
|
||||
Ui::ColorIndicesCompressed _colorIndices;
|
||||
int _maxWidth = 0;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Media::Stories
|
|
@ -146,6 +146,22 @@ bool View::skipCaption() const {
|
|||
return _controller->skipCaption();
|
||||
}
|
||||
|
||||
bool View::repost() const {
|
||||
return _controller->repost();
|
||||
}
|
||||
|
||||
QRect View::captionWithRepostGeometry(QRect caption) const {
|
||||
return _controller->captionWithRepostGeometry(caption);
|
||||
}
|
||||
|
||||
void View::drawRepostInfo(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int availableWidth) const {
|
||||
_controller->drawRepostInfo(p, x, y, availableWidth);
|
||||
}
|
||||
|
||||
void View::showFullCaption() {
|
||||
_controller->showFullCaption();
|
||||
}
|
||||
|
|
|
@ -78,8 +78,12 @@ public:
|
|||
[[nodiscard]] Data::FileOrigin fileOrigin() const;
|
||||
[[nodiscard]] TextWithEntities captionText() const;
|
||||
[[nodiscard]] bool skipCaption() const;
|
||||
[[nodiscard]] bool repost() const;
|
||||
void showFullCaption();
|
||||
|
||||
[[nodiscard]] QRect captionWithRepostGeometry(QRect caption) const;
|
||||
void drawRepostInfo(Painter &p, int x, int y, int availableWidth) const;
|
||||
|
||||
void updatePlayback(const Player::TrackState &state);
|
||||
[[nodiscard]] ClickHandlerPtr lookupAreaHandler(QPoint point) const;
|
||||
|
||||
|
|
|
@ -1434,7 +1434,10 @@ void OverlayWidget::refreshCaptionGeometry() {
|
|||
_captionShowMoreWidth = 0;
|
||||
_captionSkipBlockWidth = 0;
|
||||
|
||||
if (_caption.isEmpty()) {
|
||||
const auto storiesCaptionWidth = _w
|
||||
- st::mediaviewCaptionPadding.left()
|
||||
- st::mediaviewCaptionPadding.right();
|
||||
if (_caption.isEmpty() && (!_stories || !_stories->repost())) {
|
||||
_captionRect = QRect();
|
||||
return;
|
||||
}
|
||||
|
@ -1451,9 +1454,7 @@ void OverlayWidget::refreshCaptionGeometry() {
|
|||
? _groupThumbsTop
|
||||
: height() - st::mediaviewCaptionMargin.height();
|
||||
const auto captionWidth = _stories
|
||||
? (_w
|
||||
- st::mediaviewCaptionPadding.left()
|
||||
- st::mediaviewCaptionPadding.right())
|
||||
? storiesCaptionWidth
|
||||
: std::min(
|
||||
(_groupThumbsAvailableWidth
|
||||
- st::mediaviewCaptionPadding.left()
|
||||
|
@ -4612,8 +4613,7 @@ void OverlayWidget::paint(not_null<Renderer*> renderer) {
|
|||
if (!_stories) {
|
||||
renderer->paintFooter(footerGeometry(), opacity);
|
||||
}
|
||||
if (!_caption.isEmpty()
|
||||
&& (!_stories || !_stories->skipCaption())) {
|
||||
if (!(_stories ? _stories->skipCaption() : _caption.isEmpty())) {
|
||||
renderer->paintCaption(captionGeometry(), opacity);
|
||||
}
|
||||
if (_groupThumbs) {
|
||||
|
@ -5020,8 +5020,13 @@ void OverlayWidget::paintCaptionContent(
|
|||
QRect outer,
|
||||
QRect clip,
|
||||
float64 opacity) {
|
||||
const auto inner = outer.marginsRemoved(st::mediaviewCaptionPadding);
|
||||
if (!_stories) {
|
||||
auto inner = outer.marginsRemoved(st::mediaviewCaptionPadding);
|
||||
inner.setTop(inner.top() + inner.height() - _captionRect.height());
|
||||
if (_stories) {
|
||||
if (_stories->repost()) {
|
||||
_stories->drawRepostInfo(p, inner.x(), inner.y(), inner.width());
|
||||
}
|
||||
} else {
|
||||
p.setOpacity(opacity);
|
||||
p.setBrush(st::mediaviewCaptionBg);
|
||||
p.setPen(Qt::NoPen);
|
||||
|
@ -5068,7 +5073,9 @@ void OverlayWidget::paintCaptionContent(
|
|||
}
|
||||
|
||||
QRect OverlayWidget::captionGeometry() const {
|
||||
return _captionRect.marginsAdded(st::mediaviewCaptionPadding);
|
||||
return (_stories && _stories->repost())
|
||||
? _stories->captionWithRepostGeometry(_captionRect)
|
||||
: _captionRect.marginsAdded(st::mediaviewCaptionPadding);
|
||||
}
|
||||
|
||||
void OverlayWidget::paintGroupThumbsContent(
|
||||
|
@ -5710,6 +5717,8 @@ void OverlayWidget::updateOver(QPoint pos) {
|
|||
lnk = ensureCaptionExpandLink();
|
||||
}
|
||||
lnkhost = this;
|
||||
} else if (_stories && captionGeometry().contains(pos)) {
|
||||
//_stories->repostState();
|
||||
} else if (_groupThumbs && _groupThumbsRect.contains(pos)) {
|
||||
const auto point = pos - QPoint(_groupThumbsLeft, _groupThumbsTop);
|
||||
lnk = _groupThumbs->getState(point);
|
||||
|
|
|
@ -143,6 +143,21 @@ int BackgroundEmojiData::CacheIndex(
|
|||
return (base * 2) + (selected ? 1 : 0);
|
||||
};
|
||||
|
||||
int ColorPatternIndex(
|
||||
const ColorIndicesCompressed &indices,
|
||||
uint8 colorIndex,
|
||||
bool dark) {
|
||||
Expects(colorIndex >= 0 && colorIndex < kColorIndexCount);
|
||||
|
||||
if (!indices.colors
|
||||
|| colorIndex < kSimpleColorIndexCount) {
|
||||
return 0;
|
||||
}
|
||||
auto &data = (*indices.colors)[colorIndex];
|
||||
auto &colors = dark ? data.dark : data.light;
|
||||
return colors[2] ? 2 : colors[1] ? 1 : 0;
|
||||
}
|
||||
|
||||
ChatStyle::ChatStyle(rpl::producer<ColorIndicesCompressed> colorIndices) {
|
||||
if (colorIndices) {
|
||||
_colorIndicesLifetime = std::move(
|
||||
|
|
|
@ -236,12 +236,21 @@ struct ColorIndicesCompressed {
|
|||
std::shared_ptr<std::array<ColorIndexData, kColorIndexCount>> colors;
|
||||
};
|
||||
|
||||
[[nodiscard]] int ColorPatternIndex(
|
||||
const ColorIndicesCompressed &indices,
|
||||
uint8 colorIndex,
|
||||
bool dark);
|
||||
|
||||
struct ColorIndexValues {
|
||||
std::array<QColor, kColorPatternsCount> outlines;
|
||||
QColor name;
|
||||
QColor bg;
|
||||
};
|
||||
|
||||
[[nodiscard]] ColorIndexValues SimpleColorIndexValues(
|
||||
QColor color,
|
||||
int patternIndex);
|
||||
|
||||
class ChatStyle final : public style::palette {
|
||||
public:
|
||||
explicit ChatStyle(rpl::producer<ColorIndicesCompressed> colorIndices);
|
||||
|
|
Loading…
Reference in New Issue