diff --git a/Telegram/Resources/icons/menu/mute_for.png b/Telegram/Resources/icons/menu/mute_for.png new file mode 100644 index 0000000000..bd3c183407 Binary files /dev/null and b/Telegram/Resources/icons/menu/mute_for.png differ diff --git a/Telegram/Resources/icons/menu/mute_for@2x.png b/Telegram/Resources/icons/menu/mute_for@2x.png new file mode 100644 index 0000000000..69daac8080 Binary files /dev/null and b/Telegram/Resources/icons/menu/mute_for@2x.png differ diff --git a/Telegram/Resources/icons/menu/mute_for@3x.png b/Telegram/Resources/icons/menu/mute_for@3x.png new file mode 100644 index 0000000000..8d1e82b13f Binary files /dev/null and b/Telegram/Resources/icons/menu/mute_for@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b67ace8b1e..ebc0cc69fb 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -904,6 +904,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_mute_box_title" = "Mute notifications for..."; "lng_mute_box_no_notifications" = "No notifications"; "lng_mute_box_silent_notifications" = "Silent notifications"; +"lng_mute_box_days#one" = "{count} day"; +"lng_mute_box_days#other" = "{count} days"; +"lng_mute_box_hours#one" = "{count} hour"; +"lng_mute_box_hours#other" = "{count} hours"; +"lng_mute_box_minutes#one" = "{count} minute"; +"lng_mute_box_minutes#other" = "{count} minutes"; "lng_mute_duration_hours#one" = "For {count} hour"; "lng_mute_duration_hours#other" = "For {count} hours"; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 111bea5b8c..9d368b64af 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -989,6 +989,16 @@ scheduleTimeSeparator: FlatLabel(defaultFlatLabel) { } scheduleTimeSeparatorPadding: margins(2px, 0px, 2px, 0px); +muteBoxTimeField: InputField(scheduleDateField) { + textMargins: margins(0px, 0px, 0px, 0px); + placeholderMargins: margins(0px, 0px, 0px, 0px); + placeholderScale: 0.; + heightMin: 30px; + textAlign: align(left); + font: font(14px); +} +muteBoxTimeFieldPadding: margins(5px, 0px, 5px, 0px); + boxAttentionDividerLabel: FlatLabel(boxDividerLabel) { textFg: boxTextFgError; } diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index a7cbc234b4..6358fa0135 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -379,7 +379,7 @@ object_ptr DetailsFiller::setupMuteToggle() { MuteMenu::SetupMuteMenu( result.data(), result->clicks() | rpl::to_empty, - peer); + { peer, std::make_shared(_controller) }); object_ptr( result, st::infoIconNotifications, diff --git a/Telegram/SourceFiles/menu/menu_mute.cpp b/Telegram/SourceFiles/menu/menu_mute.cpp index 2dd39e9c75..480ed4b8f8 100644 --- a/Telegram/SourceFiles/menu/menu_mute.cpp +++ b/Telegram/SourceFiles/menu/menu_mute.cpp @@ -7,15 +7,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "menu/menu_mute.h" +#include "base/qt_signal_producer.h" #include "data/data_peer.h" #include "data/data_session.h" #include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" #include "menu/menu_check_item.h" #include "ui/effects/animation_value.h" +#include "ui/layers/generic_box.h" #include "ui/widgets/checkbox.h" +#include "ui/widgets/fields/time_part_input_with_placeholder.h" #include "ui/widgets/menu/menu_action.h" #include "ui/widgets/popup_menu.h" +#include "styles/style_boxes.h" +#include "styles/style_info.h" // infoTopBarMenu +#include "styles/style_layers.h" #include "styles/style_menu_icons.h" namespace MuteMenu { @@ -132,11 +138,162 @@ void FillSoundMenu( }, menu->lifetime()); } +void MuteBox(not_null box, not_null peer) { + using TimeField = Ui::TimePartWithPlaceholder; + const auto putNext = [](not_null field, QChar ch) { + field->setCursorPosition(0); + if (ch.unicode()) { + field->setText(ch + field->getLastText()); + field->setCursorPosition(1); + } + field->onTextEdited(); + field->setFocus(); + }; + + const auto erasePrevious = [](not_null field) { + const auto text = field->getLastText(); + if (!text.isEmpty()) { + field->setCursorPosition(text.size() - 1); + field->setText(text.mid(0, text.size() - 1)); + } + field->setFocus(); + }; + + struct State { + not_null day; + not_null hour; + not_null minute; + + base::unique_qptr menu; + + rpl::variable noSoundChanges; + int valueInSeconds = 0; + }; + + const auto content = box->addRow( + object_ptr(box, st::scheduleHeight)); + + const auto state = box->lifetime().make_state(State{ + .day = Ui::CreateChild( + content, + st::muteBoxTimeField, + rpl::never(), + QString::number(0)), + .hour = Ui::CreateChild( + content, + st::muteBoxTimeField, + rpl::never(), + QString::number(0)), + .minute = Ui::CreateChild( + content, + st::muteBoxTimeField, + rpl::never(), + QString::number(0)), + .noSoundChanges = false, + }); + + const auto day = Ui::MakeWeak(state->day); + const auto hour = Ui::MakeWeak(state->hour); + const auto minute = Ui::MakeWeak(state->minute); + + day->setPhrase(tr::lng_mute_box_days); + day->setMaxValue(31); + day->setWheelStep(1); + day->putNext() | rpl::start_with_next([=](QChar ch) { + putNext(hour, ch); + }, box->lifetime()); + + hour->setPhrase(tr::lng_mute_box_hours); + hour->setMaxValue(23); + hour->setWheelStep(1); + hour->putNext() | rpl::start_with_next([=](QChar ch) { + putNext(minute, ch); + }, box->lifetime()); + hour->erasePrevious() | rpl::start_with_next([=] { + erasePrevious(day); + }, box->lifetime()); + + minute->setPhrase(tr::lng_mute_box_minutes); + minute->setMaxValue(59); + minute->setWheelStep(10); + minute->erasePrevious() | rpl::start_with_next([=] { + erasePrevious(hour); + }, box->lifetime()); + + content->sizeValue( + ) | rpl::start_with_next([=](const QSize &s) { + const auto inputWidth = s.width() / 3; + auto rect = QRect( + 0, + (s.height() - day->height()) / 2, + inputWidth, + day->height()); + for (const auto &input : { day, hour, minute }) { + input->setGeometry(rect - st::muteBoxTimeFieldPadding); + rect.translate(inputWidth, 0); + } + }, box->lifetime()); + + box->setTitle(tr::lng_mute_box_title()); + + const auto topButton = box->addTopButton(st::infoTopBarMenu); + topButton->setClickedCallback([=] { + if (state->menu) { + return; + } + state->menu = base::make_unique_q( + topButton, + st::popupMenuWithIcons); + FillSoundMenu( + state->menu.get(), + peer, + tr::lng_mute_box_no_notifications(), + tr::lng_mute_box_silent_notifications(), + [=](bool silent) { + state->noSoundChanges = silent; + }); + state->menu->popup(QCursor::pos()); + return; + }); + + const auto updateValueInSeconds = [=] { + state->valueInSeconds = 0 + + day->getLastText().toUInt() * 3600 * 24 + + hour->getLastText().toUInt() * 3600 + + minute->getLastText().toUInt() * 60; + }; + + using Field = Ui::MaskedInputField; + auto confirmText = rpl::merge( + base::qt_signal_producer(day.data(), &Field::changed), + base::qt_signal_producer(hour.data(), &Field::changed), + base::qt_signal_producer(minute.data(), &Field::changed), + state->noSoundChanges.value() | rpl::to_empty + ) | rpl::map([=] { + updateValueInSeconds(); + return !state->valueInSeconds + ? tr::lng_mute_menu_unmute() + : state->noSoundChanges.current() + ? tr::lng_mute_box_silent_notifications() + : tr::lng_mute_menu_mute(); + }) | rpl::flatten_latest(); + const auto confirm = box->addButton(std::move(confirmText), [=] { + peer->owner().updateNotifySettings( + peer, + state->valueInSeconds, + std::nullopt, + state->noSoundChanges.current()); + box->closeBox(); + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + } // namespace void FillMuteMenu( not_null menu, - not_null peer) { + Args args) { + const auto peer = args.peer; FillSoundMenu( menu, @@ -149,6 +306,11 @@ void FillMuteMenu( menu->addSeparator(); + menu->addAction( + tr::lng_mute_menu_duration(tr::now), + [=, show = args.show] { show->showBox(Box(MuteBox, peer)); }, + &st::menuIconMuteFor); + menu->addAction( base::make_unique_q(menu, menu->st().menu, peer)); } @@ -156,7 +318,7 @@ void FillMuteMenu( void SetupMuteMenu( not_null parent, rpl::producer<> triggers, - not_null peer) { + Args args) { struct State { base::unique_qptr menu; }; @@ -170,7 +332,7 @@ void SetupMuteMenu( state->menu = base::make_unique_q( parent, st::popupMenuWithIcons); - FillMuteMenu(state->menu.get(), peer); + FillMuteMenu(state->menu.get(), args); state->menu->popup(QCursor::pos()); }, parent->lifetime()); } diff --git a/Telegram/SourceFiles/menu/menu_mute.h b/Telegram/SourceFiles/menu/menu_mute.h index e0442e3960..a89ea64d61 100644 --- a/Telegram/SourceFiles/menu/menu_mute.h +++ b/Telegram/SourceFiles/menu/menu_mute.h @@ -12,17 +12,23 @@ class PeerData; namespace Ui { class PopupMenu; class RpWidget; +class Show; } // namespace Ui namespace MuteMenu { +struct Args { + not_null peer; + std::shared_ptr show; +}; + void FillMuteMenu( not_null menu, - not_null peer); + Args args); void SetupMuteMenu( not_null parent, rpl::producer<> triggers, - not_null peer); + Args args); } // namespace MuteMenu diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index db16b99e71..b3294d0a85 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -93,6 +93,7 @@ menuIconFake: icon {{ "menu/fake", menuIconColor }}; menuIconPersonal: icon {{ "menu/personal", menuIconColor }}; menuIconPorn: icon {{ "menu/porn", menuIconColor }}; menuIconViolence: icon {{ "menu/violence", menuIconColor }}; +menuIconMuteFor: icon {{ "menu/mute_for", menuIconColor }}; menuIconSilent: icon {{ "menu/silent", menuIconColor }}; mediaMenuIconStickers: icon {{ "menu/stickers", mediaviewMenuFg }};