Implement required paywalls in tags.

This commit is contained in:
John Preston 2024-01-29 11:07:53 +04:00
parent 46579ac84d
commit 11cf0486cb
16 changed files with 242 additions and 60 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

View file

@ -2814,8 +2814,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_read_hidden" = "read";
"lng_context_read_show" = "show when";
"lng_add_tag_about" = "Tag this message with an emoji for quick search.";
"lng_subscribe_tag_about" = "Organize your Saved Messages with tags. {link}";
"lng_subscribe_tag_link" = "Learn More...";
"lng_edit_tag_about" = "You can label your emoji tag with a text name.";
"lng_edit_tag_name" = "Name";
"lng_add_tag_button" = "Add tags";
"lng_add_tag_phrase" = "to messages {arrow}";
"lng_add_tag_phrase_long" = "to your Saved Messages {arrow}";
"lng_unlock_tags" = "Unlock";
"lng_context_animated_emoji" = "This message contains emoji from **{name} pack**.";
"lng_context_animated_emoji_many#one" = "This message contains emoji from **{count} pack**.";
@ -4471,6 +4478,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stories_channel_archive_done_many#one" = "{count} story is hidden from the channel page.";
"lng_stories_channel_archive_done_many#other" = "{count} stories are hidden from the channel page.";
"lng_stories_save_promo" = "Subscribe to {link} to download other people's unprotected stories to disk.";
"lng_stories_reaction_as_message" = "Send reaction as a private message";
"lng_stealth_mode_menu_item" = "Stealth Mode";
"lng_stealth_mode_title" = "Stealth Mode";

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "boxes/share_box.h"
#include "boxes/connection_box.h"
#include "boxes/premium_preview_box.h"
#include "boxes/sticker_set_box.h"
#include "boxes/sessions_box.h"
#include "boxes/language_box.h"
@ -657,6 +658,17 @@ bool CopyPeerId(
return true;
}
bool ShowSearchTagsPromo(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
if (!controller) {
return false;
}
ShowPremiumPreviewBox(controller, PremiumPreview::TagsForMessages);
return true;
}
void ExportTestChatTheme(
not_null<Window::SessionController*> controller,
not_null<const Data::CloudTheme*> theme) {
@ -1020,6 +1032,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
{
u"^copy:(.+)$"_q,
CopyPeerId
},
{
u"about_tags"_q,
ShowSearchTagsPromo
}
};
return Result;

View file

@ -626,3 +626,7 @@ searchedBarPosition: point(17px, 7px);
dialogsSearchTagSkip: point(8px, 4px);
dialogsSearchTagBottom: 10px;
dialogsSearchTagLocked: icon{{ "emoji/premium_lock", lightButtonFgOver }};
dialogsSearchTagPromo: defaultTextStyle;
dialogsSearchTagArrow: icon{{ "dialogs/tags_arrow", windowSubTextFg }};
dialogsSearchTagArrowPadding: margins(0px, 3px, 0px, 0px);

View file

@ -18,8 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_search_tags.h"
#include "history/history.h"
#include "history/history_item.h"
#include "core/shortcuts.h"
#include "core/application.h"
#include "core/click_handler_types.h"
#include "core/shortcuts.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/scroll_area.h"
@ -1783,7 +1784,11 @@ void InnerWidget::mousePressReleased(
}
}
if (auto activated = ClickHandler::unpressed()) {
ActivateClickHandler(window(), activated, ClickContext{ button });
ActivateClickHandler(window(), activated, ClickContext{
button,
QVariant::fromValue(ClickHandlerContext{
.sessionWindow = _controller,
}) });
}
}
@ -3002,8 +3007,8 @@ void InnerWidget::searchInChat(
update(0, searchInChatOffset(), width(), height);
}, _searchTags->lifetime());
_searchTags->heightValue() | rpl::filter(
rpl::mappers::_1 > 0
_searchTags->heightValue() | rpl::skip(
1
) | rpl::start_with_next([=] {
refresh();
moveCancelSearchButtons();

View file

@ -8,13 +8,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_search_tags.h"
#include "base/qt/qt_key_modifiers.h"
#include "boxes/premium_preview_box.h"
#include "core/click_handler_types.h"
#include "core/ui_integration.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_document.h"
#include "data/data_message_reactions.h"
#include "data/data_peer_values.h"
#include "data/data_session.h"
#include "history/view/reactions/history_view_reactions.h"
#include "main/main_session.h"
#include "lang/lang_keys.h"
#include "ui/effects/animation_value.h"
#include "ui/text/text_utilities.h"
#include "ui/painter.h"
#include "ui/power_saving.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_dialogs.h"
@ -32,6 +41,45 @@ namespace {
return TextUtilities::SingleLine(result);
}
[[nodiscard]] ClickHandlerPtr MakePromoLink() {
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
ShowPremiumPreviewBox(
controller,
PremiumPreview::TagsForMessages);
}
});
}
[[nodiscard]] Ui::Text::String FillAdditionalText(
not_null<Data::Session*> owner,
int width) {
auto emoji = Ui::Text::SingleCustomEmoji(
owner->customEmojiManager().registerInternalEmoji(
st::dialogsSearchTagArrow,
st::dialogsSearchTagArrowPadding));
auto result = Ui::Text::String();
const auto context = Core::MarkedTextContext{
.session = &owner->session(),
.customEmojiRepaint = [] {},
.customEmojiLoopLimit = 1,
};
const auto attempt = [&](const auto &phrase) {
result.setMarkedText(
st::dialogsSearchTagPromo,
phrase(tr::now, lt_arrow, emoji, Ui::Text::WithEntities),
kMarkupTextOptions,
context);
return result.maxWidth() < width;
};
if (attempt(tr::lng_add_tag_phrase_long)
|| attempt(tr::lng_add_tag_phrase)) {
return result;
}
return {};
}
} // namespace
struct SearchTags::Tag {
@ -43,6 +91,7 @@ struct SearchTags::Tag {
QRect geometry;
ClickHandlerPtr link;
bool selected = false;
bool promo = false;
};
SearchTags::SearchTags(
@ -51,10 +100,13 @@ SearchTags::SearchTags(
std::vector<Data::ReactionId> selected)
: _owner(owner)
, _added(selected) {
std::move(
tags
) | rpl::start_with_next([=](const std::vector<Data::Reaction> &list) {
fill(list);
rpl::combine(
std::move(tags),
Data::AmPremiumValue(&owner->session())
) | rpl::start_with_next([=](
const std::vector<Data::Reaction> &list,
bool premium) {
fill(list, premium);
}, _lifetime);
// Mark the `selected` reactions as selected in `_tags`.
@ -73,12 +125,19 @@ SearchTags::SearchTags(
SearchTags::~SearchTags() = default;
void SearchTags::fill(const std::vector<Data::Reaction> &list) {
void SearchTags::fill(
const std::vector<Data::Reaction> &list,
bool premium) {
const auto selected = collectSelected();
_tags.clear();
_tags.reserve(list.size());
const auto link = [&](Data::ReactionId id) {
return std::make_shared<LambdaClickHandler>(crl::guard(this, [=] {
return std::make_shared<LambdaClickHandler>(crl::guard(this, [=](
ClickContext context) {
if (!premium) {
MakePromoLink()->onClick(context);
return;
}
const auto i = ranges::find(_tags, id, &Tag::id);
if (i != end(_tags)) {
if (!i->selected && !base::IsShiftPressed()) {
@ -109,6 +168,18 @@ void SearchTags::fill(const std::vector<Data::Reaction> &list) {
_owner->reactions().preloadImageFor(id);
}
};
if (!premium) {
const auto text = (list.empty() && _added.empty())
? tr::lng_add_tag_button(tr::now)
: tr::lng_unlock_tags(tr::now);
_tags.push_back({
.id = Data::ReactionId(),
.text = text,
.textWidth = st::reactionInlineTagFont->width(text),
.link = MakePromoLink(),
.promo = true,
});
}
for (const auto &reaction : list) {
if (reaction.count > 0
|| ranges::contains(_added, reaction.id)
@ -131,10 +202,11 @@ void SearchTags::layout() {
Expects(_width > 0);
if (_tags.empty()) {
_additionalText = {};
_height = 0;
return;
}
const auto &bg = validateBg(false);
const auto &bg = validateBg(false, false);
const auto skip = st::dialogsSearchTagSkip;
const auto size = bg.size() / bg.devicePixelRatio();
const auto xbase = size.width();
@ -147,10 +219,17 @@ void SearchTags::layout() {
x = 0;
y += ybase + skip.y();
}
tag.geometry = QRect(x, y, width, xbase);
tag.geometry = QRect(x, y, width, ybase);
x += width + skip.x();
}
_height = y + ybase + st::dialogsSearchTagBottom;
if (_tags.size() == 1 && _tags.front().promo) {
_additionalLeft = x;
const auto additionalWidth = _width - _additionalLeft;
_additionalText = FillAdditionalText(_owner, additionalWidth);
} else {
_additionalText = {};
}
}
void SearchTags::resizeToWidth(int width) {
@ -177,6 +256,14 @@ ClickHandlerPtr SearchTags::lookupHandler(QPoint point) const {
for (const auto &tag : _tags) {
if (tag.geometry.contains(point.x(), point.y())) {
return tag.link;
} else if (tag.promo
&& !_additionalText.isEmpty()
&& tag.geometry.united(QRect(
_additionalLeft,
tag.geometry.y(),
_additionalText.maxWidth(),
tag.geometry.height())).contains(point.x(), point.y())) {
return tag.link;
}
}
return nullptr;
@ -227,7 +314,7 @@ void SearchTags::paintCustomFrame(
}
void SearchTags::paint(
QPainter &p,
Painter &p,
QPoint position,
crl::time now,
bool paused) const {
@ -236,9 +323,9 @@ void SearchTags::paint(
const auto padding = st::reactionInlinePadding;
for (const auto &tag : _tags) {
const auto geometry = tag.geometry.translated(position);
paintBackground(p, geometry, tag.selected);
paintBackground(p, geometry, tag);
paintText(p, geometry, tag);
if (!tag.custom && tag.image.isNull()) {
if (!tag.custom && !tag.promo && tag.image.isNull()) {
tag.image = _owner->reactions().resolveImageFor(
tag.id,
::Data::Reactions::ImageSize::InlineList);
@ -247,7 +334,9 @@ void SearchTags::paint(
const auto image = QRect(
inner.topLeft() + QPoint(skip, skip),
QSize(st::reactionInlineImage, st::reactionInlineImage));
if (const auto custom = tag.custom.get()) {
if (tag.promo) {
st::dialogsSearchTagLocked.paintInCenter(p, image);
} else if (const auto custom = tag.custom.get()) {
const auto textFg = tag.selected
? st::dialogsNameFgActive->c
: st::dialogsNameFgOver->c;
@ -262,13 +351,26 @@ void SearchTags::paint(
p.drawImage(image.topLeft(), tag.image);
}
}
paintAdditionalText(p, position);
}
void SearchTags::paintAdditionalText(Painter &p, QPoint position) const {
if (_additionalText.isEmpty()) {
return;
}
const auto x = position.x() + _additionalLeft;
const auto tag = _tags.front().geometry;
const auto height = st::dialogsSearchTagPromo.font->height;
const auto y = position.y() + tag.y() + (tag.height() - height) / 2;
p.setPen(st::windowSubTextFg);
_additionalText.drawLeft(p, x, y, _width - x, _width);
}
void SearchTags::paintBackground(
QPainter &p,
QRect geometry,
bool selected) const {
const auto &image = validateBg(selected);
const Tag &tag) const {
const auto &image = validateBg(tag.selected, tag.promo);
const auto ratio = int(image.devicePixelRatio());
const auto size = image.size() / ratio;
if (const auto fill = geometry.width() - size.width(); fill > 0) {
@ -282,7 +384,7 @@ void SearchTags::paintBackground(
QRect(QPoint(), QSize(left, size.height()) * ratio));
p.fillRect(
QRect(x + left, y, fill, size.height()),
bgColor(selected));
bgColor(tag.selected, tag.promo));
p.drawImage(
QRect(x + left + fill, y, right, size.height()),
image,
@ -292,30 +394,39 @@ void SearchTags::paintBackground(
}
}
void SearchTags::paintText(QPainter &p, QRect geometry, const Tag &tag) const {
void SearchTags::paintText(
QPainter &p,
QRect geometry,
const Tag &tag) const {
using namespace HistoryView::Reactions;
if (tag.text.isEmpty()) {
return;
}
p.setPen(tag.selected ? st::dialogsTextFgActive : st::windowSubTextFg);
p.setPen(tag.promo
? st::lightButtonFgOver
: tag.selected
? st::dialogsTextFgActive
: st::windowSubTextFg);
p.setFont(st::reactionInlineTagFont);
const auto x = geometry.x() + st::reactionInlineTagNamePosition.x();
const auto y = geometry.y() + st::reactionInlineTagNamePosition.y();
p.drawText(x, y + st::reactionInlineTagFont->ascent, tag.text);
}
QColor SearchTags::bgColor(bool selected) const {
return selected
QColor SearchTags::bgColor(bool selected, bool promo) const {
return promo
? st::lightButtonBgOver->c
: selected
? st::dialogsBgActive->c
: st::dialogsBgOver->c;
}
const QImage &SearchTags::validateBg(bool selected) const {
const QImage &SearchTags::validateBg(bool selected, bool promo) const {
using namespace HistoryView::Reactions;
auto &image = selected ? _selectedBg : _normalBg;
auto &image = promo ? _promoBg : selected ? _selectedBg : _normalBg;
if (image.isNull()) {
const auto tagBg = bgColor(selected);
const auto tagBg = bgColor(selected, promo);
const auto dotBg = st::transparent->c;
image = InlineList::PrepareTagBg(tagBg, dotBg);
}

View file

@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/weak_ptr.h"
class Painter;
namespace Data {
class Session;
struct Reaction;
@ -39,7 +41,7 @@ public:
-> rpl::producer<std::vector<Data::ReactionId>>;
void paint(
QPainter &p,
Painter &p,
QPoint position,
crl::time now,
bool paused) const;
@ -49,7 +51,7 @@ public:
private:
struct Tag;
void fill(const std::vector<Data::Reaction> &list);
void fill(const std::vector<Data::Reaction> &list, bool premium);
void paintCustomFrame(
QPainter &p,
not_null<Ui::Text::CustomEmoji*> emoji,
@ -59,25 +61,26 @@ private:
const QColor &textColor) const;
void layout();
[[nodiscard]] std::vector<Data::ReactionId> collectSelected() const;
[[nodiscard]] QColor bgColor(bool selected) const;
[[nodiscard]] const QImage &validateBg(bool selected) const;
void paintBackground(
QPainter &p,
QRect geometry,
bool selected) const;
[[nodiscard]] QColor bgColor(bool selected, bool promo) const;
[[nodiscard]] const QImage &validateBg(bool selected, bool promo) const;
void paintAdditionalText(Painter &p, QPoint position) const;
void paintBackground(QPainter &p, QRect geometry, const Tag &tag) const;
void paintText(QPainter &p, QRect geometry, const Tag &tag) const;
const not_null<Data::Session*> _owner;
std::vector<Data::ReactionId> _added;
std::vector<Tag> _tags;
Ui::Text::String _additionalText;
rpl::event_stream<> _selectedChanges;
rpl::event_stream<> _repaintRequests;
mutable QImage _normalBg;
mutable QImage _selectedBg;
mutable QImage _promoBg;
mutable QImage _customCache;
mutable int _customSkip = 0;
rpl::variable<int> _height;
int _width = 0;
int _additionalLeft = 0;
rpl::lifetime _lifetime;

View file

@ -2636,9 +2636,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
desiredPosition,
reactItem,
[=](ChosenReaction reaction) { reactionChosen(reaction); },
(reactItem->reactionsAreTags()
? TextWithEntities{ u"Organize your Saved Messages with tags. Learn More..."_q }
: TextWithEntities()),
ItemReactionsAbout(reactItem),
_controller->cachedReactionIconFactory().createMethod())
: AttachSelectorResult::Skipped;
if (attached == AttachSelectorResult::Failed) {

View file

@ -2633,7 +2633,7 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
desiredPosition,
reactItem,
[=](ChosenReaction reaction) { reactionChosen(reaction); },
TextWithEntities(),
ItemReactionsAbout(reactItem),
_controller->cachedReactionIconFactory().createMethod())
: AttachSelectorResult::Skipped;
if (attached == AttachSelectorResult::Failed) {

View file

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_reply.h"
#include "history/view/history_view_view_button.h" // ViewButton.
#include "history/history.h"
#include "boxes/premium_preview_box.h"
#include "boxes/share_box.h"
#include "ui/effects/glare.h"
#include "ui/effects/reaction_fly_animation.h"
@ -49,13 +50,13 @@ namespace {
constexpr auto kPlayStatusLimit = 2;
const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_";
[[nodiscard]] std::optional<Window::SessionController*> ExtractController(
[[nodiscard]] Window::SessionController *ExtractController(
const ClickContext &context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
return controller;
}
return std::nullopt;
return nullptr;
}
class KeyboardStyle : public ReplyKeyboard::Style {
@ -2288,11 +2289,8 @@ ClickHandlerPtr Message::createGoToCommentsLink() const {
const auto fullId = data()->fullId();
const auto sessionId = data()->history()->session().uniqueId();
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto controller = ExtractController(context).value_or(nullptr);
if (!controller) {
return;
}
if (controller->session().uniqueId() != sessionId) {
const auto controller = ExtractController(context);
if (!controller || controller->session().uniqueId() != sessionId) {
return;
}
if (const auto item = controller->session().data().message(fullId)) {
@ -2984,12 +2982,20 @@ void Message::refreshReactions() {
return std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
if (const auto strong = weak.get()) {
if (strong->data()->reactionsAreTags()) {
const auto tag = Data::SearchTagToQuery(id);
HashtagClickHandler(tag).onClick(context);
const auto item = strong->data();
if (item->reactionsAreTags()) {
if (item->history()->session().premium()) {
const auto tag = Data::SearchTagToQuery(id);
HashtagClickHandler(tag).onClick(context);
} else if (const auto controller
= ExtractController(context)) {
ShowPremiumPreviewBox(
controller,
PremiumPreview::TagsForMessages);
}
return;
}
strong->data()->toggleReaction(
item->toggleReaction(
id,
HistoryItem::ReactionSource::Existing);
if (const auto now = weak.get()) {
@ -3586,11 +3592,8 @@ ClickHandlerPtr Message::prepareRightActionLink() const {
};
return std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
const auto controller = ExtractController(context).value_or(nullptr);
if (!controller) {
return;
}
if (controller->session().uniqueId() != sessionId) {
const auto controller = ExtractController(context);
if (!controller || controller->session().uniqueId() != sessionId) {
return;
}

View file

@ -12,13 +12,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/shadow.h"
#include "ui/text/text_custom_emoji.h"
#include "ui/text/text_utilities.h"
#include "ui/platform/ui_platform_utility.h"
#include "ui/painter.h"
#include "history/history.h"
#include "history/history_item.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_session.h"
#include "data/stickers/data_custom_emoji.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "chat_helpers/emoji_list_widget.h"
#include "chat_helpers/stickers_list_footer.h"
@ -277,6 +280,13 @@ Selector::Selector(
, _skipy((st::reactStripHeight - st::reactStripSize) / 2) {
setMouseTracking(true);
if (_about) {
_about->setClickHandlerFilter([=](const auto &...) {
_escapes.fire({});
return true;
});
}
_useTransparency = child || Ui::Platform::TranslucentWindowsSupported();
}
@ -1173,6 +1183,10 @@ AttachSelectorResult AttachSelectorToMenu(
chosen(std::move(reaction));
}, selector->lifetime());
selector->escapes() | rpl::start_with_next([=] {
menu->hideMenu();
}, selector->lifetime());
const auto weak = base::make_weak(controller);
controller->enableGifPauseReason(
Window::GifPauseReason::MediaPreview);
@ -1249,4 +1263,18 @@ auto AttachSelectorToMenu(
return selector;
}
TextWithEntities ItemReactionsAbout(not_null<HistoryItem*> item) {
return !item->reactionsAreTags()
? TextWithEntities()
: item->history()->session().premium()
? TextWithEntities{ tr::lng_add_tag_about(tr::now) }
: tr::lng_subscribe_tag_about(
tr::now,
lt_link,
Ui::Text::Link(
tr::lng_subscribe_tag_link(tr::now),
u"internal:about_tags"_q),
Ui::Text::WithEntities);
}
} // namespace HistoryView::Reactions

View file

@ -265,4 +265,7 @@ AttachSelectorResult AttachSelectorToMenu(
IconFactory iconFactory
) -> base::expected<not_null<Selector*>, AttachSelectorResult>;
[[nodiscard]] TextWithEntities ItemReactionsAbout(
not_null<HistoryItem*> item);
} // namespace HistoryView::Reactions

View file

@ -27,7 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "main/main_session.h"
#include "media/stories/media_stories_controller.h"
#include "lang/lang_tag.h"
#include "lang/lang_keys.h"
#include "ui/chat/chat_style.h"
#include "ui/effects/emoji_fly_animation.h"
#include "ui/effects/path_shift_gradient.h"
@ -676,7 +676,7 @@ void Reactions::Panel::create() {
_controller->uiShow(),
std::move(reactions),
TextWithEntities{ (mode == Mode::Message
? u"Send reaction as a private message"_q
? tr::lng_stories_reaction_as_message(tr::now)
: QString()) },
_controller->cachedReactionIconFactory().createMethod(),
[=](bool fast) { hide(mode); });

View file

@ -525,13 +525,16 @@ bool ShowReactPremiumError(
not_null<SessionController*> controller,
not_null<HistoryItem*> item,
const Data::ReactionId &id) {
if (controller->session().premium()
if (item->reactionsAreTags()) {
if (controller->session().premium()) {
return false;
}
ShowPremiumPreviewBox(controller, PremiumPreview::TagsForMessages);
return true;
} else if (controller->session().premium()
|| ranges::contains(item->chosenReactions(), id)
|| item->history()->peer->isBroadcast()) {
return false;
} else if (item->reactionsAreTags()) {
ShowPremiumPreviewBox(controller, PremiumPreview::TagsForMessages);
return true;
} else if (!id.custom()) {
return false;
}