diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a520d1a0d..652097375 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1811,6 +1811,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts."; "lng_accounts_limit2" = "You can free one space by subscribing to **Telegram Premium** with one of these connected accounts:"; +"lng_emoji_status_for_title" = "Set Status for..."; +"lng_emoji_status_for_submit" = "Set Status"; +"lng_emoji_status_menu_duration_any" = "Set Status for {duration}"; + "lng_group_about_header" = "You have created a group."; "lng_group_about_text" = "Groups can have:"; "lng_group_about1" = "Up to 100,000 members"; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 610e929c5..5525e3a0c 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -7,8 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/emoji_list_widget.h" +#include "base/unixtime.h" +#include "ui/text/format_values.h" #include "ui/effects/animations.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/popup_menu.h" #include "ui/widgets/shadow.h" #include "ui/text/custom_emoji_instance.h" #include "ui/effects/ripple_animation.h" @@ -33,7 +36,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "settings/settings_premium.h" #include "window/window_session_controller.h" -#include "facades.h" #include "styles/style_chat_helpers.h" namespace ChatHelpers { @@ -794,6 +796,45 @@ void EmojiListWidget::fillRecentFrom(const std::vector &list) { } } +void EmojiListWidget::fillContextMenu( + not_null menu, + SendMenu::Type type) { + if (v::is_null(_selected)) { + return; + } + const auto over = std::get_if(&_selected); + if (!over) { + return; + } + const auto section = over->section; + const auto index = over->index; + // Ignore the default status. + if (!index && (section == int(Section::Recent))) { + return; + } + const auto chosen = lookupCustomEmoji(index, section); + if (!chosen) { + return; + } + for (const auto &value : { 3600, 3600 * 8, 3600 * 24, 3600 * 24 * 7 }) { + const auto text = tr::lng_emoji_status_menu_duration_any( + tr::now, + lt_duration, + Ui::FormatMuteFor(value)); + menu->addAction(text, crl::guard(this, [=] { + selectCustom( + chosen, + { .scheduled = base::unixtime::now() + value } ); + })); + } + const auto options = Api::SendOptions{ + .scheduled = TabbedSelector::kPickCustomTimeId, + }; + menu->addAction( + tr::lng_manage_messages_ttl_after_custom(tr::now), + crl::guard(this, [=] { selectCustom(chosen, options); })); +} + void EmojiListWidget::paintEvent(QPaintEvent *e) { Painter p(this); @@ -1050,6 +1091,27 @@ bool EmojiListWidget::checkPickerHide() { return false; } +DocumentData *EmojiListWidget::lookupCustomEmoji( + int index, + int section) const { + if (section == int(Section::Recent) + && index < _recent.size()) { + const auto document = std::get_if( + &_recent[index].id.data); + const auto custom = document + ? session().data().document(document->id).get() + : nullptr; + if (custom && custom->sticker()) { + return custom; + } + } else if (section >= _staticCount + && index < _custom[section - _staticCount].list.size()) { + auto &set = _custom[section - _staticCount]; + return set.list[index].document; + } + return nullptr; +} + EmojiPtr EmojiListWidget::lookupOverEmoji(const OverEmoji *over) const { const auto section = over ? over->section : -1; const auto index = over ? over->index : -1; @@ -1131,20 +1193,8 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { return; } selectEmoji(emoji); - } else if (section == int(Section::Recent) - && index < _recent.size()) { - const auto document = std::get_if( - &_recent[index].id.data); - const auto custom = document - ? session().data().document(document->id).get() - : nullptr; - if (custom && custom->sticker()) { - selectCustom(custom); - } - } else if (section >= _staticCount - && index < _custom[section - _staticCount].list.size()) { - auto &set = _custom[section - _staticCount]; - selectCustom(set.list[index].document); + } else if (const auto custom = lookupCustomEmoji(index, section)) { + selectCustom(custom); } } else if (const auto set = std::get_if(&pressed)) { Assert(set->section >= _staticCount @@ -1201,7 +1251,9 @@ void EmojiListWidget::selectEmoji(EmojiPtr emoji) { _chosen.fire_copy(emoji); } -void EmojiListWidget::selectCustom(not_null document) { +void EmojiListWidget::selectCustom( + not_null document, + Api::SendOptions options) { if (document->isPremiumEmoji() && !document->session().premium() && !_allowWithoutPremium) { @@ -1215,7 +1267,7 @@ void EmojiListWidget::selectCustom(not_null document) { document->session().isTestMode(), } }); } - _customChosen.fire({ .document = document }); + _customChosen.fire({ .document = document, .options = options }); } void EmojiListWidget::showPicker() { diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index b47f2a198..bf4c217c2 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -126,6 +126,10 @@ public: float64 progress, RectPart origin); + void fillContextMenu( + not_null menu, + SendMenu::Type type) override; + protected: void visibleTopBottomUpdated( int visibleTop, @@ -247,8 +251,13 @@ private: void setPressed(OverState newPressed); [[nodiscard]] EmojiPtr lookupOverEmoji(const OverEmoji *over) const; + [[nodiscard]] DocumentData *lookupCustomEmoji( + int index, + int section) const; void selectEmoji(EmojiPtr emoji); - void selectCustom(not_null document); + void selectCustom( + not_null document, + Api::SendOptions options = Api::SendOptions()); void paint(QPainter &p, ExpandingContext context, QRect clip); void drawCollapsedBadge(QPainter &p, QPoint position, int count); void drawRecent( diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index a891b04ca..10f1c7406 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -60,6 +60,7 @@ class GifsListWidget; class TabbedSelector : public Ui::RpWidget { public: + static constexpr auto kPickCustomTimeId = -1; struct FileChosen { not_null document; Api::SendOptions options; diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.cpp b/Telegram/SourceFiles/data/data_emoji_statuses.cpp index 6cf2c1b58..863791969 100644 --- a/Telegram/SourceFiles/data/data_emoji_statuses.cpp +++ b/Telegram/SourceFiles/data/data_emoji_statuses.cpp @@ -239,14 +239,18 @@ void EmojiStatuses::updateColored(const MTPDmessages_stickerSet &data) { _coloredUpdated.fire({}); } -void EmojiStatuses::set(DocumentId id) { +void EmojiStatuses::set(DocumentId id, TimeId until) { auto &api = _owner->session().api(); if (_sentRequestId) { api.request(base::take(_sentRequestId)).cancel(); } - _owner->session().user()->setEmojiStatus(id); + _owner->session().user()->setEmojiStatus(id, until); _sentRequestId = api.request(MTPaccount_UpdateEmojiStatus( - id ? MTP_emojiStatus(MTP_long(id)) : MTP_emojiStatusEmpty() + !id + ? MTP_emojiStatusEmpty() + : !until + ? MTP_emojiStatus(MTP_long(id)) + : MTP_emojiStatusUntil(MTP_long(id), MTP_int(until)) )).done([=] { _sentRequestId = 0; }).fail([=] { diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.h b/Telegram/SourceFiles/data/data_emoji_statuses.h index 2bf980deb..d1172c4a5 100644 --- a/Telegram/SourceFiles/data/data_emoji_statuses.h +++ b/Telegram/SourceFiles/data/data_emoji_statuses.h @@ -48,7 +48,7 @@ public: [[nodiscard]] rpl::producer<> defaultUpdates() const; [[nodiscard]] rpl::producer<> coloredUpdates() const; - void set(DocumentId id); + void set(DocumentId id, TimeId until = 0); [[nodiscard]] bool setting() const; void registerAutomaticClear(not_null user, TimeId until); diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index 7de81c6b9..8fe55b2a2 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -22,10 +22,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/info_controller.h" #include "info/info_memento.h" #include "lang/lang_keys.h" +#include "menu/menu_send.h" // SendMenu::Type. +#include "ui/boxes/confirm_box.h" +#include "ui/boxes/time_picker_box.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/effects/ripple_animation.h" -#include "ui/text/text_block.h" +#include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/special_buttons.h" #include "ui/unread_badge.h" @@ -80,6 +83,26 @@ auto ChatStatusText(int fullCount, int onlineCount, bool isGroup) { : tr::lng_channel_status(tr::now); }; +void PickUntilBox(not_null box, Fn callback) { + box->setTitle(tr::lng_emoji_status_for_title()); + + const auto seconds = Ui::DefaultTimePickerValues(); + const auto phrases = ranges::views::all( + seconds + ) | ranges::views::transform(Ui::FormatMuteFor) | ranges::to_vector; + + const auto pickerCallback = Ui::TimePickerBox(box, seconds, phrases, 0); + + Ui::ConfirmBox(box, { + .confirmed = [=] { + callback(pickerCallback()); + box->closeBox(); + }, + .confirmText = tr::lng_emoji_status_for_submit(), + .cancelText = tr::lng_cancel(), + }); +} + } // namespace BadgeView::BadgeView( @@ -301,17 +324,37 @@ void EmojiStatusPanel::create( _panel->hide(); _panel->selector()->setAllowEmojiWithoutPremium(false); + struct Chosen { + DocumentId id = 0; + TimeId until = 0; + }; + + _panel->selector()->contextMenuRequested( + ) | rpl::start_with_next([=] { + _panel->selector()->showMenuWithType(SendMenu::Type::Scheduled); + }, _panel->lifetime()); + auto statusChosen = _panel->selector()->customEmojiChosen( ) | rpl::map([=](Selector::FileChosen data) { - return data.document->id; + return Chosen{ data.document->id, data.options.scheduled }; }); rpl::merge( std::move(statusChosen), - _panel->selector()->emojiChosen() | rpl::map_to(DocumentId()) - ) | rpl::start_with_next([=](DocumentId id) { - controller->session().data().emojiStatuses().set(id); - _panel->hideAnimated(); + _panel->selector()->emojiChosen() | rpl::map_to(Chosen()) + ) | rpl::start_with_next([=](const Chosen chosen) { + if (chosen.until == ChatHelpers::TabbedSelector::kPickCustomTimeId) { + controller->show(Box(PickUntilBox, [=](TimeId seconds) { + controller->session().data().emojiStatuses().set( + chosen.id, + base::unixtime::now() + seconds); + })); + } else { + controller->session().data().emojiStatuses().set( + chosen.id, + chosen.until); + _panel->hideAnimated(); + } }, _panel->lifetime()); _panel->selector()->showPromoForPremiumEmoji(); diff --git a/Telegram/SourceFiles/menu/menu_mute.cpp b/Telegram/SourceFiles/menu/menu_mute.cpp index 5079a7d37..85daa310c 100644 --- a/Telegram/SourceFiles/menu/menu_mute.cpp +++ b/Telegram/SourceFiles/menu/menu_mute.cpp @@ -171,24 +171,7 @@ void PickMuteBox(not_null box, not_null peer) { struct State { base::unique_qptr menu; }; - const auto seconds = std::vector{ - (60 * 15), - (60 * 30), - (3600 * 1), - (3600 * 2), - (3600 * 3), - (3600 * 4), - (3600 * 8), - (3600 * 12), - (86400 * 1), - (86400 * 2), - (86400 * 3), - (86400 * 7 * 1), - (86400 * 7 * 2), - (86400 * 31 * 1), - (86400 * 31 * 2), - (86400 * 31 * 3), - }; + const auto seconds = Ui::DefaultTimePickerValues(); const auto phrases = ranges::views::all( seconds ) | ranges::views::transform(Ui::FormatMuteFor) | ranges::to_vector; diff --git a/Telegram/SourceFiles/ui/boxes/time_picker_box.cpp b/Telegram/SourceFiles/ui/boxes/time_picker_box.cpp index 3e001e9ef..4ed30b247 100644 --- a/Telegram/SourceFiles/ui/boxes/time_picker_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/time_picker_box.cpp @@ -24,6 +24,27 @@ constexpr auto kMinYScale = 0.2; } // namespace +std::vector DefaultTimePickerValues() { + return { + (60 * 15), + (60 * 30), + (3600 * 1), + (3600 * 2), + (3600 * 3), + (3600 * 4), + (3600 * 8), + (3600 * 12), + (86400 * 1), + (86400 * 2), + (86400 * 3), + (86400 * 7 * 1), + (86400 * 7 * 2), + (86400 * 31 * 1), + (86400 * 31 * 2), + (86400 * 31 * 3), + }; +} + Fn TimePickerBox( not_null box, std::vector values, diff --git a/Telegram/SourceFiles/ui/boxes/time_picker_box.h b/Telegram/SourceFiles/ui/boxes/time_picker_box.h index 407816b4a..26d16647d 100644 --- a/Telegram/SourceFiles/ui/boxes/time_picker_box.h +++ b/Telegram/SourceFiles/ui/boxes/time_picker_box.h @@ -11,7 +11,9 @@ namespace Ui { class GenericBox; -Fn TimePickerBox( +[[nodiscard]] std::vector DefaultTimePickerValues(); + +[[nodiscard]] Fn TimePickerBox( not_null box, std::vector values, std::vector phrases,