diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2ac849314..31f384f0a 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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"; diff --git a/Telegram/SourceFiles/boxes/sessions_box.cpp b/Telegram/SourceFiles/boxes/sessions_box.cpp index 5a45ca0de..f771587de 100644 --- a/Telegram/SourceFiles/boxes/sessions_box.cpp +++ b/Telegram/SourceFiles/boxes/sessions_box.cpp @@ -689,8 +689,6 @@ public: style::margins margins = {}); private: - void subscribeToCustomDeviceModel(); - const not_null _session; rpl::event_stream _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.get())->updateName(model); - } - } - }, lifetime()); -} - void SessionsContent::ListController::prepare() { } diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 11fa6b234..357d0485c 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -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 &list) { base::unique_qptr 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(&_selected); @@ -1044,13 +1048,99 @@ base::unique_qptr 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( 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 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 &&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 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 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() { diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index b042a568e..5cc197995 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -266,6 +266,15 @@ private: void setSelected(OverState newSelected); void setPressed(OverState newPressed); + void fillRecentMenu( + not_null menu, + int section, + int index); + void fillEmojiStatusMenu( + not_null menu, + int section, + int index); + [[nodiscard]] EmojiPtr lookupOverEmoji(const OverEmoji *over) const; [[nodiscard]] DocumentData *lookupCustomEmoji( int index, diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index 8abdd7496..4a2cb3023 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -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 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(); _storiesClickTooltipHidden = (storiesClickTooltipHidden == 1); + _recentEmojiSkip = std::move(recentEmojiSkip); } QString Settings::getSoundPath(const QString &key) const { @@ -964,7 +986,8 @@ rpl::producer Settings::thirdColumnWidthChanges() const { } const std::vector &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(&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(&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> data) { if (!_recentEmojiPreload.empty() || data.isEmpty()) { diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index bc7b74282..f25345da6 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -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() const; void incrementRecentEmoji(RecentEmojiId id); + void hideRecentEmoji(RecentEmojiId id); + void resetRecentEmoji(); void setLegacyRecentEmojiPreload(QVector> data); [[nodiscard]] rpl::producer<> recentEmojiUpdated() const { return _recentEmojiUpdated.events(); @@ -894,6 +896,8 @@ private: rpl::variable _mainMenuAccountsShown = true; mutable std::vector _recentEmojiPreload; mutable std::vector _recentEmoji; + base::flat_set _recentEmojiSkip; + mutable bool _recentEmojiResolved = false; base::flat_map _emojiVariants; rpl::event_stream<> _recentEmojiUpdated; bool _tabbedSelectorSectionEnabled = false; // per-window diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index 5f19251c0..bfb508773 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -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 }};