Added initial implementation of top bar for sponsored messages.

This commit is contained in:
23rd 2024-10-26 11:00:44 +03:00
parent c857c24a64
commit 8a1cf2bb3a
8 changed files with 322 additions and 2 deletions

View file

@ -1503,6 +1503,8 @@ PRIVATE
ui/chat/choose_send_as.h
ui/chat/choose_theme_controller.cpp
ui/chat/choose_theme_controller.h
ui/chat/sponsored_message_bar.cpp
ui/chat/sponsored_message_bar.h
ui/controls/emoji_button_factory.cpp
ui/controls/emoji_button_factory.h
ui/controls/location_picker.cpp

View file

@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/chat/sponsored_message_bar.h"
#include "ui/text/text_utilities.h" // Ui::Text::RichLangValue.
namespace Data {
@ -260,12 +261,33 @@ void SponsoredMessages::parse(
list.postsBetween = postsBetween->v;
list.state = State::InjectToMiddle;
} else {
list.state = State::AppendToEnd;
list.state = history->peer->isChannel()
? State::AppendToEnd
: State::AppendToTopBar;
}
}, [](const MTPDmessages_sponsoredMessagesEmpty &) {
});
}
void SponsoredMessages::fillTopBar(
not_null<History*> history,
not_null<Ui::RpWidget*> widget) {
const auto it = _data.find(history);
if (it == end(_data)) {
return;
}
const auto &list = it->second;
if (list.entries.empty()) {
return;
}
Ui::FillSponsoredMessageBar(
widget,
_session,
list.entries.front().itemFullId,
list.entries.front().sponsored.from,
list.entries.front().sponsored.textWithEntities);
}
void SponsoredMessages::append(
not_null<History*> history,
List &list,

View file

@ -18,6 +18,10 @@ namespace Main {
class Session;
} // namespace Main
namespace Ui {
class RpWidget;
} // namespace Ui
namespace Data {
class MediaPreload;
@ -76,6 +80,7 @@ public:
None,
AppendToEnd,
InjectToMiddle,
AppendToTopBar,
};
struct Details {
std::vector<TextWithEntities> info;
@ -98,6 +103,9 @@ public:
void clearItems(not_null<History*> history);
[[nodiscard]] Details lookupDetails(const FullMsgId &fullId) const;
void clicked(const FullMsgId &fullId, bool isMedia, bool isFullscreen);
void fillTopBar(
not_null<History*> history,
not_null<Ui::RpWidget*> widget);
[[nodiscard]] AppendResult append(not_null<History*> history);
void inject(

View file

@ -1611,6 +1611,9 @@ void HistoryWidget::orderWidgets() {
if (_translateBar) {
_translateBar->raise();
}
if (_sponsoredMessageBar) {
_sponsoredMessageBar->raise();
}
if (_pinnedBar) {
_pinnedBar->raise();
}
@ -2284,6 +2287,7 @@ void HistoryWidget::showHistory(
_history->showAtMsgId = _showAtMsgId;
destroyUnreadBarOnClose();
_sponsoredMessageBar = nullptr;
_pinnedBar = nullptr;
_translateBar = nullptr;
_pinnedTracker = nullptr;
@ -2520,6 +2524,26 @@ void HistoryWidget::showHistory(
session().sponsoredMessages().canHaveFor(_history));
} else if (state == State::InjectToMiddle) {
injectSponsoredMessages();
} else if (state == State::AppendToTopBar) {
_sponsoredMessageBar
= base::make_unique_q<Ui::SlideWrap<>>(
this,
object_ptr<Ui::RpWidget>(this));
session().sponsoredMessages().fillTopBar(
_history,
_sponsoredMessageBar->entity());
_sponsoredMessageBarHeight = 0;
_sponsoredMessageBar->heightValue(
) | rpl::start_with_next([=](int height) {
_topDelta = _preserveScrollTop
? 0
: (height - _sponsoredMessageBarHeight);
_sponsoredMessageBarHeight = height;
updateHistoryGeometry();
updateControlsGeometry();
_topDelta = 0;
}, _sponsoredMessageBar->lifetime());
_sponsoredMessageBar->show(anim::type::normal);
}
});
session().sponsoredMessages().request(_history, checkState);
@ -2931,6 +2955,9 @@ void HistoryWidget::updateControlsVisibility() {
if (_pinnedBar) {
_pinnedBar->show();
}
if (_sponsoredMessageBar) {
_sponsoredMessageBar->show(anim::type::instant);
}
if (_translateBar) {
_translateBar->show();
}
@ -4128,6 +4155,9 @@ void HistoryWidget::hideChildWidgets() {
if (_pinnedBar) {
_pinnedBar->hide();
}
if (_sponsoredMessageBar) {
_sponsoredMessageBar->hide(anim::type::instant);
}
if (_translateBar) {
_translateBar->hide();
}
@ -4414,6 +4444,9 @@ void HistoryWidget::showAnimated(
if (_pinnedBar) {
_pinnedBar->finishAnimating();
}
if (_sponsoredMessageBar) {
_sponsoredMessageBar->finishAnimating();
}
if (_translateBar) {
_translateBar->finishAnimating();
}
@ -4452,6 +4485,9 @@ void HistoryWidget::showFinished() {
if (_pinnedBar) {
_pinnedBar->finishAnimating();
}
if (_sponsoredMessageBar) {
_sponsoredMessageBar->finishAnimating();
}
if (_translateBar) {
_translateBar->finishAnimating();
}
@ -4484,6 +4520,9 @@ void HistoryWidget::doneShow() {
if (_pinnedBar) {
_pinnedBar->finishAnimating();
}
if (_sponsoredMessageBar) {
_sponsoredMessageBar->finishAnimating();
}
if (_translateBar) {
_translateBar->finishAnimating();
}
@ -5999,8 +6038,14 @@ void HistoryWidget::updateControlsGeometry() {
_pinnedBar->move(0, pinnedBarTop);
_pinnedBar->resizeToWidth(width());
}
const auto translateTop = pinnedBarTop
const auto sponsoredMessageBarTop = pinnedBarTop
+ (_pinnedBar ? _pinnedBar->height() : 0);
if (_sponsoredMessageBar) {
_sponsoredMessageBar->move(0, sponsoredMessageBarTop);
_sponsoredMessageBar->resizeToWidth(width());
}
const auto translateTop = sponsoredMessageBarTop
+ (_sponsoredMessageBar ? _sponsoredMessageBar->height() : 0);
if (_translateBar) {
_translateBar->move(0, translateTop);
_translateBar->resizeToWidth(width());
@ -6245,6 +6290,9 @@ void HistoryWidget::updateHistoryGeometry(
if (_translateBar) {
newScrollHeight -= _translateBar->height();
}
if (_sponsoredMessageBar) {
newScrollHeight -= _sponsoredMessageBar->height();
}
if (_pinnedBar) {
newScrollHeight -= _pinnedBar->height();
}
@ -6662,6 +6710,7 @@ int HistoryWidget::computeMaxFieldHeight() const {
- _topBar->height()
- (_contactStatus ? _contactStatus->bar().height() : 0)
- (_businessBotStatus ? _businessBotStatus->bar().height() : 0)
- (_sponsoredMessageBar ? _sponsoredMessageBar->height() : 0)
- (_pinnedBar ? _pinnedBar->height() : 0)
- (_groupCallBar ? _groupCallBar->height() : 0)
- (_requestsBar ? _requestsBar->height() : 0)

View file

@ -73,6 +73,8 @@ class SpoilerAnimation;
class ChooseThemeController;
class ContinuousScroll;
struct ChatPaintHighlight;
template <typename Widget>
class SlideWrap;
} // namespace Ui
namespace Ui::Emoji {
@ -687,6 +689,9 @@ private:
std::unique_ptr<Ui::RequestsBar> _requestsBar;
int _requestsBarHeight = 0;
base::unique_qptr<Ui::SlideWrap<Ui::RpWidget>> _sponsoredMessageBar;
int _sponsoredMessageBarHeight = 0;
bool _preserveScrollTop = false;
bool _repaintFieldScheduled = false;

View file

@ -1153,3 +1153,5 @@ purchasedTagPadding: margins(3px, 2px, 6px, 2px);
msgSelectionCheck: RoundCheckbox(defaultPeerListCheck) {
bgActive: boxTextFgGood;
}
sponsoredMessageBarMaxHeight: 156px;

View file

@ -0,0 +1,203 @@
/*
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 "ui/chat/sponsored_message_bar.h"
#include "core/ui_integration.h" // Core::MarkedTextContext.
#include "data/components/sponsored_messages.h"
#include "data/data_session.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/dynamic_image.h"
#include "ui/dynamic_thumbnails.h"
#include "ui/image/image_prepare.h"
#include "ui/rect.h"
#include "ui/rp_widget.h"
#include "ui/widgets/shadow.h"
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h"
namespace Ui {
void FillSponsoredMessageBar(
not_null<RpWidget*> widget,
not_null<Main::Session*> session,
FullMsgId fullId,
Data::SponsoredFrom from,
const TextWithEntities &textWithEntities) {
struct State final {
Ui::Text::String title;
Ui::Text::String contentTitle;
Ui::Text::String contentText;
rpl::variable<int> lastPaintedContentLineAmount = 0;
rpl::variable<int> lastPaintedContentTop = 0;
std::shared_ptr<Ui::DynamicImage> rightPhoto;
QImage rightPhotoImage;
};
const auto state = widget->lifetime().make_state<State>();
state->title.setText(
st::semiboldTextStyle,
from.isRecommended
? tr::lng_recommended_message_title(tr::now)
: tr::lng_sponsored_message_title(tr::now));
state->contentTitle.setText(st::semiboldTextStyle, from.title);
state->contentText.setMarkedText(
st::defaultTextStyle,
textWithEntities,
kMarkupTextOptions,
Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = [=] { widget->update(); },
});
const auto kLinesForPhoto = 3;
const auto rightPhotoSize = state->title.style()->font->ascent
* kLinesForPhoto;
const auto rightPhotoPlaceholder = state->title.style()->font->height
* kLinesForPhoto;
const auto hasRightPhoto = from.photoId > 0;
if (hasRightPhoto) {
state->rightPhoto = Ui::MakePhotoThumbnail(
session->data().photo(from.photoId),
fullId);
const auto callback = [=] {
state->rightPhotoImage = Images::Round(
state->rightPhoto->image(rightPhotoSize),
ImageRoundRadius::Small);
widget->update();
};
state->rightPhoto->subscribeToUpdates(callback);
callback();
}
widget->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(widget);
const auto r = widget->rect();
p.fillRect(r, st::historyPinnedBg);
const auto leftPadding = st::msgReplyBarSkip + st::msgReplyBarSkip;
const auto rightPadding = st::msgReplyBarSkip;
const auto topPadding = st::msgReplyPadding.top();
const auto availableWidthNoPhoto = r.width()
- leftPadding
- rightPadding;
const auto availableWidth = availableWidthNoPhoto
- (hasRightPhoto ? (rightPadding + rightPhotoSize) : 0);
const auto titleRight = leftPadding
+ state->title.maxWidth()
+ state->title.style()->font->spacew * 2;
const auto hasSecondLineTitle
= (availableWidth - state->contentTitle.maxWidth() < titleRight);
p.setPen(st::windowActiveTextFg);
state->title.draw(p, {
.position = QPoint(leftPadding, topPadding),
.outerWidth = availableWidth,
.availableWidth = availableWidth,
});
p.setPen(st::windowFg);
{
const auto left = hasSecondLineTitle ? leftPadding : titleRight;
const auto top = hasSecondLineTitle
? (topPadding + state->title.style()->font->height)
: topPadding;
state->contentTitle.draw(p, {
.position = QPoint(left, top),
.outerWidth = hasSecondLineTitle
? availableWidth
: (availableWidth - titleRight),
.availableWidth = availableWidth,
.elisionLines = 1,
});
}
{
const auto left = leftPadding;
const auto top = hasSecondLineTitle
? (topPadding
+ state->title.style()->font->height
+ state->contentTitle.style()->font->height)
: topPadding + state->title.style()->font->height;
auto lastContentLineAmount = 0;
const auto lineHeight = state->contentText.style()->font->height;
const auto lineLayout = [&](int line) -> Ui::Text::LineGeometry {
line++;
lastContentLineAmount = line;
const auto diff = (st::sponsoredMessageBarMaxHeight)
- line * lineHeight;
if (diff < 3 * lineHeight) {
return {
.width = availableWidthNoPhoto,
.elided = true,
};
} else if (diff < 2 * lineHeight) {
return {};
}
if (hasRightPhoto) {
line += (hasSecondLineTitle ? 2 : 1);
return {
.width = (line > kLinesForPhoto)
? availableWidthNoPhoto
: availableWidth,
};
} else {
return { .width = availableWidth };
}
};
state->contentText.draw(p, {
.position = QPoint(left, top),
.outerWidth = availableWidth,
.availableWidth = availableWidth,
.geometry = Ui::Text::GeometryDescriptor{
.layout = std::move(lineLayout),
},
});
state->lastPaintedContentTop = top;
state->lastPaintedContentLineAmount = lastContentLineAmount;
}
if (hasRightPhoto) {
p.drawImage(
r.width() - rightPadding - rightPhotoSize,
topPadding + (rightPhotoPlaceholder - rightPhotoSize) / 2,
state->rightPhotoImage);
}
}, widget->lifetime());
rpl::combine(
state->lastPaintedContentTop.value(),
state->lastPaintedContentLineAmount.value()
) | rpl::distinct_until_changed() | rpl::start_with_next([=](
int lastTop,
int lastLines) {
const auto bottomPadding = st::msgReplyPadding.top();
const auto desiredHeight = lastTop
+ (lastLines * state->contentText.style()->font->height)
+ bottomPadding;
const auto minHeight = hasRightPhoto
? (rightPhotoPlaceholder + bottomPadding * 2)
: desiredHeight;
widget->resize(
widget->width(),
std::clamp(
desiredHeight,
minHeight,
st::sponsoredMessageBarMaxHeight));
}, widget->lifetime());
widget->resize(widget->width(), 1);
{
const auto top = Ui::CreateChild<PlainShadow>(widget);
const auto bottom = Ui::CreateChild<PlainShadow>(widget);
widget->sizeValue() | rpl::start_with_next([=] (const QSize &s) {
top->show();
top->raise();
top->resizeToWidth(s.width());
bottom->show();
bottom->raise();
bottom->resizeToWidth(s.width());
bottom->moveToLeft(0, s.height() - bottom->height());
}, top->lifetime());
}
}
} // namespace Ui

View file

@ -0,0 +1,29 @@
/*
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
namespace Data {
struct SponsoredFrom;
} // namespace Data
namespace Main {
class Session;
} // namespace Main
namespace Ui {
class RpWidget;
void FillSponsoredMessageBar(
not_null<RpWidget*> widget,
not_null<Main::Session*> session,
FullMsgId fullId,
Data::SponsoredFrom from,
const TextWithEntities &textWithEntities);
} // namespace Ui