mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Support edit / save of away message settings.
This commit is contained in:
parent
f85c3c88f7
commit
e6b9ac2267
6 changed files with 378 additions and 13 deletions
|
@ -172,4 +172,34 @@ struct BusinessDetails {
|
|||
const BusinessDetails &b) = default;
|
||||
};
|
||||
|
||||
enum class AwayScheduleType : uchar {
|
||||
Never = 0,
|
||||
Always = 1,
|
||||
OutsideWorkingHours = 2,
|
||||
Custom = 3,
|
||||
};
|
||||
|
||||
struct AwaySchedule {
|
||||
AwayScheduleType type = AwayScheduleType::Always;
|
||||
WorkingInterval customInterval;
|
||||
|
||||
friend inline bool operator==(
|
||||
const AwaySchedule &a,
|
||||
const AwaySchedule &b) = default;
|
||||
};
|
||||
|
||||
struct AwaySettings {
|
||||
BusinessRecipients recipients;
|
||||
AwaySchedule schedule;
|
||||
int shortcutId = 0;
|
||||
|
||||
explicit operator bool() const {
|
||||
return schedule.type != AwayScheduleType::Never;
|
||||
}
|
||||
|
||||
friend inline bool operator==(
|
||||
const AwaySettings &a,
|
||||
const AwaySettings &b) = default;
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -29,6 +29,51 @@ namespace {
|
|||
MTP_vector_from_range(list | ranges::views::transform(proj)));
|
||||
}
|
||||
|
||||
template <typename Flag>
|
||||
[[nodiscard]] auto RecipientsFlags(
|
||||
const BusinessRecipients &data,
|
||||
Flag) {
|
||||
using Type = BusinessChatType;
|
||||
const auto &chats = data.allButExcluded
|
||||
? data.excluded
|
||||
: data.included;
|
||||
return Flag()
|
||||
| ((chats.types & Type::NewChats) ? Flag::f_new_chats : Flag())
|
||||
| ((chats.types & Type::ExistingChats)
|
||||
? Flag::f_existing_chats
|
||||
: Flag())
|
||||
| ((chats.types & Type::Contacts) ? Flag::f_contacts : Flag())
|
||||
| ((chats.types & Type::NonContacts) ? Flag::f_non_contacts : Flag())
|
||||
| (chats.list.empty() ? Flag() : Flag::f_users)
|
||||
| (data.allButExcluded ? Flag::f_exclude_selected : Flag());
|
||||
}
|
||||
|
||||
[[nodiscard]] MTPBusinessAwayMessageSchedule ToMTP(
|
||||
const AwaySchedule &data) {
|
||||
Expects(data.type != AwayScheduleType::Never);
|
||||
|
||||
return (data.type == AwayScheduleType::Always)
|
||||
? MTP_businessAwayMessageScheduleAlways()
|
||||
: (data.type == AwayScheduleType::OutsideWorkingHours)
|
||||
? MTP_businessAwayMessageScheduleOutsideWorkHours()
|
||||
: MTP_businessAwayMessageScheduleCustom(
|
||||
MTP_int(data.customInterval.start),
|
||||
MTP_int(data.customInterval.end));
|
||||
}
|
||||
|
||||
[[nodiscard]] MTPInputBusinessAwayMessage ToMTP(const AwaySettings &data) {
|
||||
using Flag = MTPDinputBusinessAwayMessage::Flag;
|
||||
return MTP_inputBusinessAwayMessage(
|
||||
MTP_flags(RecipientsFlags(data.recipients, Flag())),
|
||||
MTP_int(data.shortcutId),
|
||||
ToMTP(data.schedule),
|
||||
MTP_vector_from_range(
|
||||
(data.recipients.allButExcluded
|
||||
? data.recipients.excluded
|
||||
: data.recipients.included).list
|
||||
| ranges::views::transform(&UserData::inputUser)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BusinessInfo::BusinessInfo(not_null<Session*> owner)
|
||||
|
@ -42,17 +87,51 @@ void BusinessInfo::saveWorkingHours(WorkingHours data) {
|
|||
if (details.hours == data) {
|
||||
return;
|
||||
}
|
||||
details.hours = std::move(data);
|
||||
|
||||
using Flag = MTPaccount_UpdateBusinessWorkHours::Flag;
|
||||
_owner->session().api().request(MTPaccount_UpdateBusinessWorkHours(
|
||||
MTP_flags(details.hours ? Flag::f_business_work_hours : Flag()),
|
||||
ToMTP(details.hours)
|
||||
MTP_flags(data ? Flag::f_business_work_hours : Flag()),
|
||||
ToMTP(data)
|
||||
)).send();
|
||||
|
||||
details.hours = std::move(data);
|
||||
_owner->session().user()->setBusinessDetails(std::move(details));
|
||||
}
|
||||
|
||||
void BusinessInfo::applyAwaySettings(AwaySettings data) {
|
||||
if (_awaySettings == data) {
|
||||
return;
|
||||
}
|
||||
_awaySettings = data;
|
||||
_awaySettingsChanged.fire({});
|
||||
}
|
||||
|
||||
void BusinessInfo::saveAwaySettings(AwaySettings data) {
|
||||
if (_awaySettings == data) {
|
||||
return;
|
||||
}
|
||||
using Flag = MTPaccount_UpdateBusinessAwayMessage::Flag;
|
||||
_owner->session().api().request(MTPaccount_UpdateBusinessAwayMessage(
|
||||
MTP_flags(data ? Flag::f_message : Flag()),
|
||||
data ? ToMTP(data) : MTPInputBusinessAwayMessage()
|
||||
)).send();
|
||||
|
||||
_awaySettings = std::move(data);
|
||||
_awaySettingsChanged.fire({});
|
||||
}
|
||||
|
||||
bool BusinessInfo::awaySettingsLoaded() const {
|
||||
return _awaySettings.has_value();
|
||||
}
|
||||
|
||||
AwaySettings BusinessInfo::awaySettings() const {
|
||||
return _awaySettings.value_or(AwaySettings());
|
||||
}
|
||||
|
||||
rpl::producer<> BusinessInfo::awaySettingsChanged() const {
|
||||
return _awaySettingsChanged.events();
|
||||
}
|
||||
|
||||
void BusinessInfo::preload() {
|
||||
preloadTimezones();
|
||||
}
|
||||
|
|
|
@ -18,9 +18,16 @@ public:
|
|||
explicit BusinessInfo(not_null<Session*> owner);
|
||||
~BusinessInfo();
|
||||
|
||||
void preload();
|
||||
|
||||
void saveWorkingHours(WorkingHours data);
|
||||
|
||||
void preload();
|
||||
void saveAwaySettings(AwaySettings data);
|
||||
void applyAwaySettings(AwaySettings data);
|
||||
[[nodiscard]] AwaySettings awaySettings() const;
|
||||
[[nodiscard]] bool awaySettingsLoaded() const;
|
||||
[[nodiscard]] rpl::producer<> awaySettingsChanged() const;
|
||||
|
||||
void preloadTimezones();
|
||||
[[nodiscard]] rpl::producer<Timezones> timezonesValue() const;
|
||||
|
||||
|
@ -28,6 +35,8 @@ private:
|
|||
const not_null<Session*> _owner;
|
||||
|
||||
rpl::variable<Timezones> _timezones;
|
||||
std::optional<AwaySettings> _awaySettings;
|
||||
rpl::event_stream<> _awaySettingsChanged;
|
||||
|
||||
mtpRequestId _timezonesRequestId = 0;
|
||||
int32 _timezonesHash = 0;
|
||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "storage/storage_user_photos.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/business/data_business_common.h"
|
||||
#include "data/business/data_business_info.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_peer_bot_command.h"
|
||||
|
@ -51,14 +52,66 @@ using UpdateFlag = Data::PeerUpdate::Flag;
|
|||
if (location) {
|
||||
const auto &data = location->data();
|
||||
result.location.address = qs(data.vaddress());
|
||||
data.vgeo_point().match([&](const MTPDgeoPoint &data) {
|
||||
result.location.point = Data::LocationPoint(data);
|
||||
}, [&](const MTPDgeoPointEmpty &) {
|
||||
});
|
||||
if (const auto point = data.vgeo_point()) {
|
||||
point->match([&](const MTPDgeoPoint &data) {
|
||||
result.location.point = Data::LocationPoint(data);
|
||||
}, [&](const MTPDgeoPointEmpty &) {
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Data::BusinessRecipients RecipientsFromMTP(
|
||||
not_null<Data::Session*> owner,
|
||||
const T &data) {
|
||||
using Type = Data::BusinessChatType;
|
||||
auto result = Data::BusinessRecipients{
|
||||
.allButExcluded = data.is_exclude_selected(),
|
||||
};
|
||||
auto &chats = result.allButExcluded
|
||||
? result.excluded
|
||||
: result.included;
|
||||
chats.types = Type()
|
||||
| (data.is_new_chats() ? Type::NewChats : Type())
|
||||
| (data.is_existing_chats() ? Type::ExistingChats : Type())
|
||||
| (data.is_contacts() ? Type::Contacts : Type())
|
||||
| (data.is_non_contacts() ? Type::NonContacts : Type());
|
||||
if (const auto users = data.vusers()) {
|
||||
for (const auto &userId : users->v) {
|
||||
chats.list.push_back(owner->user(UserId(userId.v)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::AwaySettings FromMTP(
|
||||
not_null<Data::Session*> owner,
|
||||
const tl::conditional<MTPBusinessAwayMessage> &message) {
|
||||
if (!message) {
|
||||
return Data::AwaySettings();
|
||||
}
|
||||
const auto &data = message->data();
|
||||
auto result = Data::AwaySettings{
|
||||
.recipients = RecipientsFromMTP(owner, data),
|
||||
.shortcutId = data.vshortcut_id().v,
|
||||
};
|
||||
data.vschedule().match([&](
|
||||
const MTPDbusinessAwayMessageScheduleAlways &) {
|
||||
result.schedule.type = Data::AwayScheduleType::Always;
|
||||
}, [&](const MTPDbusinessAwayMessageScheduleOutsideWorkHours &) {
|
||||
result.schedule.type = Data::AwayScheduleType::OutsideWorkingHours;
|
||||
}, [&](const MTPDbusinessAwayMessageScheduleCustom &data) {
|
||||
result.schedule.type = Data::AwayScheduleType::Custom;
|
||||
result.schedule.customInterval = Data::WorkingInterval{
|
||||
data.vstart_date().v,
|
||||
data.vend_date().v,
|
||||
};
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BotInfo::BotInfo() = default;
|
||||
|
@ -622,6 +675,10 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
|||
user->setBusinessDetails(FromMTP(
|
||||
update.vbusiness_work_hours(),
|
||||
update.vbusiness_location()));
|
||||
if (user->isSelf()) {
|
||||
user->owner().businessInfo().applyAwaySettings(
|
||||
FromMTP(&user->owner(), update.vbusiness_away_message()));
|
||||
}
|
||||
|
||||
user->owner().stories().apply(user, update.vstories());
|
||||
|
||||
|
|
|
@ -7,17 +7,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "settings/business/settings_away_message.h"
|
||||
|
||||
#include "base/unixtime.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/boxes/choose_date_time.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.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 {
|
||||
|
@ -37,9 +42,144 @@ private:
|
|||
void save();
|
||||
|
||||
rpl::variable<Data::BusinessRecipients> _recipients;
|
||||
rpl::variable<Data::AwaySchedule> _schedule;
|
||||
rpl::variable<bool> _enabled;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] TimeId StartTimeMin() {
|
||||
// Telegram was launched in August 2013 :)
|
||||
return base::unixtime::serialize(QDateTime(QDate(2013, 8, 1)));
|
||||
}
|
||||
|
||||
[[nodiscard]] TimeId EndTimeMin() {
|
||||
return StartTimeMin() + 3600;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool BadCustomInterval(const Data::WorkingInterval &interval) {
|
||||
return !interval
|
||||
|| (interval.start < StartTimeMin())
|
||||
|| (interval.end < EndTimeMin());
|
||||
}
|
||||
|
||||
struct AwayScheduleSelectorDescriptor {
|
||||
not_null<Window::SessionController*> controller;
|
||||
not_null<rpl::variable<Data::AwaySchedule>*> data;
|
||||
};
|
||||
void AddAwayScheduleSelector(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
AwayScheduleSelectorDescriptor &&descriptor) {
|
||||
using Type = Data::AwayScheduleType;
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto controller = descriptor.controller;
|
||||
const auto data = descriptor.data;
|
||||
|
||||
Ui::AddSubsectionTitle(container, tr::lng_away_schedule());
|
||||
const auto group = std::make_shared<Ui::RadioenumGroup<Type>>(
|
||||
data->current().type);
|
||||
|
||||
const auto add = [&](Type type, const QString &label) {
|
||||
container->add(
|
||||
object_ptr<Ui::Radioenum<Type>>(
|
||||
container,
|
||||
group,
|
||||
type,
|
||||
label),
|
||||
st::boxRowPadding + st::settingsAwaySchedulePadding);
|
||||
};
|
||||
add(Type::Always, tr::lng_away_schedule_always(tr::now));
|
||||
add(Type::OutsideWorkingHours, tr::lng_away_schedule_outside(tr::now));
|
||||
add(Type::Custom, tr::lng_away_schedule_custom(tr::now));
|
||||
|
||||
const auto customWrap = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(container)));
|
||||
const auto customInner = customWrap->entity();
|
||||
customWrap->toggleOn(group->value() | rpl::map(_1 == Type::Custom));
|
||||
|
||||
group->changes() | rpl::start_with_next([=](Type value) {
|
||||
auto copy = data->current();
|
||||
copy.type = value;
|
||||
*data = copy;
|
||||
}, customWrap->lifetime());
|
||||
|
||||
const auto chooseDate = [=](
|
||||
rpl::producer<QString> title,
|
||||
TimeId now,
|
||||
Fn<TimeId()> min,
|
||||
Fn<TimeId()> max,
|
||||
Fn<void(TimeId)> done) {
|
||||
using namespace Ui;
|
||||
const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||
const auto save = [=](TimeId time) {
|
||||
done(time);
|
||||
if (const auto strong = box->data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
};
|
||||
*box = controller->show(Box(ChooseDateTimeBox, ChooseDateTimeBoxArgs{
|
||||
.title = std::move(title),
|
||||
.submit = tr::lng_settings_save(),
|
||||
.done = save,
|
||||
.min = min,
|
||||
.time = now,
|
||||
.max = max,
|
||||
}));
|
||||
};
|
||||
|
||||
Ui::AddSkip(customInner);
|
||||
Ui::AddDivider(customInner);
|
||||
Ui::AddSkip(customInner);
|
||||
|
||||
auto startLabel = data->value(
|
||||
) | rpl::map([=](const Data::AwaySchedule &value) {
|
||||
return langDateTime(
|
||||
base::unixtime::parse(value.customInterval.start));
|
||||
});
|
||||
AddButtonWithLabel(
|
||||
customInner,
|
||||
tr::lng_away_custom_start(),
|
||||
std::move(startLabel),
|
||||
st::settingsButtonNoIcon
|
||||
)->setClickedCallback([=] {
|
||||
chooseDate(
|
||||
tr::lng_away_custom_start(),
|
||||
data->current().customInterval.start,
|
||||
StartTimeMin,
|
||||
[=] { return data->current().customInterval.end - 1; },
|
||||
[=](TimeId time) {
|
||||
auto copy = data->current();
|
||||
copy.customInterval.start = time;
|
||||
*data = copy;
|
||||
});
|
||||
});
|
||||
|
||||
auto endLabel = data->value(
|
||||
) | rpl::map([=](const Data::AwaySchedule &value) {
|
||||
return langDateTime(
|
||||
base::unixtime::parse(value.customInterval.end));
|
||||
});
|
||||
AddButtonWithLabel(
|
||||
customInner,
|
||||
tr::lng_away_custom_end(),
|
||||
std::move(endLabel),
|
||||
st::settingsButtonNoIcon
|
||||
)->setClickedCallback([=] {
|
||||
chooseDate(
|
||||
tr::lng_away_custom_end(),
|
||||
data->current().customInterval.end,
|
||||
[=] { return data->current().customInterval.start + 1; },
|
||||
nullptr,
|
||||
[=](TimeId time) {
|
||||
auto copy = data->current();
|
||||
copy.customInterval.end = time;
|
||||
*data = copy;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
AwayMessage::AwayMessage(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller)
|
||||
|
@ -59,12 +199,27 @@ rpl::producer<QString> AwayMessage::title() {
|
|||
|
||||
void AwayMessage::setupContent(
|
||||
not_null<Window::SessionController*> controller) {
|
||||
using namespace Data;
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
//const auto current = controller->session().data().chatbots().current();
|
||||
const auto info = &controller->session().data().businessInfo();
|
||||
const auto current = info->awaySettings();
|
||||
const auto disabled = (current.schedule.type == AwayScheduleType::Never);
|
||||
|
||||
_recipients = current.recipients;
|
||||
auto initialSchedule = disabled ? AwaySchedule{
|
||||
.type = AwayScheduleType::Always,
|
||||
} : current.schedule;
|
||||
if (BadCustomInterval(initialSchedule.customInterval)) {
|
||||
const auto now = base::unixtime::now();
|
||||
initialSchedule.customInterval = WorkingInterval{
|
||||
.start = now,
|
||||
.end = now + 24 * 60 * 60,
|
||||
};
|
||||
}
|
||||
_schedule = initialSchedule;
|
||||
|
||||
//_recipients = current.recipients;
|
||||
AddDividerTextWithLottie(content, {
|
||||
.lottie = u"sleep"_q,
|
||||
.lottieSize = st::settingsCloudPasswordIconSize,
|
||||
|
@ -79,7 +234,8 @@ void AwayMessage::setupContent(
|
|||
content,
|
||||
tr::lng_away_enable(),
|
||||
st::settingsButtonNoIcon
|
||||
))->toggleOn(rpl::single(false));
|
||||
))->toggleOn(rpl::single(!disabled));
|
||||
_enabled = enabled->toggledValue();
|
||||
|
||||
const auto wrap = content->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
|
@ -90,8 +246,32 @@ void AwayMessage::setupContent(
|
|||
Ui::AddSkip(inner);
|
||||
Ui::AddDivider(inner);
|
||||
|
||||
wrap->toggleOn(enabled->toggledValue());
|
||||
wrap->finishAnimating();
|
||||
const auto createWrap = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
const auto createInner = createWrap->entity();
|
||||
Ui::AddSkip(createInner);
|
||||
const auto create = createInner->add(object_ptr<Ui::SettingsButton>(
|
||||
createInner,
|
||||
tr::lng_away_create(),
|
||||
st::settingsButtonLightNoIcon
|
||||
));
|
||||
create->setClickedCallback([=] {
|
||||
|
||||
});
|
||||
Ui::AddSkip(createInner);
|
||||
Ui::AddDivider(createInner);
|
||||
|
||||
createWrap->toggleOn(rpl::single(true));
|
||||
|
||||
Ui::AddSkip(inner);
|
||||
AddAwayScheduleSelector(inner, {
|
||||
.controller = controller,
|
||||
.data = &_schedule,
|
||||
});
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddDivider(inner);
|
||||
|
||||
AddBusinessRecipientsSelector(inner, {
|
||||
.controller = controller,
|
||||
|
@ -101,10 +281,18 @@ void AwayMessage::setupContent(
|
|||
|
||||
Ui::AddSkip(inner, st::settingsChatbotsAccessSkip);
|
||||
|
||||
wrap->toggleOn(enabled->toggledValue());
|
||||
wrap->finishAnimating();
|
||||
|
||||
Ui::ResizeFitChild(this, content);
|
||||
}
|
||||
|
||||
void AwayMessage::save() {
|
||||
controller()->session().data().businessInfo().saveAwaySettings(
|
||||
_enabled.current() ? Data::AwaySettings{
|
||||
.recipients = _recipients.current(),
|
||||
.schedule = _schedule.current(),
|
||||
} : Data::AwaySettings());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -614,3 +614,5 @@ settingsWorkingHoursWeek: SettingsButton(settingsButtonNoIcon) {
|
|||
settingsWorkingHoursDetails: settingsNotificationTypeDetails;
|
||||
settingsWorkingHoursPicker: 200px;
|
||||
settingsWorkingHoursPickerItemHeight: 40px;
|
||||
|
||||
settingsAwaySchedulePadding: margins(0px, 8px, 0px, 8px);
|
||||
|
|
Loading…
Add table
Reference in a new issue