Add recent emoji context menu.

This commit is contained in:
John Preston 2023-08-21 16:24:12 +02:00
parent 58d762f130
commit 23dbe4742a
7 changed files with 180 additions and 25 deletions

View File

@ -1813,6 +1813,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_emoji_set_download" = "Download {size}";
"lng_emoji_set_loading" = "{percent}, {progress}";
"lng_emoji_color_all" = "Choose color for all emoji";
"lng_emoji_copy" = "Copy emoji";
"lng_emoji_view_pack" = "View pack";
"lng_emoji_remove_recent" = "Remove from recents";
"lng_emoji_reset_recent" = "Reset recents";
"lng_emoji_reset_recent_sure" = "Do you want to reset recent emoji?";
"lng_emoji_reset_recent_button" = "Reset";
"lng_recent_stickers" = "Frequently used";
"lng_faved_stickers_add" = "Add to Favorites";

View File

@ -689,8 +689,6 @@ public:
style::margins margins = {});
private:
void subscribeToCustomDeviceModel();
const not_null<Main::Session*> _session;
rpl::event_stream<uint64> _terminateRequests;
@ -1054,18 +1052,6 @@ Main::Session &SessionsContent::ListController::session() const {
return *_session;
}
void SessionsContent::ListController::subscribeToCustomDeviceModel() {
Core::App().settings().deviceModelChanges(
) | rpl::start_with_next([=](const QString &model) {
for (auto i = 0; i != delegate()->peerListFullRowsCount(); ++i) {
const auto row = delegate()->peerListRowAt(i);
if (!row->id()) {
static_cast<Row*>(row.get())->updateName(model);
}
}
}, lifetime());
}
void SessionsContent::ListController::prepare() {
}

View File

@ -8,9 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/emoji_list_widget.h"
#include "base/unixtime.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/tabbed_search.h"
#include "ui/text/format_values.h"
#include "ui/effects/animations.h"
#include "ui/widgets/menu/menu_add_action_callback.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/shadow.h"
@ -41,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_premium.h"
#include "window/window_session_controller.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_menu_icons.h"
namespace ChatHelpers {
namespace {
@ -1035,7 +1039,7 @@ void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
base::unique_qptr<Ui::PopupMenu> EmojiListWidget::fillContextMenu(
SendMenu::Type type) {
if (_mode != Mode::EmojiStatus || v::is_null(_selected)) {
if (v::is_null(_selected)) {
return nullptr;
}
const auto over = std::get_if<OverEmoji>(&_selected);
@ -1044,13 +1048,99 @@ base::unique_qptr<Ui::PopupMenu> EmojiListWidget::fillContextMenu(
}
const auto section = over->section;
const auto index = over->index;
const auto chosen = lookupCustomEmoji(index, section);
if (!chosen) {
return nullptr;
}
auto menu = base::make_unique_q<Ui::PopupMenu>(
this,
st::defaultPopupMenu);
(_mode == Mode::Full
? st::popupMenuWithIcons
: st::defaultPopupMenu));
if (_mode == Mode::Full) {
fillRecentMenu(menu, section, index);
} else if (_mode == Mode::EmojiStatus) {
fillEmojiStatusMenu(menu, section, index);
}
if (menu->empty()) {
return nullptr;
}
return menu;
}
void EmojiListWidget::fillRecentMenu(
not_null<Ui::PopupMenu*> menu,
int section,
int index) {
if (section != int(Section::Recent)) {
return;
}
const auto addAction = Ui::Menu::CreateAddActionCallback(menu);
const auto over = OverEmoji{ section, index };
const auto emoji = lookupOverEmoji(&over);
const auto custom = lookupCustomEmoji(index, section);
if (custom && custom->sticker()) {
const auto sticker = custom->sticker();
const auto emoji = sticker->alt;
const auto setId = sticker->set.id;
if (!emoji.isEmpty()) {
auto data = TextForMimeData{ emoji, { emoji } };
data.rich.entities.push_back({
EntityType::CustomEmoji,
0,
emoji.size(),
Data::SerializeCustomEmojiId(custom)
});
addAction(tr::lng_emoji_copy(tr::now), [=] {
TextUtilities::SetClipboardText(data);
}, &st::menuIconCopy);
}
if (setId && _features.openStickerSets) {
addAction(
tr::lng_emoji_view_pack(tr::now),
crl::guard(this, [=] { displaySet(setId); }),
&st::menuIconShowAll);
}
}
auto id = RecentEmojiId{ emoji };
if (custom) {
id.data = RecentEmojiDocument{
.id = custom->id,
.test = custom->session().isTestMode(),
};
}
addAction(tr::lng_emoji_remove_recent(tr::now), crl::guard(this, [=] {
Core::App().settings().hideRecentEmoji(id);
refreshRecent();
}), &st::menuIconCancel);
menu->addSeparator(&st().expandedSeparator);
const auto resetRecent = [=] {
const auto sure = [=](Fn<void()> &&close) {
Core::App().settings().resetRecentEmoji();
refreshRecent();
close();
};
checkHideWithBox(Ui::MakeConfirmBox({
.text = tr::lng_emoji_reset_recent_sure(),
.confirmed = crl::guard(this, sure),
.confirmText = tr::lng_emoji_reset_recent_button(tr::now),
.labelStyle = &st().boxLabel,
}));
};
addAction({
.text = tr::lng_emoji_reset_recent(tr::now),
.handler = crl::guard(this, resetRecent),
.icon = &st::menuIconRestoreAttention,
.isAttention = true,
});
}
void EmojiListWidget::fillEmojiStatusMenu(
not_null<Ui::PopupMenu*> menu,
int section,
int index) {
const auto chosen = lookupCustomEmoji(index, section);
if (!chosen) {
return;
}
const auto selectWith = [=](TimeId scheduled) {
selectCustom(
lookupChosen(chosen, nullptr, { .scheduled = scheduled }));
@ -1068,7 +1158,6 @@ base::unique_qptr<Ui::PopupMenu> EmojiListWidget::fillContextMenu(
tr::lng_manage_messages_ttl_after_custom(tr::now),
crl::guard(this, [=] { selectWith(
TabbedSelector::kPickCustomTimeId); }));
return menu;
}
void EmojiListWidget::paintEvent(QPaintEvent *e) {
@ -1884,6 +1973,7 @@ void EmojiListWidget::refreshRecent() {
clearSelection();
fillRecent();
resizeToWidth(width());
update();
}
void EmojiListWidget::refreshCustom() {

View File

@ -266,6 +266,15 @@ private:
void setSelected(OverState newSelected);
void setPressed(OverState newPressed);
void fillRecentMenu(
not_null<Ui::PopupMenu*> menu,
int section,
int index);
void fillEmojiStatusMenu(
not_null<Ui::PopupMenu*> menu,
int section,
int index);
[[nodiscard]] EmojiPtr lookupOverEmoji(const OverEmoji *over) const;
[[nodiscard]] DocumentData *lookupCustomEmoji(
int index,

View File

@ -201,7 +201,10 @@ QByteArray Settings::serialize() const {
+ Serialize::bytearraySize(mediaViewPosition)
+ sizeof(qint32)
+ sizeof(quint64)
+ sizeof(qint32);
+ sizeof(qint32) * 2;
for (const auto &id : _recentEmojiSkip) {
size += Serialize::stringSize(id);
}
auto result = QByteArray();
result.reserve(size);
@ -336,7 +339,11 @@ QByteArray Settings::serialize() const {
<< mediaViewPosition
<< qint32(_ignoreBatterySaving.current() ? 1 : 0)
<< quint64(_macRoundIconDigest.value_or(0))
<< qint32(_storiesClickTooltipHidden.current() ? 1 : 0);
<< qint32(_storiesClickTooltipHidden.current() ? 1 : 0)
<< qint32(_recentEmojiSkip.size());
for (const auto &id : _recentEmojiSkip) {
stream << id;
}
}
return result;
}
@ -443,6 +450,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
qint32 ignoreBatterySaving = _ignoreBatterySaving.current() ? 1 : 0;
quint64 macRoundIconDigest = _macRoundIconDigest.value_or(0);
qint32 storiesClickTooltipHidden = _storiesClickTooltipHidden.current() ? 1 : 0;
base::flat_set<QString> recentEmojiSkip;
stream >> themesAccentColors;
if (!stream.atEnd()) {
@ -680,6 +688,19 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) {
stream >> storiesClickTooltipHidden;
}
if (!stream.atEnd()) {
auto count = qint32();
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
auto id = QString();
stream >> id;
if (stream.status() == QDataStream::Ok) {
recentEmojiSkip.emplace(id);
}
}
}
}
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()"));
@ -872,6 +893,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
_ignoreBatterySaving = (ignoreBatterySaving == 1);
_macRoundIconDigest = macRoundIconDigest ? macRoundIconDigest : std::optional<uint64>();
_storiesClickTooltipHidden = (storiesClickTooltipHidden == 1);
_recentEmojiSkip = std::move(recentEmojiSkip);
}
QString Settings::getSoundPath(const QString &key) const {
@ -964,7 +986,8 @@ rpl::producer<int> Settings::thirdColumnWidthChanges() const {
}
const std::vector<RecentEmoji> &Settings::recentEmoji() const {
if (_recentEmoji.empty()) {
if (!_recentEmojiResolved) {
_recentEmojiResolved = true;
resolveRecentEmoji();
}
return _recentEmoji;
@ -1005,6 +1028,8 @@ void Settings::resolveRecentEmoji() const {
for (const auto emoji : Ui::Emoji::GetDefaultRecent()) {
if (_recentEmoji.size() >= specialCount + kRecentEmojiLimit) {
break;
} else if (_recentEmojiSkip.contains(emoji->id())) {
continue;
} else if (!haveAlready({ emoji })) {
_recentEmoji.push_back({ { emoji }, 1 });
}
@ -1014,6 +1039,9 @@ void Settings::resolveRecentEmoji() const {
void Settings::incrementRecentEmoji(RecentEmojiId id) {
resolveRecentEmoji();
if (const auto emoji = std::get_if<EmojiPtr>(&id.data)) {
_recentEmojiSkip.remove((*emoji)->id());
}
auto i = _recentEmoji.begin(), e = _recentEmoji.end();
for (; i != e; ++i) {
if (i->id == id) {
@ -1065,6 +1093,36 @@ void Settings::incrementRecentEmoji(RecentEmojiId id) {
_saveDelayed.fire({});
}
void Settings::hideRecentEmoji(RecentEmojiId id) {
resolveRecentEmoji();
_recentEmoji.erase(
ranges::remove(_recentEmoji, id, &RecentEmoji::id),
end(_recentEmoji));
if (const auto emoji = std::get_if<EmojiPtr>(&id.data)) {
for (const auto always : Ui::Emoji::GetDefaultRecent()) {
if (always == *emoji) {
_recentEmojiSkip.emplace(always->id());
break;
}
}
}
_recentEmojiUpdated.fire({});
_saveDelayed.fire({});
}
void Settings::resetRecentEmoji() {
resolveRecentEmoji();
_recentEmoji.clear();
_recentEmojiSkip.clear();
_recentEmojiPreload.clear();
_recentEmojiResolved = false;
_recentEmojiUpdated.fire({});
_saveDelayed.fire({});
}
void Settings::setLegacyRecentEmojiPreload(
QVector<QPair<QString, ushort>> data) {
if (!_recentEmojiPreload.empty() || data.isEmpty()) {

View File

@ -72,7 +72,7 @@ struct WindowTitleContent {
WindowTitleContent) = default;
};
constexpr auto kRecentEmojiLimit = 42;
constexpr auto kRecentEmojiLimit = 54;
struct RecentEmojiDocument {
DocumentId id = 0;
@ -660,6 +660,8 @@ public:
[[nodiscard]] const std::vector<RecentEmoji> &recentEmoji() const;
void incrementRecentEmoji(RecentEmojiId id);
void hideRecentEmoji(RecentEmojiId id);
void resetRecentEmoji();
void setLegacyRecentEmojiPreload(QVector<QPair<QString, ushort>> data);
[[nodiscard]] rpl::producer<> recentEmojiUpdated() const {
return _recentEmojiUpdated.events();
@ -894,6 +896,8 @@ private:
rpl::variable<bool> _mainMenuAccountsShown = true;
mutable std::vector<RecentEmojiPreload> _recentEmojiPreload;
mutable std::vector<RecentEmoji> _recentEmoji;
base::flat_set<QString> _recentEmojiSkip;
mutable bool _recentEmojiResolved = false;
base::flat_map<QString, uint8> _emojiVariants;
rpl::event_stream<> _recentEmojiUpdated;
bool _tabbedSelectorSectionEnabled = false; // per-window

View File

@ -137,6 +137,7 @@ menuIconBotCommands: icon {{ "menu/bot_commands", menuIconColor }};
menuIconPremium: icon {{ "menu/premium", menuIconColor }};
menuIconIpAddress: icon {{ "menu/ip_address", menuIconColor }};
menuIconAddress: icon {{ "menu/payment_address", menuIconColor }};
menuIconShowAll: icon {{ "menu/all_media", menuIconColor }};
menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }};
menuIconTTLAnyTextPosition: point(11px, 22px);
@ -169,6 +170,7 @@ menuIconDeleteAttention: icon {{ "menu/delete", menuIconAttentionColor }};
menuIconLeaveAttention: icon {{ "menu/leave", menuIconAttentionColor }};
menuIconDisableAttention: icon {{ "menu/disable", menuIconAttentionColor }};
menuIconReportAttention: icon {{ "menu/report", menuIconAttentionColor }};
menuIconRestoreAttention: icon {{ "menu/restore", menuIconAttentionColor }};
menuIconBlockSettings: icon {{ "menu/block", windowBgActive }};