mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Repeated scheduled messages (reminders).
This commit is contained in:
@@ -4137,6 +4137,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_reminder_message" = "Set a reminder";
|
||||
"lng_schedule_title" = "Send this message on...";
|
||||
"lng_remind_title" = "Remind me on...";
|
||||
"lng_schedule_repeat_label" = "Repeat:";
|
||||
"lng_schedule_repeat_never" = "Never";
|
||||
"lng_schedule_repeat_daily" = "Daily";
|
||||
"lng_schedule_repeat_weekly" = "Weekly";
|
||||
"lng_schedule_repeat_monthly" = "Monthly";
|
||||
"lng_schedule_repeat_every_month#one" = "Every {count} month";
|
||||
"lng_schedule_repeat_every_month#other" = "Every {count} month";
|
||||
"lng_schedule_repeat_yearly" = "Yearly";
|
||||
"lng_schedule_repeat_promo" = "Subscribe to {link} to schedule repeating messages.";
|
||||
"lng_schedule_repeat_promo_link" = "Telegram Premium";
|
||||
"lng_schedule_at" = "at";
|
||||
"lng_message_ph" = "Write a message...";
|
||||
"lng_broadcast_ph" = "Broadcast a message...";
|
||||
|
||||
@@ -4100,6 +4100,7 @@ void ApiWrap::sendMessage(
|
||||
.from = NewMessageFromId(action),
|
||||
.replyTo = action.replyTo,
|
||||
.date = NewMessageDate(action.options),
|
||||
.scheduleRepeatPeriod = action.options.scheduleRepeatPeriod,
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.starsPaid = starsPaid,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
|
||||
@@ -929,6 +929,14 @@ scheduleTimeSeparator: FlatLabel(defaultFlatLabel) {
|
||||
}
|
||||
}
|
||||
scheduleTimeSeparatorPadding: margins(2px, 0px, 2px, 0px);
|
||||
scheduleRepeatDropdownLock: IconEmoji {
|
||||
icon: icon {{ "emoji/premium_lock", windowActiveTextFg }};
|
||||
padding: margins(-2px, 1px, 0px, 0px);
|
||||
}
|
||||
scheduleRepeatDropdownArrow: IconEmoji {
|
||||
icon: icon {{ "intro_country_dropdown", windowActiveTextFg }};
|
||||
padding: margins(3px, 6px, 3px, 0px);
|
||||
}
|
||||
|
||||
muteBoxTimeField: InputField(scheduleDateField) {
|
||||
textMargins: margins(0px, 0px, 0px, 0px);
|
||||
|
||||
@@ -187,6 +187,7 @@ struct HistoryItem::CreateConfig {
|
||||
bool savedFromOutgoing = false;
|
||||
|
||||
TimeId editDate = 0;
|
||||
TimeId scheduleRepeatPeriod = 0;
|
||||
HistoryMessageMarkupData markup;
|
||||
HistoryMessageRepliesData replies;
|
||||
HistoryMessageSuggestInfo suggest;
|
||||
@@ -404,6 +405,7 @@ HistoryItem::HistoryItem(
|
||||
.flags = FlagsFromMTP(id, data.vflags().v, localFlags),
|
||||
.from = data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0),
|
||||
.date = data.vdate().v,
|
||||
.scheduleRepeatPeriod = data.vschedule_repeat_period().value_or_empty(),
|
||||
.shortcutId = data.vquick_reply_shortcut_id().value_or_empty(),
|
||||
.starsPaid = int(data.vpaid_message_stars().value_or_empty()),
|
||||
.effectId = data.veffect().value_or_empty(),
|
||||
@@ -1847,6 +1849,11 @@ bool HistoryItem::isScheduled() const {
|
||||
&& (_flags & MessageFlag::IsOrWasScheduled);
|
||||
}
|
||||
|
||||
TimeId HistoryItem::scheduleRepeatPeriod() const {
|
||||
const auto period = Get<HistoryMessageSchedulePeriod>();
|
||||
return period ? period->schedulePeriod : TimeId();
|
||||
}
|
||||
|
||||
bool HistoryItem::isSponsored() const {
|
||||
return _flags & MessageFlag::Sponsored;
|
||||
}
|
||||
@@ -2042,6 +2049,16 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) {
|
||||
}
|
||||
}
|
||||
|
||||
if (edition.repeatPeriod) {
|
||||
if (!Has<HistoryMessageSchedulePeriod>()) {
|
||||
AddComponents(HistoryMessageSchedulePeriod::Bit());
|
||||
}
|
||||
const auto period = Get<HistoryMessageSchedulePeriod>();
|
||||
period->schedulePeriod = edition.repeatPeriod;
|
||||
} else {
|
||||
RemoveComponents(HistoryMessageSchedulePeriod::Bit());
|
||||
}
|
||||
|
||||
applyTTL(edition.ttl);
|
||||
setFactcheck(FromMTP(this, edition.mtpFactcheck));
|
||||
|
||||
@@ -3813,7 +3830,7 @@ bool HistoryItem::isEmpty() const {
|
||||
}
|
||||
|
||||
Data::SavedSublist *HistoryItem::savedSublist() const {
|
||||
if (isBusinessShortcut()) {
|
||||
if (isBusinessShortcut() || isScheduled()) {
|
||||
return nullptr;
|
||||
} else if (const auto saved = Get<HistoryMessageSaved>()) {
|
||||
if (saved->savedMessagesSublist) {
|
||||
@@ -4039,8 +4056,12 @@ void HistoryItem::createComponents(CreateConfig &&config) {
|
||||
} else if (config.inlineMarkup) {
|
||||
mask |= HistoryMessageReplyMarkup::Bit();
|
||||
}
|
||||
if (config.scheduleRepeatPeriod) {
|
||||
mask |= HistoryMessageSchedulePeriod::Bit();
|
||||
}
|
||||
const auto requiresMonoforumPeer = _history->peer->amMonoforumAdmin();
|
||||
if (!isBusinessShortcut()
|
||||
&& !isScheduled()
|
||||
&& (_history->peer->isSelf()
|
||||
|| config.savedSublistPeer
|
||||
|| requiresMonoforumPeer)) {
|
||||
@@ -4088,6 +4109,9 @@ void HistoryItem::createComponents(CreateConfig &&config) {
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto period = Get<HistoryMessageSchedulePeriod>()) {
|
||||
period->schedulePeriod = config.scheduleRepeatPeriod;
|
||||
}
|
||||
if (const auto reply = Get<HistoryMessageReply>()) {
|
||||
reply->set(std::move(config.reply));
|
||||
reply->updateData(this);
|
||||
@@ -4305,6 +4329,7 @@ void HistoryItem::createComponentsHelper(HistoryItemCommonFields &&fields) {
|
||||
const auto &replyTo = fields.replyTo;
|
||||
auto config = CreateConfig();
|
||||
config.viaBotId = fields.viaBotId;
|
||||
config.scheduleRepeatPeriod = fields.scheduleRepeatPeriod;
|
||||
if (fields.flags & MessageFlag::HasReplyInfo) {
|
||||
config.reply.messageId = replyTo.messageId.msg;
|
||||
config.reply.storyId = replyTo.storyId.story;
|
||||
@@ -4482,6 +4507,8 @@ void HistoryItem::createComponents(const MTPDmessage &data) {
|
||||
: HistoryMessageRepliesData(data.vreplies());
|
||||
config.markup = HistoryMessageMarkupData(data.vreply_markup());
|
||||
config.editDate = data.vedit_date().value_or_empty();
|
||||
config.scheduleRepeatPeriod
|
||||
= data.vschedule_repeat_period().value_or_empty();
|
||||
config.postAuthor = qs(data.vpost_author().value_or_empty());
|
||||
config.restrictions = Data::UnavailableReason::Extract(
|
||||
data.vrestriction_reason());
|
||||
|
||||
@@ -81,6 +81,7 @@ struct HistoryItemCommonFields {
|
||||
PeerId from = 0;
|
||||
FullReplyTo replyTo;
|
||||
TimeId date = 0;
|
||||
TimeId scheduleRepeatPeriod = 0;
|
||||
BusinessShortcutId shortcutId = 0;
|
||||
int starsPaid = 0;
|
||||
UserId viaBotId = 0;
|
||||
@@ -193,6 +194,7 @@ public:
|
||||
[[nodiscard]] bool isAdminLogEntry() const;
|
||||
[[nodiscard]] bool isFromScheduled() const;
|
||||
[[nodiscard]] bool isScheduled() const;
|
||||
[[nodiscard]] TimeId scheduleRepeatPeriod() const;
|
||||
[[nodiscard]] bool isSponsored() const;
|
||||
[[nodiscard]] bool canLookupMessageAuthor() const;
|
||||
[[nodiscard]] bool skipNotification() const;
|
||||
|
||||
@@ -869,3 +869,8 @@ private:
|
||||
mutable int _seekingCurrent = 0;
|
||||
|
||||
};
|
||||
|
||||
struct HistoryMessageSchedulePeriod
|
||||
: RuntimeComponent<HistoryMessageSchedulePeriod, HistoryItem> {
|
||||
TimeId schedulePeriod = 0;
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ HistoryMessageEdition::HistoryMessageEdition(
|
||||
: suggest(HistoryMessageSuggestInfo(message.vsuggested_post())) {
|
||||
isEditHide = message.is_edit_hide();
|
||||
isMediaUnread = message.is_media_unread();
|
||||
repeatPeriod = message.vschedule_repeat_period().value_or_empty();
|
||||
editDate = message.vedit_date().value_or(-1);
|
||||
textWithEntities = TextWithEntities{
|
||||
qs(message.vmessage()),
|
||||
|
||||
@@ -21,7 +21,8 @@ struct HistoryMessageEdition {
|
||||
|
||||
bool isEditHide = false;
|
||||
bool isMediaUnread = false;
|
||||
int editDate = 0;
|
||||
TimeId repeatPeriod = 0;
|
||||
TimeId editDate = 0;
|
||||
int views = -1;
|
||||
int forwards = -1;
|
||||
int ttl = 0;
|
||||
|
||||
@@ -574,6 +574,7 @@ bool AddRescheduleAction(
|
||||
const auto date = (itemDate == Api::kScheduledUntilOnlineTimestamp)
|
||||
? HistoryView::DefaultScheduleTime()
|
||||
: itemDate + (firstItem->isScheduled() ? 0 : crl::time(600));
|
||||
const auto repeatPeriod = firstItem->scheduleRepeatPeriod();
|
||||
|
||||
const auto box = request.navigation->parentController()->show(
|
||||
HistoryView::PrepareScheduleBox(
|
||||
@@ -581,7 +582,7 @@ bool AddRescheduleAction(
|
||||
request.navigation->uiShow(),
|
||||
{ .type = sendMenuType, .effectAllowed = false },
|
||||
callback,
|
||||
{}, // initial options
|
||||
{ .scheduleRepeatPeriod = repeatPeriod },
|
||||
date));
|
||||
|
||||
owner->itemRemoved(
|
||||
|
||||
@@ -8,18 +8,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/history_view_schedule_box.h"
|
||||
|
||||
#include "api/api_common.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/qt/qt_key_modifiers.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "main/main_session.h"
|
||||
#include "menu/menu_send.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_chat.h"
|
||||
@@ -76,6 +81,9 @@ void ScheduleBox(
|
||||
Fn<void(Api::SendOptions)> done,
|
||||
TimeId time,
|
||||
ScheduleBoxStyleArgs style) {
|
||||
const auto repeat = (details.type == SendMenu::Type::Reminder)
|
||||
? std::make_shared<TimeId>(initialOptions.scheduleRepeatPeriod)
|
||||
: nullptr;
|
||||
const auto submit = [=](Api::SendOptions options) {
|
||||
if (!options.scheduled) {
|
||||
return;
|
||||
@@ -84,6 +92,9 @@ void ScheduleBox(
|
||||
if (base::IsCtrlPressed()) {
|
||||
options.silent = true;
|
||||
}
|
||||
if (repeat) {
|
||||
options.scheduleRepeatPeriod = *repeat;
|
||||
}
|
||||
const auto copy = done;
|
||||
box->closeBox();
|
||||
copy(options);
|
||||
@@ -103,6 +114,40 @@ void ScheduleBox(
|
||||
.style = style.chooseDateTimeArgs,
|
||||
});
|
||||
|
||||
if (repeat) {
|
||||
const auto showPremiumPromo = [=] {
|
||||
if (show->session().premium()) {
|
||||
return false;
|
||||
}
|
||||
Settings::ShowPremiumPromoToast(
|
||||
show,
|
||||
tr::lng_schedule_repeat_promo(
|
||||
tr::now,
|
||||
lt_link,
|
||||
Ui::Text::Link(
|
||||
Ui::Text::Bold(
|
||||
tr::lng_schedule_repeat_promo_link(tr::now))),
|
||||
Ui::Text::RichLangValue),
|
||||
u"schedule_repeat"_q);
|
||||
return true;
|
||||
};
|
||||
auto locked = Data::AmPremiumValue(
|
||||
&show->session()
|
||||
) | rpl::map([=](bool premium) {
|
||||
return !premium;
|
||||
});
|
||||
const auto row = box->addRow(Ui::ChooseRepeatPeriod(box, {
|
||||
.value = show->session().premium() ? *repeat : TimeId(),
|
||||
.locked = std::move(locked),
|
||||
.filter = showPremiumPromo,
|
||||
.changed = [=](TimeId value) { *repeat = value; },
|
||||
.test = show->session().isTestMode(),
|
||||
}), style::al_top);
|
||||
std::move(descriptor.width) | rpl::start_with_next([=](int width) {
|
||||
row->setNaturalWidth(width);
|
||||
}, row->lifetime());
|
||||
}
|
||||
|
||||
using namespace SendMenu;
|
||||
const auto childType = (details.type == Type::Disabled)
|
||||
? Type::Disabled
|
||||
|
||||
@@ -384,7 +384,9 @@ void ScheduledWidget::setupComposeControls() {
|
||||
if (const auto item = session().data().message(data.fullId)) {
|
||||
if (item->isScheduled()) {
|
||||
const auto spoiler = data.spoilered;
|
||||
edit(item, data.options, saveEditMsgRequestId, spoiler);
|
||||
auto &options = data.options;
|
||||
options.scheduleRepeatPeriod = item->scheduleRepeatPeriod();
|
||||
edit(item, options, saveEditMsgRequestId, spoiler);
|
||||
}
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
@@ -698,7 +698,7 @@ FillMenuResult FillSendMenu(
|
||||
}
|
||||
if (sending && type != Type::SilentOnly) {
|
||||
menu->addAction(
|
||||
(type == Type::Reminder
|
||||
((type == Type::Reminder)
|
||||
? tr::lng_reminder_message(tr::now)
|
||||
: tr::lng_schedule_message(tr::now)),
|
||||
[=] { action({ .type = ActionType::Schedule }, details); },
|
||||
|
||||
@@ -10,8 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/unixtime.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "ui/boxes/calendar_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/widgets/time_input.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "lang/lang_keys.h"
|
||||
@@ -56,6 +58,7 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||
ChooseDateTimeBoxArgs &&args) {
|
||||
struct State {
|
||||
rpl::variable<QDate> date;
|
||||
rpl::variable<int> width;
|
||||
not_null<InputField*> day;
|
||||
not_null<TimeInput*> time;
|
||||
not_null<FlatLabel*> at;
|
||||
@@ -64,7 +67,8 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||
box->setWidth(st::boxWideWidth);
|
||||
|
||||
const auto content = box->addRow(
|
||||
object_ptr<FixedHeightWidget>(box, st::scheduleHeight));
|
||||
object_ptr<FixedHeightWidget>(box, st::scheduleHeight),
|
||||
style::al_top);
|
||||
if (args.description) {
|
||||
box->addRow(object_ptr<FlatLabel>(
|
||||
box,
|
||||
@@ -125,6 +129,16 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
state->at->widthValue() | rpl::start_with_next([=](int width) {
|
||||
const auto full = st::scheduleDateWidth
|
||||
+ st::scheduleAtSkip
|
||||
+ width
|
||||
+ st::scheduleAtSkip
|
||||
+ st::scheduleTimeWidth;
|
||||
content->setNaturalWidth(full);
|
||||
state->width = full;
|
||||
}, state->at->lifetime());
|
||||
|
||||
content->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
const auto paddings = width
|
||||
@@ -200,6 +214,7 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||
|
||||
auto result = ChooseDateTimeBoxDescriptor();
|
||||
box->setFocusCallback([=] { state->time->setFocusFast(); });
|
||||
result.width = state->width.value();
|
||||
result.submit = box->addButton(std::move(args.submit), save);
|
||||
result.collect = [=] {
|
||||
if (const auto result = collect()) {
|
||||
@@ -217,4 +232,109 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||
return result;
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> ChooseRepeatPeriod(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
ChooseRepeatPeriodArgs &&args) {
|
||||
auto result = object_ptr<Ui::RpWidget>(parent.get());
|
||||
const auto raw = result.data();
|
||||
|
||||
struct Entry {
|
||||
TimeId value = 0;
|
||||
QString text;
|
||||
};
|
||||
auto map = std::vector<Entry>{
|
||||
{ 0, tr::lng_schedule_repeat_never(tr::now) },
|
||||
{ 24 * 60 * 60, tr::lng_schedule_repeat_daily(tr::now) },
|
||||
{ 7 * 24 * 60 * 60, tr::lng_schedule_repeat_weekly(tr::now) },
|
||||
{ 30 * 24 * 60 * 60, tr::lng_schedule_repeat_monthly(tr::now) },
|
||||
{
|
||||
91 * 24 * 60 * 60,
|
||||
tr::lng_schedule_repeat_every_month(tr::now, lt_count, 3)
|
||||
},
|
||||
{
|
||||
182 * 24 * 60 * 60,
|
||||
tr::lng_schedule_repeat_every_month(tr::now, lt_count, 6)
|
||||
},
|
||||
{ 365 * 24 * 60 * 60, tr::lng_schedule_repeat_yearly(tr::now) },
|
||||
};
|
||||
if (args.test) {
|
||||
map.insert(begin(map) + 1, Entry{ 300, u"Every 5 minutes"_q });
|
||||
map.insert(begin(map) + 1, Entry{ 60, u"Every minute"_q });
|
||||
}
|
||||
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(raw, QString());
|
||||
rpl::combine(
|
||||
raw->widthValue(),
|
||||
label->naturalWidthValue()
|
||||
) | rpl::start_with_next([=](int outer, int natural) {
|
||||
label->resizeToWidth(std::min(outer, natural));
|
||||
}, raw->lifetime());
|
||||
label->heightValue() | rpl::start_with_next([=](int height) {
|
||||
raw->resize(raw->width(), height);
|
||||
}, label->lifetime());
|
||||
|
||||
struct State {
|
||||
rpl::variable<TimeId> value;
|
||||
rpl::variable<bool> locked;
|
||||
std::unique_ptr<Ui::PopupMenu> menu;
|
||||
};
|
||||
const auto state = raw->lifetime().make_state<State>(State{
|
||||
.value = args.value,
|
||||
.locked = std::move(args.locked),
|
||||
});
|
||||
|
||||
rpl::combine(
|
||||
state->value.value(),
|
||||
state->locked.value()
|
||||
) | rpl::start_with_next([=](TimeId value, bool locked) {
|
||||
auto result = tr::lng_schedule_repeat_label(
|
||||
tr::now,
|
||||
Ui::Text::WithEntities);
|
||||
|
||||
const auto text = [&] {
|
||||
const auto i = ranges::lower_bound(
|
||||
map,
|
||||
value,
|
||||
ranges::less{},
|
||||
&Entry::value);
|
||||
return (i != end(map)) ? i->text : map.back().text;
|
||||
}();
|
||||
|
||||
label->setMarkedText(result.append(' ').append(Ui::Text::Link(
|
||||
Ui::Text::Bold(text).append(
|
||||
Ui::Text::IconEmoji(locked
|
||||
? &st::scheduleRepeatDropdownLock
|
||||
: &st::scheduleRepeatDropdownArrow))
|
||||
)));
|
||||
return result;
|
||||
}, label->lifetime());
|
||||
|
||||
label->setClickHandlerFilter([=](const auto &...) {
|
||||
if (args.filter && args.filter()) {
|
||||
return false;
|
||||
}
|
||||
const auto changed = args.changed;
|
||||
|
||||
state->menu = std::make_unique<Ui::PopupMenu>(label);
|
||||
const auto menu = state->menu.get();
|
||||
|
||||
menu->setDestroyedCallback(crl::guard(label, [=] {
|
||||
if (state->menu.get() == menu) {
|
||||
state->menu.release();
|
||||
}
|
||||
}));
|
||||
for (const auto &entry : map) {
|
||||
const auto value = entry.value;
|
||||
menu->addAction(entry.text, [=] {
|
||||
state->value = value;
|
||||
changed(value);
|
||||
});
|
||||
}
|
||||
menu->popup(QCursor::pos());
|
||||
return false;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -23,6 +23,7 @@ struct ChooseDateTimeBoxDescriptor {
|
||||
QPointer<RoundButton> submit;
|
||||
Fn<TimeId()> collect;
|
||||
rpl::producer<TimeId> values;
|
||||
rpl::producer<int> width;
|
||||
};
|
||||
|
||||
struct ChooseDateTimeStyleArgs {
|
||||
@@ -50,4 +51,16 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||
not_null<GenericBox*> box,
|
||||
ChooseDateTimeBoxArgs &&args);
|
||||
|
||||
struct ChooseRepeatPeriodArgs {
|
||||
TimeId value = 0;
|
||||
rpl::variable<bool> locked;
|
||||
Fn<bool()> filter;
|
||||
Fn<void(TimeId)> changed;
|
||||
bool test = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> ChooseRepeatPeriod(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
ChooseRepeatPeriodArgs &&args);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
Reference in New Issue
Block a user