Add auto-delete control to compose controls.

This commit is contained in:
John Preston 2021-02-11 11:25:43 +04:00
parent 107d4aea36
commit f97064ed37
17 changed files with 293 additions and 87 deletions

View file

@ -497,6 +497,8 @@ PRIVATE
history/view/controls/compose_controls_common.h
history/view/controls/history_view_compose_controls.cpp
history/view/controls/history_view_compose_controls.h
history/view/controls/history_view_ttl_button.cpp
history/view/controls/history_view_ttl_button.h
history/view/controls/history_view_voice_record_bar.cpp
history/view/controls/history_view_voice_record_bar.h
history/view/controls/history_view_voice_record_button.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -982,6 +982,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_messages_ttl_after2" = "After 7 days";
"lng_manage_messages_ttl_about" = "Turning on this setting will make auto-delete messages from this group after the selected period.";
"lng_manage_messages_ttl_about_channel" = "Turning on this setting will make auto-delete messages from this channel after the selected period.";
"lng_ttl_about_tooltip" = "Messages in this chat will auto-delete in {duration}.";
"lng_ttl_about_duration1" = "24 hours";
"lng_ttl_about_duration2" = "7 days";
"lng_report_title" = "Report channel";
"lng_report_group_title" = "Report group";

View file

@ -50,46 +50,47 @@ struct PeerUpdate {
None = 0,
// Common flags
Name = (1 << 0),
Username = (1 << 1),
Photo = (1 << 2),
About = (1 << 3),
Notifications = (1 << 4),
Migration = (1 << 5),
UnavailableReason = (1 << 6),
PinnedMessages = (1 << 7),
IsBlocked = (1 << 8),
Name = (1U << 0),
Username = (1U << 1),
Photo = (1U << 2),
About = (1U << 3),
Notifications = (1U << 4),
Migration = (1U << 5),
UnavailableReason = (1U << 6),
PinnedMessages = (1U << 7),
IsBlocked = (1U << 8),
MessagesTTL = (1U << 9),
// For users
CanShareContact = (1 << 9),
IsContact = (1 << 10),
PhoneNumber = (1 << 11),
OnlineStatus = (1 << 12),
BotCommands = (1 << 13),
BotCanBeInvited = (1 << 14),
BotStartToken = (1 << 15),
CommonChats = (1 << 16),
HasCalls = (1 << 17),
SupportInfo = (1 << 18),
IsBot = (1 << 19),
CanShareContact = (1U << 10),
IsContact = (1U << 11),
PhoneNumber = (1U << 12),
OnlineStatus = (1U << 13),
BotCommands = (1U << 14),
BotCanBeInvited = (1U << 15),
BotStartToken = (1U << 16),
CommonChats = (1U << 17),
HasCalls = (1U << 18),
SupportInfo = (1U << 19),
IsBot = (1U << 20),
// For chats and channels
InviteLinks = (1 << 20),
Members = (1 << 21),
Admins = (1 << 22),
BannedUsers = (1 << 23),
Rights = (1 << 24),
InviteLinks = (1U << 21),
Members = (1U << 22),
Admins = (1U << 23),
BannedUsers = (1U << 24),
Rights = (1U << 25),
// For channels
ChannelAmIn = (1 << 25),
StickersSet = (1 << 26),
ChannelLinkedChat = (1 << 27),
ChannelLocation = (1 << 28),
Slowmode = (1 << 29),
GroupCall = (1 << 30),
ChannelAmIn = (1U << 26),
StickersSet = (1U << 27),
ChannelLinkedChat = (1U << 28),
ChannelLocation = (1U << 29),
Slowmode = (1U << 30),
GroupCall = (1U << 31),
// For iteration
LastUsedBit = (1 << 30),
LastUsedBit = (1U << 31),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }
@ -103,22 +104,22 @@ struct HistoryUpdate {
enum class Flag : uint32 {
None = 0,
IsPinned = (1 << 0),
UnreadView = (1 << 1),
TopPromoted = (1 << 2),
Folder = (1 << 3),
UnreadMentions = (1 << 4),
LocalMessages = (1 << 5),
ChatOccupied = (1 << 6),
MessageSent = (1 << 7),
ScheduledSent = (1 << 8),
ForwardDraft = (1 << 9),
OutboxRead = (1 << 10),
BotKeyboard = (1 << 11),
CloudDraft = (1 << 12),
LocalDraftSet = (1 << 13),
IsPinned = (1U << 0),
UnreadView = (1U << 1),
TopPromoted = (1U << 2),
Folder = (1U << 3),
UnreadMentions = (1U << 4),
LocalMessages = (1U << 5),
ChatOccupied = (1U << 6),
MessageSent = (1U << 7),
ScheduledSent = (1U << 8),
ForwardDraft = (1U << 9),
OutboxRead = (1U << 10),
BotKeyboard = (1U << 11),
CloudDraft = (1U << 12),
LocalDraftSet = (1U << 13),
LastUsedBit = (1 << 13),
LastUsedBit = (1U << 13),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }
@ -132,16 +133,16 @@ struct MessageUpdate {
enum class Flag : uint32 {
None = 0,
Edited = (1 << 0),
Destroyed = (1 << 1),
DialogRowRepaint = (1 << 2),
DialogRowRefresh = (1 << 3),
NewAdded = (1 << 4),
ReplyMarkup = (1 << 5),
BotCallbackSent = (1 << 6),
NewMaybeAdded = (1 << 7),
Edited = (1U << 0),
Destroyed = (1U << 1),
DialogRowRepaint = (1U << 2),
DialogRowRefresh = (1U << 3),
NewAdded = (1U << 4),
ReplyMarkup = (1U << 5),
BotCallbackSent = (1U << 6),
NewMaybeAdded = (1U << 7),
LastUsedBit = (1 << 7),
LastUsedBit = (1U << 7),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }
@ -155,9 +156,9 @@ struct EntryUpdate {
enum class Flag : uint32 {
None = 0,
Repaint = (1 << 0),
Repaint = (1U << 0),
LastUsedBit = (1 << 0),
LastUsedBit = (1U << 0),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }

View file

@ -955,9 +955,16 @@ void PeerData::setMessagesTTL(
TimeId myPeriod,
TimeId peerPeriod,
bool oneSide) {
_ttlMyPeriod = myPeriod;
_ttlPeerPeriod = peerPeriod;
_ttlOneSide = oneSide;
if (_ttlMyPeriod != myPeriod
|| _ttlPeerPeriod != peerPeriod
|| _ttlOneSide != oneSide) {
_ttlMyPeriod = myPeriod;
_ttlPeerPeriod = peerPeriod;
_ttlOneSide = oneSide;
session().changes().peerUpdated(
this,
Data::PeerUpdate::Flag::MessagesTTL);
}
}
void PeerData::applyMessagesTTL(const MTPPeerHistoryTTL &ttl) {

View file

@ -65,6 +65,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_components.h"
//#include "history/feed/history_feed_section.h" // #feed
#include "history/view/controls/history_view_voice_record_bar.h"
#include "history/view/controls/history_view_ttl_button.h"
#include "history/view/history_view_service_message.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_scheduled_section.h"
@ -589,6 +590,7 @@ HistoryWidget::HistoryWidget(
| UpdateFlag::Slowmode
| UpdateFlag::BotStartToken
| UpdateFlag::PinnedMessages
| UpdateFlag::MessagesTTL
) | rpl::filter([=](const Data::PeerUpdate &update) {
if (_migrated && update.peer.get() == _migrated->peer) {
if (_pinnedTracker
@ -638,6 +640,9 @@ HistoryWidget::HistoryWidget(
if (_pinnedTracker && (flags & UpdateFlag::PinnedMessages)) {
checkPinnedBarState();
}
if (flags & UpdateFlag::MessagesTTL) {
checkMessagesTTL();
}
}, lifetime());
rpl::merge(
@ -1267,6 +1272,7 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) {
}
void HistoryWidget::orderWidgets() {
_voiceRecordBar->raise();
_send->raise();
if (_contactStatus) {
_contactStatus->raise();
@ -1942,6 +1948,7 @@ void HistoryWidget::showHistory(
setupPinnedTracker();
setupGroupCallTracker();
checkMessagesTTL();
if (_history->scrollTopItem
|| (_migrated && _migrated->scrollTopItem)
|| _history->isReadyFor(_showAtMsgId)) {
@ -1995,6 +2002,7 @@ void HistoryWidget::showHistory(
} else {
refreshTopBarActiveChat();
updateTopBarSelection();
checkMessagesTTL();
clearFieldText();
doneShow();
@ -2206,6 +2214,9 @@ void HistoryWidget::updateControlsVisibility() {
if (_scheduled) {
_scheduled->hide();
}
if (_ttlInfo) {
_ttlInfo->hide();
}
_kbScroll->hide();
_fieldBarCancel->hide();
_attachToggle->hide();
@ -2268,6 +2279,9 @@ void HistoryWidget::updateControlsVisibility() {
if (_scheduled) {
_scheduled->show();
}
if (_ttlInfo) {
_ttlInfo->show();
}
updateFieldPlaceholder();
if (_editMsgId || _replyToId || readyToForward() || (_previewData && _previewData->pendingTill >= 0) || _kbReplyTo) {
@ -2296,6 +2310,9 @@ void HistoryWidget::updateControlsVisibility() {
if (_scheduled) {
_scheduled->hide();
}
if (_ttlInfo) {
_ttlInfo->hide();
}
_kbScroll->hide();
_fieldBarCancel->hide();
_attachToggle->hide();
@ -2492,23 +2509,6 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages
} break;
}
const auto ExtractFirstId = [&] {
return histList->empty() ? -1 : IdFromMessage(histList->front());
};
const auto ExtractLastId = [&] {
return histList->empty() ? -1 : IdFromMessage(histList->back());
};
const auto PeerString = [](PeerId peerId) {
if (peerIsUser(peerId)) {
return QString("User-%1").arg(peerToUser(peerId));
} else if (peerIsChat(peerId)) {
return QString("Chat-%1").arg(peerToChat(peerId));
} else if (peerIsChannel(peerId)) {
return QString("Channel-%1").arg(peerToChannel(peerId));
}
return QString("Bad-%1").arg(peerId);
};
if (_preloadRequest == requestId) {
auto to = toMigrated ? _migrated : _history;
addMessagesToFront(peer, *histList);
@ -3992,7 +3992,7 @@ void HistoryWidget::moveFieldControls() {
}
// _attachToggle --------- _inlineResults -------------------------------------- _tabbedPanel --------- _fieldBarCancel
// (_attachDocument|_attachPhoto) _field (_scheduled) (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) _send
// (_attachDocument|_attachPhoto) _field (_ttlInfo) (_scheduled) (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) _send
// (_botStart|_unblock|_joinChannel|_muteUnmute)
auto buttonsBottom = bottom - _attachToggle->height();
@ -4015,6 +4015,10 @@ void HistoryWidget::moveFieldControls() {
}
if (_scheduled) {
_scheduled->moveToRight(right, buttonsBottom);
right += _scheduled->width();
}
if (_ttlInfo) {
_ttlInfo->move(width() - right - _ttlInfo->width(), buttonsBottom);
}
_fieldBarCancel->moveToRight(0, _field->y() - st::historySendPadding - _fieldBarCancel->height());
@ -4046,6 +4050,7 @@ void HistoryWidget::updateFieldSize() {
if (_cmdStartShown) fieldWidth -= _botCommandStart->width();
if (_silent) fieldWidth -= _silent->width();
if (_scheduled) fieldWidth -= _scheduled->width();
if (_ttlInfo) fieldWidth -= _ttlInfo->width();
if (_field->width() != fieldWidth) {
_field->resize(fieldWidth, _field->height());
@ -5405,6 +5410,23 @@ void HistoryWidget::checkPinnedBarState() {
}
}
void HistoryWidget::checkMessagesTTL() {
if (!_peer || !_peer->messagesTTL()) {
if (_ttlInfo) {
_ttlInfo = nullptr;
updateControlsGeometry();
updateControlsVisibility();
}
} else if (!_ttlInfo) {
_ttlInfo = std::make_unique<HistoryView::Controls::TTLButton>(
this,
_peer);
orderWidgets();
updateControlsGeometry();
updateControlsVisibility();
}
}
void HistoryWidget::refreshPinnedBarButton(bool many) {
const auto close = !many;
auto button = object_ptr<Ui::IconButton>(

View file

@ -99,6 +99,7 @@ class GroupCallTracker;
namespace Controls {
class RecordLock;
class VoiceRecordBar;
class TTLButton;
} // namespace Controls
} // namespace HistoryView
@ -483,6 +484,7 @@ private:
int wasScrollTop,
int nowScrollTop);
void checkMessagesTTL();
void setupGroupCallTracker();
void sendInlineResult(InlineBots::ResultSelected result);
@ -692,6 +694,7 @@ private:
object_ptr<Ui::IconButton> _botCommandStart;
object_ptr<Ui::SilentToggle> _silent = { nullptr };
object_ptr<Ui::IconButton> _scheduled = { nullptr };
std::unique_ptr<HistoryView::Controls::TTLButton> _ttlInfo;
const std::unique_ptr<VoiceRecordBar> _voiceRecordBar;
bool _cmdStartShown = false;
object_ptr<Ui::InputField> _field;

View file

@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "history/history_item.h"
#include "history/view/controls/history_view_voice_record_bar.h"
#include "history/view/controls/history_view_ttl_button.h"
#include "history/view/history_view_webpage_preview.h"
#include "inline_bots/inline_results_widget.h"
#include "inline_bots/inline_bot_result.h"
@ -635,6 +636,10 @@ Main::Session &ComposeControls::session() const {
}
void ComposeControls::setHistory(SetHistoryArgs &&args) {
// Right now only single non-null set of history is supported.
// Otherwise initWebpageProcess should be updated / rewritten.
Expects(!_history && *args.history);
_showSlowmodeError = std::move(args.showSlowmodeError);
_slowmodeSecondsLeft = rpl::single(0)
| rpl::then(std::move(args.slowmodeSecondsLeft));
@ -643,20 +648,21 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
_writeRestriction = rpl::single(std::optional<QString>())
| rpl::then(std::move(args.writeRestriction));
const auto history = *args.history;
if (_history == history) {
return;
}
//if (_history == history) {
// return;
//}
_history = history;
_window->tabbedSelector()->setCurrentPeer(
history ? history->peer.get() : nullptr);
initWebpageProcess();
updateBotCommandShown();
updateMessagesTTLShown();
updateControlsGeometry(_wrap->size());
updateControlsVisibility();
updateFieldPlaceholder();
if (!_history) {
return;
}
//if (!_history) {
// return;
//}
const auto peer = _history->peer;
if (peer->isChat() && peer->asChat()->noParticipantInfo()) {
session().api().requestFullPeer(peer);
@ -1736,7 +1742,7 @@ void ComposeControls::finishAnimating() {
void ComposeControls::updateControlsGeometry(QSize size) {
// _attachToggle -- _inlineResults ------ _tabbedPanel -- _fieldBarCancel
// (_attachDocument|_attachPhoto) _field (_silent|_botCommandStart) _tabbedSelectorToggle _send
// (_attachDocument|_attachPhoto) _field (_ttlInfo) (_silent|_botCommandStart) _tabbedSelectorToggle _send
const auto fieldWidth = size.width()
- _attachToggle->width()
@ -1744,7 +1750,8 @@ void ComposeControls::updateControlsGeometry(QSize size) {
- _send->width()
- _tabbedSelectorToggle->width()
- (_botCommandShown ? _botCommandStart->width() : 0)
- (_silent ? _silent->width() : 0);
- (_silent ? _silent->width() : 0)
- (_ttlInfo ? _ttlInfo->width() : 0);
{
const auto oldFieldHeight = _field->height();
_field->resizeToWidth(fieldWidth);
@ -1775,8 +1782,15 @@ void ComposeControls::updateControlsGeometry(QSize size) {
_tabbedSelectorToggle->moveToRight(right, buttonsTop);
right += _tabbedSelectorToggle->width();
_botCommandStart->moveToRight(right, buttonsTop);
if (_botCommandShown) {
right += _botCommandStart->width();
}
if (_silent) {
_silent->moveToRight(right, buttonsTop);
right += _silent->width();
}
if (_ttlInfo) {
_ttlInfo->move(size.width() - right - _ttlInfo->width(), buttonsTop);
}
_voiceRecordBar->resizeToWidth(size.width());
@ -1787,6 +1801,9 @@ void ComposeControls::updateControlsGeometry(QSize size) {
void ComposeControls::updateControlsVisibility() {
_botCommandStart->setVisible(_botCommandShown);
if (_ttlInfo) {
_ttlInfo->show();
}
}
bool ComposeControls::updateBotCommandShown() {
@ -1818,6 +1835,20 @@ void ComposeControls::updateOuterGeometry(QRect rect) {
}
}
void ComposeControls::updateMessagesTTLShown() {
const auto peer = _history ? _history->peer.get() : nullptr;
const auto shown = peer && (peer->messagesTTL() > 0);
if (!shown && _ttlInfo) {
_ttlInfo = nullptr;
updateControlsVisibility();
updateControlsGeometry(_wrap->size());
} else if (shown && !_ttlInfo) {
_ttlInfo = std::make_unique<Controls::TTLButton>(_wrap.get(), peer);
updateControlsVisibility();
updateControlsGeometry(_wrap->size());
}
}
void ComposeControls::paintBackground(QRect clip) {
Painter p(_wrap.get());
@ -2176,6 +2207,7 @@ void ComposeControls::initWebpageProcess() {
session().changes().peerUpdates(
Data::PeerUpdate::Flag::Rights
| Data::PeerUpdate::Flag::Notifications
| Data::PeerUpdate::Flag::MessagesTTL
) | rpl::filter([=](const Data::PeerUpdate &update) {
return (update.peer.get() == peer);
}) | rpl::map([](const Data::PeerUpdate &update) {
@ -2189,6 +2221,9 @@ void ComposeControls::initWebpageProcess() {
if (flags & Data::PeerUpdate::Flag::Notifications) {
updateSilentBroadcast();
}
if (flags & Data::PeerUpdate::Flag::MessagesTTL) {
updateMessagesTTLShown();
}
}, lifetime);
base::ObservableViewer(

View file

@ -69,6 +69,7 @@ namespace HistoryView {
namespace Controls {
class VoiceRecordBar;
class TTLButton;
} // namespace Controls
class FieldHeader;
@ -201,6 +202,7 @@ private:
void initKeyHandler();
void updateSubmitSettings();
void updateSendButtonType();
void updateMessagesTTLShown();
void updateHeight();
void updateWrappingVisibility();
void updateControlsVisibility();
@ -286,6 +288,7 @@ private:
const not_null<Ui::InputField*> _field;
const not_null<Ui::IconButton*> _botCommandStart;
std::unique_ptr<Ui::SilentToggle> _silent;
std::unique_ptr<Controls::TTLButton> _ttlInfo;
std::unique_ptr<InlineBots::Layout::Widget> _inlineResults;
std::unique_ptr<ChatHelpers::TabbedPanel> _tabbedPanel;

View file

@ -0,0 +1,94 @@
/*
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 "history/view/controls/history_view_ttl_button.h"
#include "data/data_peer.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_changes.h"
#include "main/main_session.h"
#include "lang/lang_keys.h"
#include "boxes/peers/edit_peer_info_box.h"
#include "ui/layers/generic_box.h"
#include "ui/toast/toast.h"
#include "apiwrap.h"
#include "styles/style_chat.h"
namespace HistoryView::Controls {
TTLButton::TTLButton(not_null<QWidget*> parent, not_null<PeerData*> peer)
: _button(parent, st::historyMessagesTTL) {
_button.setClickedCallback([=] {
const auto canEdit = peer->isUser()
|| (peer->isChat()
&& peer->asChat()->canDeleteMessages())
|| (peer->isChannel()
&& peer->asChannel()->canDeleteMessages());
if (!canEdit) {
const auto duration = (peer->messagesTTL() < 3 * 86400)
? tr::lng_ttl_about_duration1(tr::now)
: tr::lng_ttl_about_duration2(tr::now);
Ui::Toast::Show(tr::lng_ttl_about_tooltip(
tr::now,
lt_duration,
duration));
return;
}
const auto callback = crl::guard(&peer->session(), [=](
TimeId period) {
using Flag = MTPmessages_SetHistoryTTL::Flag;
peer->session().api().request(MTPmessages_SetHistoryTTL(
MTP_flags(peer->oneSideTTL()
? Flag::f_pm_oneside
: Flag(0)),
peer->input,
MTP_int(period)
)).done([=](const MTPUpdates &result) {
peer->session().api().applyUpdates(result);
}).fail([=](const RPCError &error) {
}).send();
});
Ui::show(
Box(
AutoDeleteSettingsBox,
peer->myMessagesTTL(),
callback),
Ui::LayerOption(0));
});
peer->session().changes().peerUpdates(
peer,
Data::PeerUpdate::Flag::MessagesTTL
) | rpl::start_with_next([=] {
const auto ttl = peer->messagesTTL();
if (ttl < 3 * 86400) {
_button.setIconOverride(nullptr, nullptr);
} else {
_button.setIconOverride(
&st::historyMessagesTTL2Icon,
&st::historyMessagesTTL2IconOver);
}
}, _button.lifetime());
}
void TTLButton::show() {
_button.show();
}
void TTLButton::hide() {
_button.hide();
}
void TTLButton::move(int x, int y) {
_button.move(x, y);
}
int TTLButton::width() const {
return _button.width();
}
} // namespace HistoryView::Controls

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
#include "ui/widgets/buttons.h"
namespace HistoryView::Controls {
class TTLButton final {
public:
TTLButton(not_null<QWidget*> parent, not_null<PeerData*> peer);
void show();
void hide();
void move(int x, int y);
[[nodiscard]] int width() const;
private:
Ui::IconButton _button;
};
} // namespace HistoryView::Controls

View file

@ -308,6 +308,13 @@ historyAttachEmoji: IconButton(historyAttach) {
iconOver: icon {{ "send_control_emoji", historyComposeIconFgOver }};
iconPosition: point(-1px, -1px);
}
historyMessagesTTL: IconButton(historyAttach) {
icon: icon {{ "chat/send_control_autodelete_1d", historyComposeIconFg }};
iconOver: icon {{ "chat/send_control_autodelete_1d", historyComposeIconFgOver }};
iconPosition: point(-1px, -1px);
}
historyMessagesTTL2Icon: icon {{ "chat/send_control_autodelete_7d", historyComposeIconFg }};
historyMessagesTTL2IconOver: icon {{ "chat/send_control_autodelete_7d", historyComposeIconFgOver }};
historyAttachEmojiFgActive: windowActiveTextFg;
historyAttachEmojiActive: icon {{ "send_control_emoji", historyAttachEmojiFgActive }};
historyAttachEmojiTooltipDelta: 4px;