mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 22:54:01 +02:00
Implement required paywalls in tags.
This commit is contained in:
parent
46579ac84d
commit
11cf0486cb
16 changed files with 242 additions and 60 deletions
BIN
Telegram/Resources/icons/dialogs/tags_arrow.png
Normal file
BIN
Telegram/Resources/icons/dialogs/tags_arrow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/dialogs/tags_arrow@2x.png
Normal file
BIN
Telegram/Resources/icons/dialogs/tags_arrow@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 196 B |
BIN
Telegram/Resources/icons/dialogs/tags_arrow@3x.png
Normal file
BIN
Telegram/Resources/icons/dialogs/tags_arrow@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 244 B |
|
@ -2814,8 +2814,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_context_read_hidden" = "read";
|
"lng_context_read_hidden" = "read";
|
||||||
"lng_context_read_show" = "show when";
|
"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_about" = "You can label your emoji tag with a text name.";
|
||||||
"lng_edit_tag_name" = "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" = "This message contains emoji from **{name} pack**.";
|
||||||
"lng_context_animated_emoji_many#one" = "This message contains emoji from **{count} 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#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_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_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_menu_item" = "Stealth Mode";
|
||||||
"lng_stealth_mode_title" = "Stealth Mode";
|
"lng_stealth_mode_title" = "Stealth Mode";
|
||||||
|
|
|
@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/boxes/confirm_box.h"
|
#include "ui/boxes/confirm_box.h"
|
||||||
#include "boxes/share_box.h"
|
#include "boxes/share_box.h"
|
||||||
#include "boxes/connection_box.h"
|
#include "boxes/connection_box.h"
|
||||||
|
#include "boxes/premium_preview_box.h"
|
||||||
#include "boxes/sticker_set_box.h"
|
#include "boxes/sticker_set_box.h"
|
||||||
#include "boxes/sessions_box.h"
|
#include "boxes/sessions_box.h"
|
||||||
#include "boxes/language_box.h"
|
#include "boxes/language_box.h"
|
||||||
|
@ -657,6 +658,17 @@ bool CopyPeerId(
|
||||||
return true;
|
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(
|
void ExportTestChatTheme(
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
not_null<const Data::CloudTheme*> theme) {
|
not_null<const Data::CloudTheme*> theme) {
|
||||||
|
@ -1020,6 +1032,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
|
||||||
{
|
{
|
||||||
u"^copy:(.+)$"_q,
|
u"^copy:(.+)$"_q,
|
||||||
CopyPeerId
|
CopyPeerId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
u"about_tags"_q,
|
||||||
|
ShowSearchTagsPromo
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return Result;
|
return Result;
|
||||||
|
|
|
@ -626,3 +626,7 @@ searchedBarPosition: point(17px, 7px);
|
||||||
|
|
||||||
dialogsSearchTagSkip: point(8px, 4px);
|
dialogsSearchTagSkip: point(8px, 4px);
|
||||||
dialogsSearchTagBottom: 10px;
|
dialogsSearchTagBottom: 10px;
|
||||||
|
dialogsSearchTagLocked: icon{{ "emoji/premium_lock", lightButtonFgOver }};
|
||||||
|
dialogsSearchTagPromo: defaultTextStyle;
|
||||||
|
dialogsSearchTagArrow: icon{{ "dialogs/tags_arrow", windowSubTextFg }};
|
||||||
|
dialogsSearchTagArrowPadding: margins(0px, 3px, 0px, 0px);
|
||||||
|
|
|
@ -18,8 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "dialogs/dialogs_search_tags.h"
|
#include "dialogs/dialogs_search_tags.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "core/shortcuts.h"
|
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
|
#include "core/click_handler_types.h"
|
||||||
|
#include "core/shortcuts.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/widgets/scroll_area.h"
|
#include "ui/widgets/scroll_area.h"
|
||||||
|
@ -1783,7 +1784,11 @@ void InnerWidget::mousePressReleased(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (auto activated = ClickHandler::unpressed()) {
|
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);
|
update(0, searchInChatOffset(), width(), height);
|
||||||
}, _searchTags->lifetime());
|
}, _searchTags->lifetime());
|
||||||
|
|
||||||
_searchTags->heightValue() | rpl::filter(
|
_searchTags->heightValue() | rpl::skip(
|
||||||
rpl::mappers::_1 > 0
|
1
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
refresh();
|
refresh();
|
||||||
moveCancelSearchButtons();
|
moveCancelSearchButtons();
|
||||||
|
|
|
@ -8,13 +8,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "dialogs/dialogs_search_tags.h"
|
#include "dialogs/dialogs_search_tags.h"
|
||||||
|
|
||||||
#include "base/qt/qt_key_modifiers.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/stickers/data_custom_emoji.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_message_reactions.h"
|
#include "data/data_message_reactions.h"
|
||||||
|
#include "data/data_peer_values.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "history/view/reactions/history_view_reactions.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/effects/animation_value.h"
|
||||||
|
#include "ui/text/text_utilities.h"
|
||||||
|
#include "ui/painter.h"
|
||||||
#include "ui/power_saving.h"
|
#include "ui/power_saving.h"
|
||||||
|
#include "window/window_session_controller.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
#include "styles/style_dialogs.h"
|
#include "styles/style_dialogs.h"
|
||||||
|
|
||||||
|
@ -32,6 +41,45 @@ namespace {
|
||||||
return TextUtilities::SingleLine(result);
|
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
|
} // namespace
|
||||||
|
|
||||||
struct SearchTags::Tag {
|
struct SearchTags::Tag {
|
||||||
|
@ -43,6 +91,7 @@ struct SearchTags::Tag {
|
||||||
QRect geometry;
|
QRect geometry;
|
||||||
ClickHandlerPtr link;
|
ClickHandlerPtr link;
|
||||||
bool selected = false;
|
bool selected = false;
|
||||||
|
bool promo = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
SearchTags::SearchTags(
|
SearchTags::SearchTags(
|
||||||
|
@ -51,10 +100,13 @@ SearchTags::SearchTags(
|
||||||
std::vector<Data::ReactionId> selected)
|
std::vector<Data::ReactionId> selected)
|
||||||
: _owner(owner)
|
: _owner(owner)
|
||||||
, _added(selected) {
|
, _added(selected) {
|
||||||
std::move(
|
rpl::combine(
|
||||||
tags
|
std::move(tags),
|
||||||
) | rpl::start_with_next([=](const std::vector<Data::Reaction> &list) {
|
Data::AmPremiumValue(&owner->session())
|
||||||
fill(list);
|
) | rpl::start_with_next([=](
|
||||||
|
const std::vector<Data::Reaction> &list,
|
||||||
|
bool premium) {
|
||||||
|
fill(list, premium);
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
|
|
||||||
// Mark the `selected` reactions as selected in `_tags`.
|
// Mark the `selected` reactions as selected in `_tags`.
|
||||||
|
@ -73,12 +125,19 @@ SearchTags::SearchTags(
|
||||||
|
|
||||||
SearchTags::~SearchTags() = default;
|
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();
|
const auto selected = collectSelected();
|
||||||
_tags.clear();
|
_tags.clear();
|
||||||
_tags.reserve(list.size());
|
_tags.reserve(list.size());
|
||||||
const auto link = [&](Data::ReactionId id) {
|
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);
|
const auto i = ranges::find(_tags, id, &Tag::id);
|
||||||
if (i != end(_tags)) {
|
if (i != end(_tags)) {
|
||||||
if (!i->selected && !base::IsShiftPressed()) {
|
if (!i->selected && !base::IsShiftPressed()) {
|
||||||
|
@ -109,6 +168,18 @@ void SearchTags::fill(const std::vector<Data::Reaction> &list) {
|
||||||
_owner->reactions().preloadImageFor(id);
|
_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) {
|
for (const auto &reaction : list) {
|
||||||
if (reaction.count > 0
|
if (reaction.count > 0
|
||||||
|| ranges::contains(_added, reaction.id)
|
|| ranges::contains(_added, reaction.id)
|
||||||
|
@ -131,10 +202,11 @@ void SearchTags::layout() {
|
||||||
Expects(_width > 0);
|
Expects(_width > 0);
|
||||||
|
|
||||||
if (_tags.empty()) {
|
if (_tags.empty()) {
|
||||||
|
_additionalText = {};
|
||||||
_height = 0;
|
_height = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto &bg = validateBg(false);
|
const auto &bg = validateBg(false, false);
|
||||||
const auto skip = st::dialogsSearchTagSkip;
|
const auto skip = st::dialogsSearchTagSkip;
|
||||||
const auto size = bg.size() / bg.devicePixelRatio();
|
const auto size = bg.size() / bg.devicePixelRatio();
|
||||||
const auto xbase = size.width();
|
const auto xbase = size.width();
|
||||||
|
@ -147,10 +219,17 @@ void SearchTags::layout() {
|
||||||
x = 0;
|
x = 0;
|
||||||
y += ybase + skip.y();
|
y += ybase + skip.y();
|
||||||
}
|
}
|
||||||
tag.geometry = QRect(x, y, width, xbase);
|
tag.geometry = QRect(x, y, width, ybase);
|
||||||
x += width + skip.x();
|
x += width + skip.x();
|
||||||
}
|
}
|
||||||
_height = y + ybase + st::dialogsSearchTagBottom;
|
_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) {
|
void SearchTags::resizeToWidth(int width) {
|
||||||
|
@ -177,6 +256,14 @@ ClickHandlerPtr SearchTags::lookupHandler(QPoint point) const {
|
||||||
for (const auto &tag : _tags) {
|
for (const auto &tag : _tags) {
|
||||||
if (tag.geometry.contains(point.x(), point.y())) {
|
if (tag.geometry.contains(point.x(), point.y())) {
|
||||||
return tag.link;
|
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;
|
return nullptr;
|
||||||
|
@ -227,7 +314,7 @@ void SearchTags::paintCustomFrame(
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchTags::paint(
|
void SearchTags::paint(
|
||||||
QPainter &p,
|
Painter &p,
|
||||||
QPoint position,
|
QPoint position,
|
||||||
crl::time now,
|
crl::time now,
|
||||||
bool paused) const {
|
bool paused) const {
|
||||||
|
@ -236,9 +323,9 @@ void SearchTags::paint(
|
||||||
const auto padding = st::reactionInlinePadding;
|
const auto padding = st::reactionInlinePadding;
|
||||||
for (const auto &tag : _tags) {
|
for (const auto &tag : _tags) {
|
||||||
const auto geometry = tag.geometry.translated(position);
|
const auto geometry = tag.geometry.translated(position);
|
||||||
paintBackground(p, geometry, tag.selected);
|
paintBackground(p, geometry, tag);
|
||||||
paintText(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.image = _owner->reactions().resolveImageFor(
|
||||||
tag.id,
|
tag.id,
|
||||||
::Data::Reactions::ImageSize::InlineList);
|
::Data::Reactions::ImageSize::InlineList);
|
||||||
|
@ -247,7 +334,9 @@ void SearchTags::paint(
|
||||||
const auto image = QRect(
|
const auto image = QRect(
|
||||||
inner.topLeft() + QPoint(skip, skip),
|
inner.topLeft() + QPoint(skip, skip),
|
||||||
QSize(st::reactionInlineImage, st::reactionInlineImage));
|
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
|
const auto textFg = tag.selected
|
||||||
? st::dialogsNameFgActive->c
|
? st::dialogsNameFgActive->c
|
||||||
: st::dialogsNameFgOver->c;
|
: st::dialogsNameFgOver->c;
|
||||||
|
@ -262,13 +351,26 @@ void SearchTags::paint(
|
||||||
p.drawImage(image.topLeft(), tag.image);
|
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(
|
void SearchTags::paintBackground(
|
||||||
QPainter &p,
|
QPainter &p,
|
||||||
QRect geometry,
|
QRect geometry,
|
||||||
bool selected) const {
|
const Tag &tag) const {
|
||||||
const auto &image = validateBg(selected);
|
const auto &image = validateBg(tag.selected, tag.promo);
|
||||||
const auto ratio = int(image.devicePixelRatio());
|
const auto ratio = int(image.devicePixelRatio());
|
||||||
const auto size = image.size() / ratio;
|
const auto size = image.size() / ratio;
|
||||||
if (const auto fill = geometry.width() - size.width(); fill > 0) {
|
if (const auto fill = geometry.width() - size.width(); fill > 0) {
|
||||||
|
@ -282,7 +384,7 @@ void SearchTags::paintBackground(
|
||||||
QRect(QPoint(), QSize(left, size.height()) * ratio));
|
QRect(QPoint(), QSize(left, size.height()) * ratio));
|
||||||
p.fillRect(
|
p.fillRect(
|
||||||
QRect(x + left, y, fill, size.height()),
|
QRect(x + left, y, fill, size.height()),
|
||||||
bgColor(selected));
|
bgColor(tag.selected, tag.promo));
|
||||||
p.drawImage(
|
p.drawImage(
|
||||||
QRect(x + left + fill, y, right, size.height()),
|
QRect(x + left + fill, y, right, size.height()),
|
||||||
image,
|
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;
|
using namespace HistoryView::Reactions;
|
||||||
|
|
||||||
if (tag.text.isEmpty()) {
|
if (tag.text.isEmpty()) {
|
||||||
return;
|
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);
|
p.setFont(st::reactionInlineTagFont);
|
||||||
const auto x = geometry.x() + st::reactionInlineTagNamePosition.x();
|
const auto x = geometry.x() + st::reactionInlineTagNamePosition.x();
|
||||||
const auto y = geometry.y() + st::reactionInlineTagNamePosition.y();
|
const auto y = geometry.y() + st::reactionInlineTagNamePosition.y();
|
||||||
p.drawText(x, y + st::reactionInlineTagFont->ascent, tag.text);
|
p.drawText(x, y + st::reactionInlineTagFont->ascent, tag.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
QColor SearchTags::bgColor(bool selected) const {
|
QColor SearchTags::bgColor(bool selected, bool promo) const {
|
||||||
return selected
|
return promo
|
||||||
|
? st::lightButtonBgOver->c
|
||||||
|
: selected
|
||||||
? st::dialogsBgActive->c
|
? st::dialogsBgActive->c
|
||||||
: st::dialogsBgOver->c;
|
: st::dialogsBgOver->c;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QImage &SearchTags::validateBg(bool selected) const {
|
const QImage &SearchTags::validateBg(bool selected, bool promo) const {
|
||||||
using namespace HistoryView::Reactions;
|
using namespace HistoryView::Reactions;
|
||||||
auto &image = selected ? _selectedBg : _normalBg;
|
auto &image = promo ? _promoBg : selected ? _selectedBg : _normalBg;
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
const auto tagBg = bgColor(selected);
|
const auto tagBg = bgColor(selected, promo);
|
||||||
const auto dotBg = st::transparent->c;
|
const auto dotBg = st::transparent->c;
|
||||||
image = InlineList::PrepareTagBg(tagBg, dotBg);
|
image = InlineList::PrepareTagBg(tagBg, dotBg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "base/weak_ptr.h"
|
#include "base/weak_ptr.h"
|
||||||
|
|
||||||
|
class Painter;
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
class Session;
|
class Session;
|
||||||
struct Reaction;
|
struct Reaction;
|
||||||
|
@ -39,7 +41,7 @@ public:
|
||||||
-> rpl::producer<std::vector<Data::ReactionId>>;
|
-> rpl::producer<std::vector<Data::ReactionId>>;
|
||||||
|
|
||||||
void paint(
|
void paint(
|
||||||
QPainter &p,
|
Painter &p,
|
||||||
QPoint position,
|
QPoint position,
|
||||||
crl::time now,
|
crl::time now,
|
||||||
bool paused) const;
|
bool paused) const;
|
||||||
|
@ -49,7 +51,7 @@ public:
|
||||||
private:
|
private:
|
||||||
struct Tag;
|
struct Tag;
|
||||||
|
|
||||||
void fill(const std::vector<Data::Reaction> &list);
|
void fill(const std::vector<Data::Reaction> &list, bool premium);
|
||||||
void paintCustomFrame(
|
void paintCustomFrame(
|
||||||
QPainter &p,
|
QPainter &p,
|
||||||
not_null<Ui::Text::CustomEmoji*> emoji,
|
not_null<Ui::Text::CustomEmoji*> emoji,
|
||||||
|
@ -59,25 +61,26 @@ private:
|
||||||
const QColor &textColor) const;
|
const QColor &textColor) const;
|
||||||
void layout();
|
void layout();
|
||||||
[[nodiscard]] std::vector<Data::ReactionId> collectSelected() const;
|
[[nodiscard]] std::vector<Data::ReactionId> collectSelected() const;
|
||||||
[[nodiscard]] QColor bgColor(bool selected) const;
|
[[nodiscard]] QColor bgColor(bool selected, bool promo) const;
|
||||||
[[nodiscard]] const QImage &validateBg(bool selected) const;
|
[[nodiscard]] const QImage &validateBg(bool selected, bool promo) const;
|
||||||
void paintBackground(
|
void paintAdditionalText(Painter &p, QPoint position) const;
|
||||||
QPainter &p,
|
void paintBackground(QPainter &p, QRect geometry, const Tag &tag) const;
|
||||||
QRect geometry,
|
|
||||||
bool selected) const;
|
|
||||||
void paintText(QPainter &p, QRect geometry, const Tag &tag) const;
|
void paintText(QPainter &p, QRect geometry, const Tag &tag) const;
|
||||||
|
|
||||||
const not_null<Data::Session*> _owner;
|
const not_null<Data::Session*> _owner;
|
||||||
std::vector<Data::ReactionId> _added;
|
std::vector<Data::ReactionId> _added;
|
||||||
std::vector<Tag> _tags;
|
std::vector<Tag> _tags;
|
||||||
|
Ui::Text::String _additionalText;
|
||||||
rpl::event_stream<> _selectedChanges;
|
rpl::event_stream<> _selectedChanges;
|
||||||
rpl::event_stream<> _repaintRequests;
|
rpl::event_stream<> _repaintRequests;
|
||||||
mutable QImage _normalBg;
|
mutable QImage _normalBg;
|
||||||
mutable QImage _selectedBg;
|
mutable QImage _selectedBg;
|
||||||
|
mutable QImage _promoBg;
|
||||||
mutable QImage _customCache;
|
mutable QImage _customCache;
|
||||||
mutable int _customSkip = 0;
|
mutable int _customSkip = 0;
|
||||||
rpl::variable<int> _height;
|
rpl::variable<int> _height;
|
||||||
int _width = 0;
|
int _width = 0;
|
||||||
|
int _additionalLeft = 0;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
|
|
@ -2636,9 +2636,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
desiredPosition,
|
desiredPosition,
|
||||||
reactItem,
|
reactItem,
|
||||||
[=](ChosenReaction reaction) { reactionChosen(reaction); },
|
[=](ChosenReaction reaction) { reactionChosen(reaction); },
|
||||||
(reactItem->reactionsAreTags()
|
ItemReactionsAbout(reactItem),
|
||||||
? TextWithEntities{ u"Organize your Saved Messages with tags. Learn More..."_q }
|
|
||||||
: TextWithEntities()),
|
|
||||||
_controller->cachedReactionIconFactory().createMethod())
|
_controller->cachedReactionIconFactory().createMethod())
|
||||||
: AttachSelectorResult::Skipped;
|
: AttachSelectorResult::Skipped;
|
||||||
if (attached == AttachSelectorResult::Failed) {
|
if (attached == AttachSelectorResult::Failed) {
|
||||||
|
|
|
@ -2633,7 +2633,7 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
desiredPosition,
|
desiredPosition,
|
||||||
reactItem,
|
reactItem,
|
||||||
[=](ChosenReaction reaction) { reactionChosen(reaction); },
|
[=](ChosenReaction reaction) { reactionChosen(reaction); },
|
||||||
TextWithEntities(),
|
ItemReactionsAbout(reactItem),
|
||||||
_controller->cachedReactionIconFactory().createMethod())
|
_controller->cachedReactionIconFactory().createMethod())
|
||||||
: AttachSelectorResult::Skipped;
|
: AttachSelectorResult::Skipped;
|
||||||
if (attached == AttachSelectorResult::Failed) {
|
if (attached == AttachSelectorResult::Failed) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/history_view_reply.h"
|
#include "history/view/history_view_reply.h"
|
||||||
#include "history/view/history_view_view_button.h" // ViewButton.
|
#include "history/view/history_view_view_button.h" // ViewButton.
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
|
#include "boxes/premium_preview_box.h"
|
||||||
#include "boxes/share_box.h"
|
#include "boxes/share_box.h"
|
||||||
#include "ui/effects/glare.h"
|
#include "ui/effects/glare.h"
|
||||||
#include "ui/effects/reaction_fly_animation.h"
|
#include "ui/effects/reaction_fly_animation.h"
|
||||||
|
@ -49,13 +50,13 @@ namespace {
|
||||||
constexpr auto kPlayStatusLimit = 2;
|
constexpr auto kPlayStatusLimit = 2;
|
||||||
const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_";
|
const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_";
|
||||||
|
|
||||||
[[nodiscard]] std::optional<Window::SessionController*> ExtractController(
|
[[nodiscard]] Window::SessionController *ExtractController(
|
||||||
const ClickContext &context) {
|
const ClickContext &context) {
|
||||||
const auto my = context.other.value<ClickHandlerContext>();
|
const auto my = context.other.value<ClickHandlerContext>();
|
||||||
if (const auto controller = my.sessionWindow.get()) {
|
if (const auto controller = my.sessionWindow.get()) {
|
||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
class KeyboardStyle : public ReplyKeyboard::Style {
|
class KeyboardStyle : public ReplyKeyboard::Style {
|
||||||
|
@ -2288,11 +2289,8 @@ ClickHandlerPtr Message::createGoToCommentsLink() const {
|
||||||
const auto fullId = data()->fullId();
|
const auto fullId = data()->fullId();
|
||||||
const auto sessionId = data()->history()->session().uniqueId();
|
const auto sessionId = data()->history()->session().uniqueId();
|
||||||
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
|
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
|
||||||
const auto controller = ExtractController(context).value_or(nullptr);
|
const auto controller = ExtractController(context);
|
||||||
if (!controller) {
|
if (!controller || controller->session().uniqueId() != sessionId) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (controller->session().uniqueId() != sessionId) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (const auto item = controller->session().data().message(fullId)) {
|
if (const auto item = controller->session().data().message(fullId)) {
|
||||||
|
@ -2984,12 +2982,20 @@ void Message::refreshReactions() {
|
||||||
return std::make_shared<LambdaClickHandler>([=](
|
return std::make_shared<LambdaClickHandler>([=](
|
||||||
ClickContext context) {
|
ClickContext context) {
|
||||||
if (const auto strong = weak.get()) {
|
if (const auto strong = weak.get()) {
|
||||||
if (strong->data()->reactionsAreTags()) {
|
const auto item = strong->data();
|
||||||
const auto tag = Data::SearchTagToQuery(id);
|
if (item->reactionsAreTags()) {
|
||||||
HashtagClickHandler(tag).onClick(context);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
strong->data()->toggleReaction(
|
item->toggleReaction(
|
||||||
id,
|
id,
|
||||||
HistoryItem::ReactionSource::Existing);
|
HistoryItem::ReactionSource::Existing);
|
||||||
if (const auto now = weak.get()) {
|
if (const auto now = weak.get()) {
|
||||||
|
@ -3586,11 +3592,8 @@ ClickHandlerPtr Message::prepareRightActionLink() const {
|
||||||
};
|
};
|
||||||
return std::make_shared<LambdaClickHandler>([=](
|
return std::make_shared<LambdaClickHandler>([=](
|
||||||
ClickContext context) {
|
ClickContext context) {
|
||||||
const auto controller = ExtractController(context).value_or(nullptr);
|
const auto controller = ExtractController(context);
|
||||||
if (!controller) {
|
if (!controller || controller->session().uniqueId() != sessionId) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (controller->session().uniqueId() != sessionId) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,13 +12,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/widgets/shadow.h"
|
#include "ui/widgets/shadow.h"
|
||||||
#include "ui/text/text_custom_emoji.h"
|
#include "ui/text/text_custom_emoji.h"
|
||||||
|
#include "ui/text/text_utilities.h"
|
||||||
#include "ui/platform/ui_platform_utility.h"
|
#include "ui/platform/ui_platform_utility.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_document_media.h"
|
#include "data/data_document_media.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/stickers/data_custom_emoji.h"
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "chat_helpers/emoji_list_widget.h"
|
#include "chat_helpers/emoji_list_widget.h"
|
||||||
#include "chat_helpers/stickers_list_footer.h"
|
#include "chat_helpers/stickers_list_footer.h"
|
||||||
|
@ -277,6 +280,13 @@ Selector::Selector(
|
||||||
, _skipy((st::reactStripHeight - st::reactStripSize) / 2) {
|
, _skipy((st::reactStripHeight - st::reactStripSize) / 2) {
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
|
|
||||||
|
if (_about) {
|
||||||
|
_about->setClickHandlerFilter([=](const auto &...) {
|
||||||
|
_escapes.fire({});
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_useTransparency = child || Ui::Platform::TranslucentWindowsSupported();
|
_useTransparency = child || Ui::Platform::TranslucentWindowsSupported();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1173,6 +1183,10 @@ AttachSelectorResult AttachSelectorToMenu(
|
||||||
chosen(std::move(reaction));
|
chosen(std::move(reaction));
|
||||||
}, selector->lifetime());
|
}, selector->lifetime());
|
||||||
|
|
||||||
|
selector->escapes() | rpl::start_with_next([=] {
|
||||||
|
menu->hideMenu();
|
||||||
|
}, selector->lifetime());
|
||||||
|
|
||||||
const auto weak = base::make_weak(controller);
|
const auto weak = base::make_weak(controller);
|
||||||
controller->enableGifPauseReason(
|
controller->enableGifPauseReason(
|
||||||
Window::GifPauseReason::MediaPreview);
|
Window::GifPauseReason::MediaPreview);
|
||||||
|
@ -1249,4 +1263,18 @@ auto AttachSelectorToMenu(
|
||||||
return selector;
|
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
|
} // namespace HistoryView::Reactions
|
||||||
|
|
|
@ -265,4 +265,7 @@ AttachSelectorResult AttachSelectorToMenu(
|
||||||
IconFactory iconFactory
|
IconFactory iconFactory
|
||||||
) -> base::expected<not_null<Selector*>, AttachSelectorResult>;
|
) -> base::expected<not_null<Selector*>, AttachSelectorResult>;
|
||||||
|
|
||||||
|
[[nodiscard]] TextWithEntities ItemReactionsAbout(
|
||||||
|
not_null<HistoryItem*> item);
|
||||||
|
|
||||||
} // namespace HistoryView::Reactions
|
} // namespace HistoryView::Reactions
|
||||||
|
|
|
@ -27,7 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "media/stories/media_stories_controller.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/chat/chat_style.h"
|
||||||
#include "ui/effects/emoji_fly_animation.h"
|
#include "ui/effects/emoji_fly_animation.h"
|
||||||
#include "ui/effects/path_shift_gradient.h"
|
#include "ui/effects/path_shift_gradient.h"
|
||||||
|
@ -676,7 +676,7 @@ void Reactions::Panel::create() {
|
||||||
_controller->uiShow(),
|
_controller->uiShow(),
|
||||||
std::move(reactions),
|
std::move(reactions),
|
||||||
TextWithEntities{ (mode == Mode::Message
|
TextWithEntities{ (mode == Mode::Message
|
||||||
? u"Send reaction as a private message"_q
|
? tr::lng_stories_reaction_as_message(tr::now)
|
||||||
: QString()) },
|
: QString()) },
|
||||||
_controller->cachedReactionIconFactory().createMethod(),
|
_controller->cachedReactionIconFactory().createMethod(),
|
||||||
[=](bool fast) { hide(mode); });
|
[=](bool fast) { hide(mode); });
|
||||||
|
|
|
@ -525,13 +525,16 @@ bool ShowReactPremiumError(
|
||||||
not_null<SessionController*> controller,
|
not_null<SessionController*> controller,
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
const Data::ReactionId &id) {
|
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)
|
|| ranges::contains(item->chosenReactions(), id)
|
||||||
|| item->history()->peer->isBroadcast()) {
|
|| item->history()->peer->isBroadcast()) {
|
||||||
return false;
|
return false;
|
||||||
} else if (item->reactionsAreTags()) {
|
|
||||||
ShowPremiumPreviewBox(controller, PremiumPreview::TagsForMessages);
|
|
||||||
return true;
|
|
||||||
} else if (!id.custom()) {
|
} else if (!id.custom()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue