diff --git a/Telegram/SourceFiles/data/business/data_business_common.h b/Telegram/SourceFiles/data/business/data_business_common.h index a74b5df48..914d62503 100644 --- a/Telegram/SourceFiles/data/business/data_business_common.h +++ b/Telegram/SourceFiles/data/business/data_business_common.h @@ -202,4 +202,18 @@ struct AwaySettings { const AwaySettings &b) = default; }; +struct GreetingSettings { + BusinessRecipients recipients; + int noActivityDays = 0; + int shortcutId = 0; + + explicit operator bool() const { + return noActivityDays > 0; + } + + friend inline bool operator==( + const GreetingSettings &a, + const GreetingSettings &b) = default; +}; + } // namespace Data diff --git a/Telegram/SourceFiles/data/business/data_business_info.cpp b/Telegram/SourceFiles/data/business/data_business_info.cpp index d054d3a56..1a1d30555 100644 --- a/Telegram/SourceFiles/data/business/data_business_info.cpp +++ b/Telegram/SourceFiles/data/business/data_business_info.cpp @@ -74,6 +74,20 @@ template | ranges::views::transform(&UserData::inputUser))); } +[[nodiscard]] MTPInputBusinessGreetingMessage ToMTP( + const GreetingSettings &data) { + using Flag = MTPDinputBusinessGreetingMessage::Flag; + return MTP_inputBusinessGreetingMessage( + MTP_flags(RecipientsFlags(data.recipients, Flag())), + MTP_int(data.shortcutId), + MTP_vector_from_range( + (data.recipients.allButExcluded + ? data.recipients.excluded + : data.recipients.included).list + | ranges::views::transform(&UserData::inputUser)), + MTP_int(data.noActivityDays)); +} + } // namespace BusinessInfo::BusinessInfo(not_null owner) @@ -132,6 +146,40 @@ rpl::producer<> BusinessInfo::awaySettingsChanged() const { return _awaySettingsChanged.events(); } +void BusinessInfo::applyGreetingSettings(GreetingSettings data) { + if (_greetingSettings == data) { + return; + } + _greetingSettings = data; + _greetingSettingsChanged.fire({}); +} + +void BusinessInfo::saveGreetingSettings(GreetingSettings data) { + if (_greetingSettings == data) { + return; + } + using Flag = MTPaccount_UpdateBusinessGreetingMessage::Flag; + _owner->session().api().request(MTPaccount_UpdateBusinessGreetingMessage( + MTP_flags(data ? Flag::f_message : Flag()), + data ? ToMTP(data) : MTPInputBusinessGreetingMessage() + )).send(); + + _greetingSettings = std::move(data); + _greetingSettingsChanged.fire({}); +} + +bool BusinessInfo::greetingSettingsLoaded() const { + return _greetingSettings.has_value(); +} + +GreetingSettings BusinessInfo::greetingSettings() const { + return _greetingSettings.value_or(GreetingSettings()); +} + +rpl::producer<> BusinessInfo::greetingSettingsChanged() const { + return _greetingSettingsChanged.events(); +} + void BusinessInfo::preload() { preloadTimezones(); } diff --git a/Telegram/SourceFiles/data/business/data_business_info.h b/Telegram/SourceFiles/data/business/data_business_info.h index 9cd7f9681..e572d2757 100644 --- a/Telegram/SourceFiles/data/business/data_business_info.h +++ b/Telegram/SourceFiles/data/business/data_business_info.h @@ -28,6 +28,12 @@ public: [[nodiscard]] bool awaySettingsLoaded() const; [[nodiscard]] rpl::producer<> awaySettingsChanged() const; + void saveGreetingSettings(GreetingSettings data); + void applyGreetingSettings(GreetingSettings data); + [[nodiscard]] GreetingSettings greetingSettings() const; + [[nodiscard]] bool greetingSettingsLoaded() const; + [[nodiscard]] rpl::producer<> greetingSettingsChanged() const; + void preloadTimezones(); [[nodiscard]] rpl::producer timezonesValue() const; @@ -35,9 +41,13 @@ private: const not_null _owner; rpl::variable _timezones; + std::optional _awaySettings; rpl::event_stream<> _awaySettingsChanged; + std::optional _greetingSettings; + rpl::event_stream<> _greetingSettingsChanged; + mtpRequestId _timezonesRequestId = 0; int32 _timezonesHash = 0; diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 93346ec90..35ad558c7 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -112,6 +112,20 @@ Data::BusinessRecipients RecipientsFromMTP( return result; } +[[nodiscard]] Data::GreetingSettings FromMTP( + not_null owner, + const tl::conditional &message) { + if (!message) { + return Data::GreetingSettings(); + } + const auto &data = message->data(); + return Data::GreetingSettings{ + .recipients = RecipientsFromMTP(owner, data), + .noActivityDays = data.vno_activity_days().v, + .shortcutId = data.vshortcut_id().v, + }; +} + } // namespace BotInfo::BotInfo() = default; @@ -678,6 +692,8 @@ void ApplyUserUpdate(not_null user, const MTPDuserFull &update) { if (user->isSelf()) { user->owner().businessInfo().applyAwaySettings( FromMTP(&user->owner(), update.vbusiness_away_message())); + user->owner().businessInfo().applyGreetingSettings( + FromMTP(&user->owner(), update.vbusiness_greeting_message())); } user->owner().stories().apply(user, update.vstories()); diff --git a/Telegram/SourceFiles/settings/business/settings_greeting.cpp b/Telegram/SourceFiles/settings/business/settings_greeting.cpp index 599b25b2c..39520846f 100644 --- a/Telegram/SourceFiles/settings/business/settings_greeting.cpp +++ b/Telegram/SourceFiles/settings/business/settings_greeting.cpp @@ -7,22 +7,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/business/settings_greeting.h" +#include "base/event_filter.h" #include "core/application.h" +#include "data/business/data_business_info.h" #include "data/data_session.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "settings/business/settings_recipients_helper.h" +#include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" +#include "ui/widgets/box_content_divider.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/vertical_drum_picker.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "ui/vertical_list.h" #include "window/window_session_controller.h" +#include "styles/style_layers.h" #include "styles/style_settings.h" namespace Settings { namespace { +constexpr auto kDefaultNoActivityDays = 7; + class Greeting : public BusinessSection { public: Greeting( @@ -32,21 +40,129 @@ public: [[nodiscard]] rpl::producer title() override; + const Ui::RoundRect *bottomSkipRounding() const { + return &_bottomSkipRounding; + } + private: void setupContent(not_null controller); void save(); + Ui::RoundRect _bottomSkipRounding; + rpl::variable _recipients; + rpl::variable _noActivityDays; + rpl::variable _enabled; }; Greeting::Greeting( QWidget *parent, not_null controller) -: BusinessSection(parent, controller) { +: BusinessSection(parent, controller) +, _bottomSkipRounding(st::boxRadius, st::boxDividerBg) { setupContent(controller); } +void EditPeriodBox( + not_null box, + int days, + Fn save) { + auto values = base::flat_set{ 7, 14, 21, 28 }; + if (!values.contains(days)) { + values.emplace(days); + } + const auto startIndex = int(values.find(days) - begin(values)); + + const auto content = box->addRow(object_ptr( + box, + st::settingsWorkingHoursPicker)); + + const auto font = st::boxTextFont; + const auto itemHeight = st::settingsWorkingHoursPickerItemHeight; + auto paintCallback = [=]( + QPainter &p, + int index, + float64 y, + float64 distanceFromCenter, + int outerWidth) { + const auto r = QRectF(0, y, outerWidth, itemHeight); + const auto progress = std::abs(distanceFromCenter); + const auto revProgress = 1. - progress; + p.save(); + p.translate(r.center()); + constexpr auto kMinYScale = 0.2; + const auto yScale = kMinYScale + + (1. - kMinYScale) * anim::easeOutCubic(1., revProgress); + p.scale(1., yScale); + p.translate(-r.center()); + p.setOpacity(revProgress); + p.setFont(font); + p.setPen(st::defaultFlatLabel.textFg); + p.drawText( + r, + tr::lng_days(tr::now, lt_count, *(values.begin() + index)), + style::al_center); + p.restore(); + }; + + const auto picker = Ui::CreateChild( + content, + std::move(paintCallback), + int(values.size()), + itemHeight, + startIndex); + + content->sizeValue( + ) | rpl::start_with_next([=](const QSize &s) { + picker->resize(s.width(), s.height()); + picker->moveToLeft((s.width() - picker->width()) / 2, 0); + }, content->lifetime()); + + content->paintRequest( + ) | rpl::start_with_next([=](const QRect &r) { + auto p = QPainter(content); + + p.fillRect(r, Qt::transparent); + + const auto lineRect = QRect( + 0, + content->height() / 2, + content->width(), + st::defaultInputField.borderActive); + p.fillRect(lineRect.translated(0, itemHeight / 2), st::activeLineFg); + p.fillRect(lineRect.translated(0, -itemHeight / 2), st::activeLineFg); + }, content->lifetime()); + + base::install_event_filter(content, [=](not_null e) { + if ((e->type() == QEvent::MouseButtonPress) + || (e->type() == QEvent::MouseButtonRelease) + || (e->type() == QEvent::MouseMove)) { + picker->handleMouseEvent(static_cast(e.get())); + } else if (e->type() == QEvent::Wheel) { + picker->handleWheelEvent(static_cast(e.get())); + } + return base::EventFilterResult::Continue; + }); + base::install_event_filter(box, [=](not_null e) { + if (e->type() == QEvent::KeyPress) { + picker->handleKeyEvent(static_cast(e.get())); + } + return base::EventFilterResult::Continue; + }); + + box->addButton(tr::lng_settings_save(), [=] { + const auto weak = Ui::MakeWeak(box); + save(*(begin(values) + picker->index())); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }); + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); +} + Greeting::~Greeting() { if (!Core::Quitting()) { save(); @@ -62,9 +178,14 @@ void Greeting::setupContent( using namespace rpl::mappers; const auto content = Ui::CreateChild(this); - //const auto current = controller->session().data().chatbots().current(); + const auto info = &controller->session().data().businessInfo(); + const auto current = info->greetingSettings(); + const auto disabled = !current.noActivityDays; - //_recipients = current.recipients; + _recipients = current.recipients; + _noActivityDays = disabled + ? kDefaultNoActivityDays + : current.noActivityDays; AddDividerTextWithLottie(content, { .lottie = u"greeting"_q, @@ -80,7 +201,27 @@ void Greeting::setupContent( content, tr::lng_greeting_enable(), st::settingsButtonNoIcon - ))->toggleOn(rpl::single(false)); + ))->toggleOn(rpl::single(!disabled)); + + _enabled = enabled->toggledValue(); + + Ui::AddSkip(content); + + content->add( + object_ptr>( + content, + object_ptr( + content, + st::boxDividerHeight, + st::boxDividerBg, + RectPart::Top)) + )->setDuration(0)->toggleOn(enabled->toggledValue() | rpl::map(!_1)); + content->add( + object_ptr>( + content, + object_ptr( + content)) + )->setDuration(0)->toggleOn(enabled->toggledValue()); const auto wrap = content->add( object_ptr>( @@ -88,24 +229,50 @@ void Greeting::setupContent( object_ptr(content))); const auto inner = wrap->entity(); - Ui::AddSkip(inner); - Ui::AddDivider(inner); - - wrap->toggleOn(enabled->toggledValue()); - wrap->finishAnimating(); - AddBusinessRecipientsSelector(inner, { .controller = controller, .title = tr::lng_greeting_recipients(), .data = &_recipients, }); - Ui::AddSkip(inner, st::settingsChatbotsAccessSkip); + Ui::AddSkip(inner); + Ui::AddDivider(inner); + Ui::AddSkip(inner); + + AddButtonWithLabel( + inner, + tr::lng_greeting_period_title(), + _noActivityDays.value( + ) | rpl::map( + [](int days) { return tr::lng_days(tr::now, lt_count, days); } + ), + st::settingsButtonNoIcon + )->setClickedCallback([=] { + controller->show(Box( + EditPeriodBox, + _noActivityDays.current(), + [=](int days) { _noActivityDays = days; })); + }); + + Ui::AddSkip(inner); + Ui::AddDividerText( + inner, + tr::lng_greeting_period_about(), + st::settingsChatbotsBottomTextMargin, + RectPart::Top); + + wrap->toggleOn(enabled->toggledValue()); + wrap->finishAnimating(); Ui::ResizeFitChild(this, content); } void Greeting::save() { + controller()->session().data().businessInfo().saveGreetingSettings( + _enabled.current() ? Data::GreetingSettings{ + .recipients = _recipients.current(), + .noActivityDays = _noActivityDays.current(), + } : Data::GreetingSettings()); } } // namespace