Start customizable webpages.

This commit is contained in:
John Preston 2023-10-19 11:05:39 +04:00
parent 486d5b63d3
commit b2e8e0431e
16 changed files with 172 additions and 73 deletions

View File

@ -1434,9 +1434,11 @@ QString MediaCall::Text(
MediaWebPage::MediaWebPage(
not_null<HistoryItem*> parent,
not_null<WebPageData*> page)
not_null<WebPageData*> page,
MediaWebPageFlags flags)
: Media(parent)
, _page(page) {
, _page(page)
, _flags(flags) {
parent->history()->owner().registerWebPageItem(_page, parent);
}
@ -1445,7 +1447,7 @@ MediaWebPage::~MediaWebPage() {
}
std::unique_ptr<Media> MediaWebPage::clone(not_null<HistoryItem*> parent) {
return std::make_unique<MediaWebPage>(parent, _page);
return std::make_unique<MediaWebPage>(parent, _page, _flags);
}
DocumentData *MediaWebPage::document() const {
@ -1524,7 +1526,7 @@ std::unique_ptr<HistoryView::Media> MediaWebPage::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
return std::make_unique<HistoryView::WebPage>(message, _page);
return std::make_unique<HistoryView::WebPage>(message, _page, _flags);
}
MediaGame::MediaGame(

View File

@ -372,7 +372,8 @@ class MediaWebPage final : public Media {
public:
MediaWebPage(
not_null<HistoryItem*> parent,
not_null<WebPageData*> page);
not_null<WebPageData*> page,
MediaWebPageFlags flags);
~MediaWebPage();
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
@ -398,7 +399,8 @@ public:
HistoryView::Element *replacing = nullptr) override;
private:
not_null<WebPageData*> _page;
const not_null<WebPageData*> _page;
const MediaWebPageFlags _flags;
};

View File

@ -3288,6 +3288,7 @@ not_null<WebPageData*> Session::processWebpage(const MTPDwebPagePending &data) {
webpageApplyFields(
result,
WebPageType::Article,
false,
QString(),
QString(),
QString(),
@ -3312,6 +3313,7 @@ not_null<WebPageData*> Session::webpage(
return webpage(
id,
WebPageType::Article,
false,
QString(),
QString(),
siteName,
@ -3328,6 +3330,7 @@ not_null<WebPageData*> Session::webpage(
not_null<WebPageData*> Session::webpage(
WebPageId id,
WebPageType type,
bool hasLargeMedia,
const QString &url,
const QString &displayUrl,
const QString &siteName,
@ -3343,6 +3346,7 @@ not_null<WebPageData*> Session::webpage(
webpageApplyFields(
result,
type,
hasLargeMedia,
url,
displayUrl,
siteName,
@ -3434,6 +3438,7 @@ void Session::webpageApplyFields(
webpageApplyFields(
page,
(story ? WebPageType::Story : ParseWebPageType(data)),
data.is_has_large_media(),
qs(data.vurl()),
qs(data.vdisplay_url()),
siteName,
@ -3459,6 +3464,7 @@ void Session::webpageApplyFields(
void Session::webpageApplyFields(
not_null<WebPageData*> page,
WebPageType type,
bool hasLargeMedia,
const QString &url,
const QString &displayUrl,
const QString &siteName,
@ -3474,6 +3480,7 @@ void Session::webpageApplyFields(
const auto requestPending = (!page->pendingTill && pendingTill > 0);
const auto changed = page->applyChanges(
type,
hasLargeMedia,
url,
displayUrl,
siteName,

View File

@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class Image;
class HistoryItem;
struct WebPageCollage;
enum class WebPageType;
enum class WebPageType : uint8;
enum class NewMessageType;
namespace HistoryView {
@ -552,6 +552,7 @@ public:
[[nodiscard]] not_null<WebPageData*> webpage(
WebPageId id,
WebPageType type,
bool hasLargeMedia,
const QString &url,
const QString &displayUrl,
const QString &siteName,
@ -813,6 +814,7 @@ private:
void webpageApplyFields(
not_null<WebPageData*> page,
WebPageType type,
bool hasLargeMedia,
const QString &url,
const QString &displayUrl,
const QString &siteName,

View File

@ -246,65 +246,75 @@ enum class MessageFlag : uint64 {
MentionsMe = (1ULL << 15),
IsOrWasScheduled = (1ULL << 16),
NoForwards = (1ULL << 17),
InvertMedia = (1ULL << 18),
// Needs to return back to inline mode.
HasSwitchInlineButton = (1ULL << 18),
HasSwitchInlineButton = (1ULL << 19),
// For "shared links" indexing.
HasTextLinks = (1ULL << 19),
HasTextLinks = (1ULL << 20),
// Group / channel create or migrate service message.
IsGroupEssential = (1ULL << 20),
IsGroupEssential = (1ULL << 21),
// Edited media is generated on the client
// and should not update media from server.
IsLocalUpdateMedia = (1ULL << 21),
IsLocalUpdateMedia = (1ULL << 22),
// Sent from inline bot, need to re-set media when sent.
FromInlineBot = (1ULL << 22),
FromInlineBot = (1ULL << 23),
// Generated on the client side and should be unread.
ClientSideUnread = (1ULL << 23),
ClientSideUnread = (1ULL << 24),
// In a supergroup.
HasAdminBadge = (1ULL << 24),
HasAdminBadge = (1ULL << 25),
// Outgoing message that is being sent.
BeingSent = (1ULL << 25),
BeingSent = (1ULL << 26),
// Outgoing message and failed to be sent.
SendingFailed = (1ULL << 26),
SendingFailed = (1ULL << 27),
// No media and only a several emoji or an only custom emoji text.
SpecialOnlyEmoji = (1ULL << 27),
SpecialOnlyEmoji = (1ULL << 28),
// Message existing in the message history.
HistoryEntry = (1ULL << 28),
HistoryEntry = (1ULL << 29),
// Local message, not existing on the server.
Local = (1ULL << 29),
Local = (1ULL << 30),
// Fake message for some UI element.
FakeHistoryItem = (1ULL << 30),
FakeHistoryItem = (1ULL << 31),
// Contact sign-up message, notification should be skipped for Silent.
IsContactSignUp = (1ULL << 31),
IsContactSignUp = (1ULL << 32),
// Optimization for item text custom emoji repainting.
CustomEmojiRepainting = (1ULL << 32),
CustomEmojiRepainting = (1ULL << 33),
// Profile photo suggestion, views have special media type.
IsUserpicSuggestion = (1ULL << 33),
IsUserpicSuggestion = (1ULL << 34),
OnlyEmojiAndSpaces = (1ULL << 34),
OnlyEmojiAndSpacesSet = (1ULL << 35),
OnlyEmojiAndSpaces = (1ULL << 35),
OnlyEmojiAndSpacesSet = (1ULL << 36),
// Fake message with bot cover and information.
FakeBotAbout = (1ULL << 36),
FakeBotAbout = (1ULL << 37),
StoryItem = (1ULL << 37),
StoryItem = (1ULL << 38),
InHighlightProcess = (1ULL << 38),
InHighlightProcess = (1ULL << 39),
};
inline constexpr bool is_flag_type(MessageFlag) { return true; }
using MessageFlags = base::flags<MessageFlag>;
enum class MediaWebPageFlag : uint8 {
ForceLargeMedia = (1 << 0),
ForceSmallMedia = (1 << 1),
Manual = (1 << 2),
Safe = (1 << 3),
};
inline constexpr bool is_flag_type(MediaWebPageFlag) { return true; }
using MediaWebPageFlags = base::flags<MediaWebPageFlag>;

View File

@ -213,6 +213,7 @@ Main::Session &WebPageData::session() const {
bool WebPageData::applyChanges(
WebPageType newType,
bool newHasLargeMedia,
const QString &newUrl,
const QString &newDisplayUrl,
const QString &newSiteName,
@ -254,6 +255,7 @@ bool WebPageData::applyChanges(
}();
if (type == newType
&& hasLargeMedia == newHasLargeMedia
&& url == resultUrl
&& displayUrl == resultDisplayUrl
&& siteName == resultSiteName
@ -272,6 +274,7 @@ bool WebPageData::applyChanges(
_owner->session().api().clearWebPageRequest(this);
}
type = newType;
hasLargeMedia = newHasLargeMedia;
url = resultUrl;
displayUrl = resultDisplayUrl;
siteName = resultSiteName;

View File

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/flags.h"
#include "data/data_photo.h"
#include "data/data_document.h"
@ -16,7 +17,7 @@ namespace Data {
class Session;
} // namespace Data
enum class WebPageType {
enum class WebPageType : uint8 {
Message,
Group,
@ -44,8 +45,7 @@ enum class WebPageType {
VoiceChat,
Livestream,
};
WebPageType ParseWebPageType(const MTPDwebPage &type);
[[nodiscard]] WebPageType ParseWebPageType(const MTPDwebPage &type);
struct WebPageCollage {
using Item = std::variant<PhotoData*, DocumentData*>;
@ -67,6 +67,7 @@ struct WebPageData {
bool applyChanges(
WebPageType newType,
bool newHasLargeMedia,
const QString &newUrl,
const QString &newDisplayUrl,
const QString &newSiteName,
@ -87,17 +88,18 @@ struct WebPageData {
WebPageId id = 0;
WebPageType type = WebPageType::Article;
bool hasLargeMedia = false;
QString url;
QString displayUrl;
QString siteName;
QString title;
TextWithEntities description;
FullStoryId storyId;
int duration = 0;
QString author;
PhotoData *photo = nullptr;
DocumentData *document = nullptr;
WebPageCollage collage;
int duration = 0;
int pendingTill = 0;
int version = 0;

View File

@ -252,16 +252,28 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
return nullptr;
});
}, [&](const MTPDmessageMediaWebPage &media) {
using Flag = MediaWebPageFlag;
const auto flags = Flag()
| (media.is_force_large_media()
? Flag::ForceLargeMedia
: Flag())
| (media.is_force_small_media()
? Flag::ForceSmallMedia
: Flag())
| (media.is_manual() ? Flag::Manual : Flag())
| (media.is_safe() ? Flag::Safe : Flag());
return media.vwebpage().match([](const MTPDwebPageEmpty &) -> Result {
return nullptr;
}, [&](const MTPDwebPagePending &webpage) -> Result {
return std::make_unique<Data::MediaWebPage>(
item,
item->history()->owner().processWebpage(webpage));
item->history()->owner().processWebpage(webpage),
flags);
}, [&](const MTPDwebPage &webpage) -> Result {
return std::make_unique<Data::MediaWebPage>(
item,
item->history()->owner().processWebpage(webpage));
item->history()->owner().processWebpage(webpage),
flags);
}, [](const MTPDwebPageNotModified &) -> Result {
LOG(("API Error: "
"webPageNotModified is unexpected in message media."));
@ -503,6 +515,9 @@ HistoryItem::HistoryItem(
};
if (mediaOriginal && !ignoreMedia()) {
_media = mediaOriginal->clone(this);
if (original->invertMedia()) {
_flags |= MessageFlag::InvertMedia;
}
}
const auto dropCustomEmoji = dropForwardInfo

View File

@ -240,6 +240,9 @@ public:
[[nodiscard]] bool isPinned() const {
return _flags & MessageFlag::Pinned;
}
[[nodiscard]] bool invertMedia() const {
return _flags & MessageFlag::InvertMedia;
}
[[nodiscard]] bool unread(not_null<Data::Thread*> thread) const;
[[nodiscard]] bool showNotification() const;
void markClientSideAsRead();

View File

@ -331,9 +331,12 @@ MessageFlags FlagsFromMTP(
| ((flags & MTP::f_from_id) ? Flag::HasFromId : Flag())
| ((flags & MTP::f_reply_to) ? Flag::HasReplyInfo : Flag())
| ((flags & MTP::f_reply_markup) ? Flag::HasReplyMarkup : Flag())
| ((flags & MTP::f_from_scheduled) ? Flag::IsOrWasScheduled : Flag())
| ((flags & MTP::f_from_scheduled)
? Flag::IsOrWasScheduled
: Flag())
| ((flags & MTP::f_views) ? Flag::HasViews : Flag())
| ((flags & MTP::f_noforwards) ? Flag::NoForwards : Flag());
| ((flags & MTP::f_noforwards) ? Flag::NoForwards : Flag())
| ((flags & MTP::f_invert_media) ? Flag::InvertMedia : Flag());
}
MessageFlags FlagsFromMTP(

View File

@ -411,6 +411,7 @@ Message::Message(
not_null<HistoryItem*> data,
Element *replacing)
: Element(delegate, data, replacing, Flag(0))
, _invertMedia(data->invertMedia() && !data->emptyText())
, _bottomInfo(
&data->history()->owner().reactions(),
BottomInfoDataFromMessage(this)) {
@ -1078,16 +1079,20 @@ void Message::draw(Painter &p, const PaintContext &context) const {
trect.setHeight(trect.height()
- (_bottomInfo.height() - st::msgDateFont->height));
}
paintText(p, trect, context);
if (mediaDisplayed) {
auto mediaHeight = media->height();
auto mediaPosition = QPoint(
inner.left(),
trect.y() + trect.height() - mediaHeight);
auto textSelection = context.selection;
const auto mediaHeight = mediaDisplayed ? media->height() : 0;
const auto paintMedia = [&](int top) {
if (!mediaDisplayed) {
return;
}
const auto mediaSelection = _invertMedia
? context.selection
: skipTextSelection(context.selection);
auto mediaPosition = QPoint(inner.left(), top);
p.translate(mediaPosition);
media->draw(p, context.translated(
-mediaPosition
).withSelection(skipTextSelection(context.selection)));
).withSelection(mediaSelection));
if (context.reactionInfo && !displayInfo && !_reactions) {
const auto add = QPoint(0, mediaHeight);
context.reactionInfo->position = mediaPosition + add;
@ -1096,6 +1101,27 @@ void Message::draw(Painter &p, const PaintContext &context) const {
}
}
p.translate(-mediaPosition);
};
if (mediaDisplayed && _invertMedia) {
if (!mediaOnTop) {
trect.setY(trect.y() + st::mediaInBubbleSkip);
}
paintMedia(trect.y());
trect.setY(trect.y()
+ mediaHeight
+ (mediaOnBottom ? 0 : st::mediaInBubbleSkip));
textSelection = media->skipSelection(textSelection);
}
paintText(p, trect, context.withSelection(textSelection));
if (mediaDisplayed && !_invertMedia) {
paintMedia(trect.y() + trect.height() - mediaHeight);
if (context.reactionInfo && !displayInfo && !_reactions) {
context.reactionInfo->position
= QPoint(inner.left(), trect.y() + trect.height());
if (context.reactionInfo->effectPaint) {
context.reactionInfo->effectOffset -= QPoint(0, mediaHeight);
}
}
}
if (entry) {
auto entryLeft = inner.left();
@ -2106,25 +2132,33 @@ TextState Message::textState(
}
};
if (!result.symbol && inBubble) {
if (mediaDisplayed) {
auto mediaHeight = media->height();
auto mediaLeft = trect.x() - st::msgPadding.left();
auto mediaTop = (trect.y() + trect.height() - mediaHeight);
if (point.y() >= mediaTop && point.y() < mediaTop + mediaHeight) {
result = media->textState(point - QPoint(mediaLeft, mediaTop), request);
const auto mediaHeight = mediaDisplayed ? media->height() : 0;
const auto mediaLeft = trect.x() - st::msgPadding.left();
const auto mediaTop = (!mediaDisplayed || _invertMedia)
? (trect.y() + (mediaOnTop ? 0 : st::mediaInBubbleSkip))
: (trect.y() + trect.height() - mediaHeight);
if (mediaDisplayed && _invertMedia) {
trect.setY(mediaTop
+ mediaHeight
+ (mediaOnBottom ? 0 : st::mediaInBubbleSkip));
}
if (point.y() >= mediaTop
&& point.y() < mediaTop + mediaHeight) {
result = media->textState(
point - QPoint(mediaLeft, mediaTop),
request);
if (!_invertMedia) {
result.symbol += visibleTextLength();
} else if (getStateText(point, trect, &result, request)) {
checkBottomInfoState();
return result;
} else if (point.y() >= trect.y() + trect.height()) {
result.symbol = visibleTextLength();
}
} else if (getStateText(point, trect, &result, request)) {
if (_invertMedia) {
result.symbol += visibleMediaTextLength();
}
checkBottomInfoState();
return result;
} else if (point.y() >= trect.y() + trect.height()) {
result.symbol = visibleTextLength();
result.symbol = visibleTextLength()
+ visibleMediaTextLength();
}
}
checkBottomInfoState();
@ -3031,7 +3065,8 @@ void Message::initLogEntryOriginal() {
if (const auto log = data()->Get<HistoryMessageLogEntryOriginal>()) {
AddComponents(LogEntryOriginal::Bit());
const auto entry = Get<LogEntryOriginal>();
entry->page = std::make_unique<WebPage>(this, log->page);
using Flags = MediaWebPageFlags;
entry->page = std::make_unique<WebPage>(this, log->page, Flags());
}
}
@ -3491,7 +3526,8 @@ void Message::updateMediaInBubbleState() {
}
const auto reactionsInBubble = (_reactions && embedReactionsInBubble());
auto mediaHasSomethingBelow = (_viewButton != nullptr)
|| reactionsInBubble;
|| reactionsInBubble
|| (invertMedia() && hasVisibleText());
auto mediaHasSomethingAbove = false;
auto getMediaHasSomethingAbove = [&] {
return displayFromName()
@ -3529,7 +3565,7 @@ void Message::updateMediaInBubbleState() {
if (!entry) {
mediaHasSomethingAbove = getMediaHasSomethingAbove();
}
if (hasVisibleText()) {
if (!invertMedia() && hasVisibleText()) {
mediaHasSomethingAbove = true;
}
const auto state = [&] {
@ -3661,7 +3697,7 @@ QRect Message::countGeometry() const {
// contentLeft += st::msgPhotoSkip - (hmaxwidth - hwidth);
}
accumulate_min(contentWidth, maxWidth());
accumulate_min(contentWidth, _bubbleWidthLimit);
accumulate_min(contentWidth, int(_bubbleWidthLimit));
if (mediaWidth < contentWidth) {
const auto textualWidth = plainMaxWidth();
if (mediaWidth < textualWidth
@ -3768,7 +3804,7 @@ int Message::resizeContentGetHeight(int newWidth) {
}
accumulate_min(contentWidth, maxWidth());
_bubbleWidthLimit = std::max(st::msgMaxWidth, monospaceMaxWidth());
accumulate_min(contentWidth, _bubbleWidthLimit);
accumulate_min(contentWidth, int(_bubbleWidthLimit));
if (mediaDisplayed) {
media->resizeGetHeight(contentWidth);
if (media->width() < contentWidth) {
@ -3924,11 +3960,15 @@ bool Message::needInfoDisplay() const {
const auto entry = logEntryOriginal();
return entry
? !entry->customInfoLayout()
: (mediaDisplayed
: ((mediaDisplayed && media->isBubbleBottom())
? !media->customInfoLayout()
: true);
}
bool Message::invertMedia() const {
return _invertMedia;
}
bool Message::hasVisibleText() const {
if (data()->emptyText()) {
if (const auto media = data()->media()) {

View File

@ -270,6 +270,7 @@ private:
[[nodiscard]] int visibleTextLength() const;
[[nodiscard]] int visibleMediaTextLength() const;
[[nodiscard]] bool needInfoDisplay() const;
[[nodiscard]] bool invertMedia() const;
[[nodiscard]] bool isPinnedContext() const;
@ -309,7 +310,8 @@ private:
mutable std::unique_ptr<FromNameStatus> _fromNameStatus;
Ui::Text::String _rightBadge;
mutable int _fromNameVersion = 0;
int _bubbleWidthLimit = 0;
uint32 _bubbleWidthLimit : 31 = 0;
uint32 _invertMedia : 1 = 0;
BottomInfo _bottomInfo;

View File

@ -121,14 +121,16 @@ std::vector<std::unique_ptr<Data::Media>> PrepareCollageMedia(
WebPage::WebPage(
not_null<Element*> parent,
not_null<WebPageData*> data)
not_null<WebPageData*> data,
MediaWebPageFlags flags)
: Media(parent)
, _st(st::historyPagePreview)
, _data(data)
, _colorIndex(parent->data()->computeColorIndex())
, _siteName(st::msgMinWidth - _st.padding.left() - _st.padding.right())
, _title(st::msgMinWidth - _st.padding.left() - _st.padding.right())
, _description(st::msgMinWidth - _st.padding.left() - _st.padding.right()) {
, _description(st::msgMinWidth - _st.padding.left() - _st.padding.right())
, _flags(flags) {
history()->owner().registerWebPageView(_data, _parent);
}
@ -238,7 +240,12 @@ QSize WebPage::countOptimalSize() {
auto title = TextUtilities::SingleLine(_data->title.isEmpty()
? _data->author
: _data->title);
if (!_collage.empty()) {
using Flag = MediaWebPageFlag;
if (_data->hasLargeMedia && (_flags & Flag::ForceLargeMedia)) {
_asArticle = 0;
} else if (_data->photo && (_flags & Flag::ForceSmallMedia)) {
_asArticle = 1;
} else if (!_collage.empty()) {
_asArticle = 0;
} else if (!_data->document
&& _data->photo
@ -956,11 +963,9 @@ TextForMimeData WebPage::selectedText(TextSelection selection) const {
QMargins WebPage::inBubblePadding() const {
return {
st::msgPadding.left(),
isBubbleTop() ? st::msgPadding.left() : st::mediaInBubbleSkip,
isBubbleTop() ? st::msgPadding.left() : 0,
st::msgPadding.right(),
(isBubbleBottom()
? (st::msgPadding.left() + bottomInfoPadding())
: st::mediaInBubbleSkip),
isBubbleBottom() ? (st::msgPadding.left() + bottomInfoPadding()) : 0
};
}

View File

@ -24,7 +24,8 @@ class WebPage : public Media {
public:
WebPage(
not_null<Element*> parent,
not_null<WebPageData*> data);
not_null<WebPageData*> data,
MediaWebPageFlags flags);
[[nodiscard]] static bool HasButton(not_null<WebPageData*> data);
@ -151,6 +152,8 @@ private:
int _pixw = 0;
int _pixh = 0;
const MediaWebPageFlags _flags;
};
} // namespace HistoryView

View File

@ -2168,5 +2168,5 @@ stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool;
premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:string limit:int = premium.BoostsList;
premium.getMyBoosts#be77b4a = premium.MyBoosts;
premium.applyBoost#46b6a16b flags:# slot:flags.0?int peer:InputPeer = Bool;
premium.applyBoost#184bc3b9 flags:# slots:flags.0?Vector<int> peer:InputPeer = Bool;
premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus;

View File

@ -785,7 +785,7 @@ void SessionNavigation::applyBoostChecked(
Fn<void(bool)> done) {
_api.request(MTPpremium_ApplyBoost(
MTP_flags(0),
MTPint(), // slot
MTPVector<MTPint>(), // slots
channel->input
)).done([=](const MTPBool &result) {
done(true);