Add emoji-status disclaimer for unknown peers.

This commit is contained in:
John Preston 2022-09-06 11:20:55 +04:00
parent 400d4b793a
commit f0955f2021
15 changed files with 223 additions and 66 deletions

View File

@ -1910,6 +1910,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_new_contact_unarchive" = "Unarchive";
"lng_new_contact_from_request_channel" = "{user} is an admin of {name}, a channel you requested to join.";
"lng_new_contact_from_request_group" = "{user} is an admin of {name}, a group you requested to join.";
"lng_new_contact_about_status" = "This account uses {emoji} as a custom status next to its\nname. Such emoji statuses are available to all\nsubscribers of {link}.";
"lng_new_contact_about_status_link" = "Telegram Premium";
"lng_from_request_title_channel" = "Chat with channel's admin";
"lng_from_request_title_group" = "Chat with group's admin";
"lng_from_request_body" = "You received this message because you requested to join {name} on {date}.";

View File

@ -247,12 +247,11 @@ void ChooseJoinAsBox(
: tr::lng_group_call_schedule)(makeLink),
Ui::Text::WithEntities),
labelSt));
label->setClickHandlerFilter([=](const auto&...) {
label->overrideLinkClickHandler([=] {
auto withJoinAs = info;
withJoinAs.joinAs = controller->selected();
box->getDelegate()->show(
Box(ScheduleGroupCallBox, withJoinAs, done));
return false;
});
}
auto next = (context == Context::Switch)

View File

@ -758,17 +758,26 @@ void CustomEmojiManager::scheduleRepaintTimer() {
void CustomEmojiManager::invokeRepaints() {
_repaintNext = 0;
const auto now = crl::now();
auto repaint = std::vector<base::weak_ptr<Ui::CustomEmoji::Instance>>();
for (auto i = begin(_repaints); i != end(_repaints);) {
if (i->second.when > now) {
++i;
continue;
}
auto bunch = std::move(i->second);
auto &list = i->second.instances;
if (repaint.empty()) {
repaint = std::move(list);
} else {
repaint.insert(
end(repaint),
std::make_move_iterator(begin(list)),
std::make_move_iterator(end(list)));
}
i = _repaints.erase(i);
for (const auto &weak : bunch.instances) {
if (const auto strong = weak.get()) {
strong->repaint();
}
}
for (const auto &weak : repaint) {
if (const auto strong = weak.get()) {
strong->repaint();
}
}
scheduleRepaintTimer();

View File

@ -2367,13 +2367,12 @@ void InnerWidget::refreshEmptyLabel() {
});
_empty.create(this, std::move(full), st::dialogsEmptyLabel);
resizeEmptyLabel();
_empty->setClickHandlerFilter([=](const auto &...) {
_empty->overrideLinkClickHandler([=] {
if (_emptyState == EmptyState::NoContacts) {
_controller->showAddContact();
} else if (_emptyState == EmptyState::EmptyFolder) {
editOpenedFilter();
}
return false;
});
_empty->setVisible(_state == WidgetState::Default);
}

View File

@ -300,9 +300,8 @@ void SettingsWidget::addLocationLabel(
Ui::Text::WithEntities),
st::exportLocationLabel),
st::exportLocationPadding);
label->setClickHandlerFilter([=](auto&&...) {
label->overrideLinkClickHandler([=] {
chooseFolder();
return false;
});
#endif // OS_MAC_STORE
}
@ -357,10 +356,7 @@ void SettingsWidget::addFormatAndLocationLabel(
Ui::Text::WithEntities),
st::exportLocationLabel),
st::exportLocationPadding);
label->setClickHandlerFilter([=](
const ClickHandlerPtr &handler,
Qt::MouseButton) {
const auto url = handler->dragText();
label->overrideLinkClickHandler([=](const QString &url) {
if (url == qstr("internal:edit_export_path")) {
chooseFolder();
} else if (url == qstr("internal:edit_format")) {
@ -368,7 +364,6 @@ void SettingsWidget::addFormatAndLocationLabel(
} else {
Unexpected("Click handler URL in export limits edit.");
}
return false;
});
#endif // OS_MAC_STORE
}
@ -413,10 +408,7 @@ void SettingsWidget::addLimitsLabel(
std::move(datesText),
st::exportLocationLabel),
st::exportLimitsPadding);
label->setClickHandlerFilter([=](
const ClickHandlerPtr &handler,
Qt::MouseButton) {
const auto url = handler->dragText();
label->overrideLinkClickHandler([=](const QString &url) {
if (url == qstr("internal:edit_from")) {
const auto done = [=](TimeId limit) {
changeData([&](Settings &settings) {
@ -444,7 +436,6 @@ void SettingsWidget::addLimitsLabel(
} else {
Unexpected("Click handler URL in export limits edit.");
}
return false;
});
}

View File

@ -11,18 +11,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/layers/generic_box.h"
#include "ui/toast/toast.h"
#include "ui/text/format_values.h" // Ui::FormatPhone
#include "ui/text/text_utilities.h"
#include "ui/boxes/confirm_box.h"
#include "ui/layers/generic_box.h"
#include "core/ui_integration.h"
#include "data/notify/data_notify_settings.h"
#include "data/data_peer.h"
#include "data/data_user.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_changes.h"
#include "data/data_document.h"
#include "data/data_session.h"
#include "data/stickers/data_custom_emoji.h"
#include "settings/settings_premium.h"
#include "window/window_peer_menu.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
@ -38,7 +44,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView {
namespace {
bool BarCurrentlyHidden(not_null<PeerData*> peer) {
class Listener final : public Data::CustomEmojiManager::Listener {
public:
explicit Listener(Fn<void(not_null<DocumentData*>)> check)
: _check(std::move(check)) {
}
void customEmojiResolveDone(
not_null<DocumentData*> document) override {
_check(document);
}
private:
Fn<void(not_null<DocumentData*>)> _check;
};
[[nodiscard]] bool BarCurrentlyHidden(not_null<PeerData*> peer) {
const auto settings = peer->settings();
if (!settings) {
return false;
@ -58,6 +80,66 @@ bool BarCurrentlyHidden(not_null<PeerData*> peer) {
return false;
}
[[nodiscard]] rpl::producer<TextWithEntities> ResolveIsCustom(
not_null<Data::Session*> owner,
DocumentId id) {
return [=](auto consumer) {
const auto document = owner->document(id);
const auto manager = &owner->customEmojiManager();
const auto check = [=](not_null<DocumentData*> document) {
if (const auto sticker = document->sticker()) {
const auto setId = manager->coloredSetId();
const auto text = (setId == sticker->set.id)
? QString()
: sticker->alt;
if (text.isEmpty()) {
consumer.put_next({});
} else {
consumer.put_next({
.text = text,
.entities = { EntityInText(
EntityType::CustomEmoji,
0,
text.size(),
Data::SerializeCustomEmojiId(document)) },
});
}
return true;
}
return false;
};
auto lifetime = rpl::lifetime();
if (!check(document)) {
const auto manager = &owner->customEmojiManager();
const auto listener = new Listener(check);
lifetime.add([=] {
manager->unregisterListener(listener);
delete listener;
});
manager->resolve(id, listener);
}
return lifetime;
};
}
[[nodiscard]] rpl::producer<TextWithEntities> PeerCustomStatus(
not_null<PeerData*> peer) {
const auto user = peer->asUser();
if (!user) {
return rpl::single(TextWithEntities());
}
const auto owner = &user->owner();
return user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::EmojiStatus
) | rpl::map([=] {
const auto id = user->emojiStatusId();
return id
? ResolveIsCustom(owner, id)
: rpl::single(TextWithEntities());
}) | rpl::flatten_latest() | rpl::distinct_until_changed();
}
} // namespace
class ContactStatus::BgButton final : public Ui::RippleButton {
@ -78,7 +160,10 @@ class ContactStatus::Bar final : public Ui::RpWidget {
public:
Bar(QWidget *parent, const QString &name);
void showState(State state);
void showState(
State state,
TextWithEntities status,
Fn<std::any(Fn<void()> customEmojiRepaint)> context);
[[nodiscard]] rpl::producer<> unarchiveClicks() const;
[[nodiscard]] rpl::producer<> addClicks() const;
@ -87,10 +172,13 @@ public:
[[nodiscard]] rpl::producer<> reportClicks() const;
[[nodiscard]] rpl::producer<> closeClicks() const;
[[nodiscard]] rpl::producer<> requestInfoClicks() const;
[[nodiscard]] rpl::producer<> emojiStatusClicks() const;
private:
int resizeGetHeight(int newWidth) override;
void emojiStatusRepaint();
QString _name;
object_ptr<Ui::FlatButton> _add;
object_ptr<Ui::FlatButton> _unarchive;
@ -100,6 +188,10 @@ private:
object_ptr<Ui::IconButton> _close;
object_ptr<BgButton> _requestChatBg;
object_ptr<Ui::FlatLabel> _requestChatInfo;
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>> _emojiStatusInfo;
object_ptr<Ui::PlainShadow> _emojiStatusShadow;
bool _emojiStatusRepaintScheduled = false;
rpl::event_stream<> _emojiStatusClicks;
};
@ -152,13 +244,30 @@ ContactStatus::Bar::Bar(
, _close(this, st::historyReplyCancel)
, _requestChatBg(this, st::historyContactStatusButton)
, _requestChatInfo(
this,
QString(),
st::historyContactStatusLabel) {
this,
QString(),
st::historyContactStatusLabel)
, _emojiStatusInfo(
this,
object_ptr<Ui::FlatLabel>(this, u""_q, st::historyEmojiStatusInfoLabel),
QMargins(
st::historyContactStatusMinSkip,
st::topBarArrowPadding.top(),
st::historyContactStatusMinSkip,
st::topBarArrowPadding.top()))
, _emojiStatusShadow(this) {
_requestChatInfo->setAttribute(Qt::WA_TransparentForMouseEvents);
_emojiStatusInfo->paintRequest(
) | rpl::start_with_next([=, raw = _emojiStatusInfo.data()](QRect clip) {
_emojiStatusRepaintScheduled = false;
QPainter(raw).fillRect(clip, st::historyComposeButtonBg);
}, lifetime());
}
void ContactStatus::Bar::showState(State state) {
void ContactStatus::Bar::showState(
State state,
TextWithEntities status,
Fn<std::any(Fn<void()> customEmojiRepaint)> context) {
using Type = State::Type;
const auto type = state.type;
_add->setVisible(type == Type::AddOrBlock || type == Type::Add);
@ -172,6 +281,25 @@ void ContactStatus::Bar::showState(State state) {
|| type == Type::UnarchiveOrReport);
_requestChatInfo->setVisible(type == Type::RequestChatInfo);
_requestChatBg->setVisible(type == Type::RequestChatInfo);
const auto has = !status.empty();
_emojiStatusShadow->setVisible(
has && (type == Type::AddOrBlock || type == Type::UnarchiveOrBlock));
if (has) {
_emojiStatusInfo->entity()->setMarkedText(
tr::lng_new_contact_about_status(
tr::now,
lt_emoji,
status,
lt_link,
Ui::Text::Link(
tr::lng_new_contact_about_status_link(tr::now)),
Ui::Text::WithEntities),
context([=] { emojiStatusRepaint(); }));
_emojiStatusInfo->entity()->overrideLinkClickHandler([=] {
_emojiStatusClicks.fire({});
});
}
_emojiStatusInfo->setVisible(has);
_add->setText((type == Type::Add)
? tr::lng_new_contact_add_name(tr::now, lt_user, _name).toUpper()
: tr::lng_new_contact_add(tr::now).toUpper());
@ -219,14 +347,19 @@ rpl::producer<> ContactStatus::Bar::requestInfoClicks() const {
return _requestChatBg->clicks() | rpl::to_empty;
}
rpl::producer<> ContactStatus::Bar::emojiStatusClicks() const {
return _emojiStatusClicks.events();
}
int ContactStatus::Bar::resizeGetHeight(int newWidth) {
_close->moveToRight(0, 0);
const auto closeWidth = _close->width();
const auto closeHeight = _close->height();
const auto available = newWidth - closeWidth;
const auto skip = st::historyContactStatusMinSkip;
if (available <= 2 * skip) {
return _close->height();
return closeHeight;
}
const auto buttonWidth = [&](const object_ptr<Ui::FlatButton> &button) {
return button->textWidth() + 2 * skip;
@ -237,7 +370,7 @@ int ContactStatus::Bar::resizeGetHeight(int newWidth) {
const object_ptr<Ui::FlatButton> &button,
int buttonWidth,
int rightTextMargin = 0) {
button->setGeometry(accumulatedLeft, 0, buttonWidth, height());
button->setGeometry(accumulatedLeft, 0, buttonWidth, closeHeight);
button->setTextMargins({ 0, 0, rightTextMargin, 0 });
accumulatedLeft += buttonWidth;
};
@ -287,7 +420,17 @@ int ContactStatus::Bar::resizeGetHeight(int newWidth) {
placeOne(_report);
}
if (_requestChatInfo->isHidden()) {
return _close->height();
_emojiStatusInfo->resizeToWidth(newWidth);
_emojiStatusInfo->move(0, _close->height());
_emojiStatusShadow->setGeometry(
0,
closeHeight,
newWidth,
st::lineWidth);
_emojiStatusShadow->move(0, _close->height());
return closeHeight + (_emojiStatusInfo->isHidden()
? 0
: _emojiStatusInfo->height());
}
const auto vskip = st::topBarArrowPadding.top();
_requestChatInfo->resizeToWidth(available - 2 * skip);
@ -297,6 +440,14 @@ int ContactStatus::Bar::resizeGetHeight(int newWidth) {
return newHeight;
}
void ContactStatus::Bar::emojiStatusRepaint() {
if (_emojiStatusRepaintScheduled) {
return;
}
_emojiStatusRepaintScheduled = true;
_emojiStatusInfo->entity()->update();
}
ContactStatus::ContactStatus(
not_null<Window::SessionController*> window,
not_null<Ui::RpWidget*> parent,
@ -393,15 +544,23 @@ void ContactStatus::setupState(not_null<PeerData*> peer) {
peer->session().api().requestPeerSettings(peer);
}
_bar.entity()->showState(State());
PeerState(
peer
) | rpl::start_with_next([=](State state) {
_context = [=](Fn<void()> customEmojiRepaint) {
return Core::MarkedTextContext{
.session = &peer->session(),
.customEmojiRepaint = customEmojiRepaint,
};
};
_bar.entity()->showState({}, {}, _context);
rpl::combine(
PeerState(peer),
PeerCustomStatus(peer)
) | rpl::start_with_next([=](State state, TextWithEntities status) {
_state = state;
_status = status;
if (state.type == State::Type::None) {
_bar.hide(anim::type::normal);
} else {
_bar.entity()->showState(state);
_bar.entity()->showState(state, std::move(status), _context);
_bar.show(anim::type::normal);
}
}, _bar.lifetime());
@ -417,6 +576,7 @@ void ContactStatus::setupHandlers(not_null<PeerData*> peer) {
setupReportHandler(peer);
setupCloseHandler(peer);
setupRequestInfoHandler(peer);
setupEmojiStatusHandler(peer);
}
void ContactStatus::setupAddHandler(not_null<UserData*> user) {
@ -583,12 +743,19 @@ void ContactStatus::setupRequestInfoHandler(not_null<PeerData*> peer) {
}, _bar.lifetime());
}
void ContactStatus::setupEmojiStatusHandler(not_null<PeerData*> peer) {
_bar.entity()->emojiStatusClicks(
) | rpl::start_with_next([=] {
Settings::ShowEmojiStatusPremium(_controller, peer);
}, _bar.lifetime());
}
void ContactStatus::show() {
const auto visible = (_state.type != State::Type::None);
if (!_shown) {
_shown = true;
if (visible) {
_bar.entity()->showState(_state);
_bar.entity()->showState(_state, _status, _context);
}
}
_bar.toggle(visible, anim::type::instant);

View File

@ -72,11 +72,14 @@ private:
void setupReportHandler(not_null<PeerData*> peer);
void setupCloseHandler(not_null<PeerData*> peer);
void setupRequestInfoHandler(not_null<PeerData*> peer);
void setupEmojiStatusHandler(not_null<PeerData*> peer);
static rpl::producer<State> PeerState(not_null<PeerData*> peer);
const not_null<Window::SessionController*> _controller;
State _state;
TextWithEntities _status;
Fn<std::any(Fn<void()> customEmojiRepaint)> _context;
Ui::SlideWrap<Bar> _bar;
Ui::PlainShadow _shadow;
bool _shown = false;

View File

@ -354,7 +354,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
std::move(linkText),
QString());
const auto controller = _controller->parentController();
link->setClickHandlerFilter([=, peer = _peer](auto&&...) {
link->overrideLinkClickHandler([=, peer = _peer] {
const auto link = peer->session().createInternalLinkFull(
peer->userName());
if (!link.isEmpty()) {
@ -363,7 +363,6 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
Window::Show(controller).toastParent(),
tr::lng_username_copied(tr::now));
}
return false;
});
if (const auto channel = _peer->asChannel()) {

View File

@ -460,13 +460,8 @@ void Widget::showTerms() {
Ui::Text::WithEntities),
st::introTermsLabel);
_terms.create(this, std::move(entity));
_terms->entity()->setClickHandlerFilter([=](
const ClickHandlerPtr &handler,
Qt::MouseButton button) {
if (button == Qt::LeftButton) {
showTerms(nullptr);
}
return false;
_terms->entity()->overrideLinkClickHandler([=] {
showTerms(nullptr);
});
updateControlsGeometry();
_terms->hide(anim::type::instant);

View File

@ -154,10 +154,7 @@ void VerifyBox::setupControls(
) | rpl::start_with_next([=] {
_content->resizeToWidth(st::boxWidth);
}, _content->lifetime());
label->setClickHandlerFilter([=](auto&&...) {
resend();
return false;
});
label->overrideLinkClickHandler(resend);
}
std::move(
error

View File

@ -359,10 +359,9 @@ void FormSummary::setupPrices(not_null<VerticalLayout*> layout) {
const auto text = formatAmount(_invoice.tipsSelected);
const auto label = addRow(
tr::lng_payments_tips_label(tr::now),
Ui::Text::Link(text, "internal:edit_tips"));
label->setClickHandlerFilter([=](auto&&...) {
Ui::Text::Link(text));
label->overrideLinkClickHandler([=] {
_delegate->panelChooseTips();
return false;
});
setupSuggestedTips(layout);
}

View File

@ -192,7 +192,7 @@ void Cover::initViewers() {
refreshUsernameGeometry(width());
}, lifetime());
_username->setClickHandlerFilter([=](auto&&...) {
_username->overrideLinkClickHandler([=] {
const auto username = _user->userName();
if (username.isEmpty()) {
_controller->show(Box<UsernameBox>(&_user->session()));
@ -203,7 +203,6 @@ void Cover::initViewers() {
Window::Show(_controller).toastParent(),
tr::lng_username_copied(tr::now));
}
return false;
});
}

View File

@ -498,17 +498,11 @@ auto PhoneNumberPrivacyController::warning() const
void PhoneNumberPrivacyController::prepareWarningLabel(
not_null<Ui::FlatLabel*> warning) const {
warning->setClickHandlerFilter([=](
const ClickHandlerPtr &link,
Qt::MouseButton button) {
if (button == Qt::LeftButton) {
QGuiApplication::clipboard()->setText(PublicLinkByPhone(
_controller->session().user()));
_controller->window().showToast(
tr::lng_username_copied(tr::now));
return false;
}
return true;
warning->overrideLinkClickHandler([=] {
QGuiApplication::clipboard()->setText(PublicLinkByPhone(
_controller->session().user()));
_controller->window().showToast(
tr::lng_username_copied(tr::now));
});
}

View File

@ -305,6 +305,10 @@ historyContactStatusBlock: FlatButton(historyContactStatusButton) {
historyContactStatusLabel: FlatLabel(defaultFlatLabel) {
minWidth: 240px;
}
historyEmojiStatusInfoLabel: FlatLabel(historyContactStatusLabel) {
align: align(top);
textFg: windowSubTextFg;
}
historyContactStatusMinSkip: 16px;
historySendIcon: icon {{ "chat/input_send", historySendIconFg }};

@ -1 +1 @@
Subproject commit 2e63c6103e3b23bfcd65dcb8afb19c020511b168
Subproject commit 4ec399f169e9308cd5da194b6fa2104578c39e45