mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-07 15:43:55 +02:00
Move CalendarBox and ChooseDateTimeBox to td_ui.
This commit is contained in:
parent
1cce383d15
commit
97fb310f54
19 changed files with 1161 additions and 1008 deletions
|
@ -203,8 +203,6 @@ PRIVATE
|
||||||
boxes/background_box.h
|
boxes/background_box.h
|
||||||
boxes/background_preview_box.cpp
|
boxes/background_preview_box.cpp
|
||||||
boxes/background_preview_box.h
|
boxes/background_preview_box.h
|
||||||
boxes/calendar_box.cpp
|
|
||||||
boxes/calendar_box.h
|
|
||||||
boxes/change_phone_box.cpp
|
boxes/change_phone_box.cpp
|
||||||
boxes/change_phone_box.h
|
boxes/change_phone_box.h
|
||||||
boxes/confirm_box.cpp
|
boxes/confirm_box.cpp
|
||||||
|
|
|
@ -431,9 +431,12 @@ auto InviteLinks::parse(
|
||||||
void InviteLinks::requestMoreLinks(
|
void InviteLinks::requestMoreLinks(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const QString &last,
|
const QString &last,
|
||||||
|
bool revoked,
|
||||||
Fn<void(Links)> done) {
|
Fn<void(Links)> done) {
|
||||||
|
using Flag = MTPmessages_GetExportedChatInvites::Flag;
|
||||||
_api->request(MTPmessages_GetExportedChatInvites(
|
_api->request(MTPmessages_GetExportedChatInvites(
|
||||||
MTP_flags(MTPmessages_GetExportedChatInvites::Flag::f_offset_link),
|
MTP_flags(Flag::f_offset_link
|
||||||
|
| (revoked ? Flag::f_revoked : Flag(0))),
|
||||||
peer->input,
|
peer->input,
|
||||||
MTPInputUser(), // admin_id,
|
MTPInputUser(), // admin_id,
|
||||||
MTP_string(last),
|
MTP_string(last),
|
||||||
|
|
|
@ -81,6 +81,7 @@ public:
|
||||||
void requestMoreLinks(
|
void requestMoreLinks(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const QString &last,
|
const QString &last,
|
||||||
|
bool revoked,
|
||||||
Fn<void(Links)> done);
|
Fn<void(Links)> done);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -905,3 +905,32 @@ pollResultsShowMore: SettingsButton(defaultSettingsButton) {
|
||||||
|
|
||||||
ripple: defaultRippleAnimation;
|
ripple: defaultRippleAnimation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scheduleHeight: 95px;
|
||||||
|
scheduleDateTop: 38px;
|
||||||
|
scheduleDateField: InputField(defaultInputField) {
|
||||||
|
textMargins: margins(2px, 0px, 2px, 0px);
|
||||||
|
placeholderScale: 0.;
|
||||||
|
heightMin: 30px;
|
||||||
|
textAlign: align(top);
|
||||||
|
font: font(14px);
|
||||||
|
}
|
||||||
|
scheduleTimeField: InputField(scheduleDateField) {
|
||||||
|
border: 0px;
|
||||||
|
borderActive: 0px;
|
||||||
|
heightMin: 28px;
|
||||||
|
placeholderFont: font(14px);
|
||||||
|
placeholderFgActive: placeholderFgActive;
|
||||||
|
}
|
||||||
|
scheduleDateWidth: 136px;
|
||||||
|
scheduleTimeWidth: 72px;
|
||||||
|
scheduleAtSkip: 24px;
|
||||||
|
scheduleAtTop: 42px;
|
||||||
|
scheduleAtLabel: FlatLabel(defaultFlatLabel) {
|
||||||
|
}
|
||||||
|
scheduleTimeSeparator: FlatLabel(defaultFlatLabel) {
|
||||||
|
style: TextStyle(defaultTextStyle) {
|
||||||
|
font: font(14px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scheduleTimeSeparatorPadding: margins(2px, 0px, 2px, 0px);
|
||||||
|
|
|
@ -20,10 +20,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/toast/toast.h"
|
#include "ui/toast/toast.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
#include "ui/text/text_options.h"
|
#include "ui/text/text_options.h"
|
||||||
|
#include "ui/boxes/calendar_box.h"
|
||||||
#include "ui/special_buttons.h"
|
#include "ui/special_buttons.h"
|
||||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||||
#include "settings/settings_privacy_security.h"
|
#include "settings/settings_privacy_security.h"
|
||||||
#include "boxes/calendar_box.h"
|
|
||||||
#include "boxes/confirm_box.h"
|
#include "boxes/confirm_box.h"
|
||||||
#include "boxes/passcode_box.h"
|
#include "boxes/passcode_box.h"
|
||||||
#include "boxes/peers/edit_peer_permissions_box.h"
|
#include "boxes/peers/edit_peer_permissions_box.h"
|
||||||
|
@ -691,7 +691,7 @@ void EditRestrictedBox::showRestrictUntil() {
|
||||||
: base::unixtime::parse(getRealUntilValue()).date();
|
: base::unixtime::parse(getRealUntilValue()).date();
|
||||||
auto month = highlighted;
|
auto month = highlighted;
|
||||||
_restrictUntilBox = Ui::show(
|
_restrictUntilBox = Ui::show(
|
||||||
Box<CalendarBox>(
|
Box<Ui::CalendarBox>(
|
||||||
month,
|
month,
|
||||||
highlighted,
|
highlighted,
|
||||||
[this](const QDate &date) {
|
[this](const QDate &date) {
|
||||||
|
|
|
@ -18,6 +18,7 @@ class LinkButton;
|
||||||
class Checkbox;
|
class Checkbox;
|
||||||
class Radiobutton;
|
class Radiobutton;
|
||||||
class RadiobuttonGroup;
|
class RadiobuttonGroup;
|
||||||
|
class CalendarBox;
|
||||||
template <typename Widget>
|
template <typename Widget>
|
||||||
class SlideWrap;
|
class SlideWrap;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
@ -26,7 +27,6 @@ namespace Core {
|
||||||
struct CloudPasswordResult;
|
struct CloudPasswordResult;
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
||||||
class CalendarBox;
|
|
||||||
class PasscodeBox;
|
class PasscodeBox;
|
||||||
|
|
||||||
class EditParticipantBox : public Ui::BoxContent {
|
class EditParticipantBox : public Ui::BoxContent {
|
||||||
|
@ -162,7 +162,7 @@ private:
|
||||||
|
|
||||||
std::shared_ptr<Ui::RadiobuttonGroup> _untilGroup;
|
std::shared_ptr<Ui::RadiobuttonGroup> _untilGroup;
|
||||||
std::vector<base::unique_qptr<Ui::Radiobutton>> _untilVariants;
|
std::vector<base::unique_qptr<Ui::Radiobutton>> _untilVariants;
|
||||||
QPointer<CalendarBox> _restrictUntilBox;
|
QPointer<Ui::CalendarBox> _restrictUntilBox;
|
||||||
|
|
||||||
static constexpr auto kUntilOneDay = -1;
|
static constexpr auto kUntilOneDay = -1;
|
||||||
static constexpr auto kUntilOneWeek = -2;
|
static constexpr auto kUntilOneWeek = -2;
|
||||||
|
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_histories.h"
|
#include "data/data_histories.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "api/api_invite_links.h"
|
#include "api/api_invite_links.h"
|
||||||
|
#include "ui/boxes/edit_invite_link.h"
|
||||||
#include "ui/wrap/vertical_layout.h"
|
#include "ui/wrap/vertical_layout.h"
|
||||||
#include "ui/wrap/slide_wrap.h"
|
#include "ui/wrap/slide_wrap.h"
|
||||||
#include "ui/wrap/padding_wrap.h"
|
#include "ui/wrap/padding_wrap.h"
|
||||||
|
@ -27,7 +28,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
#include "ui/toast/toast.h"
|
#include "ui/toast/toast.h"
|
||||||
#include "history/view/history_view_group_call_tracker.h" // GenerateUs...
|
#include "history/view/history_view_group_call_tracker.h" // GenerateUs...
|
||||||
#include "history/view/history_view_schedule_box.h" // ChooseDateTimeBox.
|
|
||||||
#include "history/history_message.h" // GetErrorTextForSending.
|
#include "history/history_message.h" // GetErrorTextForSending.
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
|
@ -53,9 +53,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kPreloadPages = 2;
|
constexpr auto kPreloadPages = 2;
|
||||||
constexpr auto kMaxLimit = std::numeric_limits<int>::max();
|
|
||||||
constexpr auto kHour = 3600;
|
|
||||||
constexpr auto kDay = 86400;
|
|
||||||
|
|
||||||
enum class Color {
|
enum class Color {
|
||||||
Permanent,
|
Permanent,
|
||||||
|
@ -130,27 +127,6 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] QString FormatExpireDate(TimeId date) {
|
|
||||||
if (date > 0) {
|
|
||||||
return langDateTime(base::unixtime::parse(date));
|
|
||||||
} else if (-date < kDay) {
|
|
||||||
return tr::lng_group_call_duration_hours(
|
|
||||||
tr::now,
|
|
||||||
lt_count,
|
|
||||||
(-date / kHour));
|
|
||||||
} else if (-date < 7 * kDay) {
|
|
||||||
return tr::lng_group_call_duration_days(
|
|
||||||
tr::now,
|
|
||||||
lt_count,
|
|
||||||
(-date / kDay));
|
|
||||||
} else {
|
|
||||||
return tr::lng_local_storage_limit_weeks(
|
|
||||||
tr::now,
|
|
||||||
lt_count,
|
|
||||||
(-date / (7 * kDay)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] uint64 ComputeRowId(const QString &link) {
|
[[nodiscard]] uint64 ComputeRowId(const QString &link) {
|
||||||
return XXH64(link.data(), link.size() * sizeof(ushort), 0);
|
return XXH64(link.data(), link.size() * sizeof(ushort), 0);
|
||||||
}
|
}
|
||||||
|
@ -282,6 +258,45 @@ void ShareLinkBox(not_null<PeerData*> peer, const QString &link) {
|
||||||
std::move(filterCallback)));
|
std::move(filterCallback)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EditLink(not_null<PeerData*> peer, const InviteLinkData &data) {
|
||||||
|
const auto creating = data.link.isEmpty();
|
||||||
|
const auto box = std::make_shared<QPointer<Ui::GenericBox>>();
|
||||||
|
using Fields = Ui::InviteLinkFields;
|
||||||
|
const auto done = [=](Fields result) {
|
||||||
|
const auto finish = [=](Api::InviteLink finished) {
|
||||||
|
if (*box) {
|
||||||
|
(*box)->closeBox();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (creating) {
|
||||||
|
peer->session().api().inviteLinks().create(
|
||||||
|
peer,
|
||||||
|
finish,
|
||||||
|
result.expireDate,
|
||||||
|
result.usageLimit);
|
||||||
|
} else {
|
||||||
|
peer->session().api().inviteLinks().edit(
|
||||||
|
peer,
|
||||||
|
result.link,
|
||||||
|
result.expireDate,
|
||||||
|
result.usageLimit,
|
||||||
|
finish);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*box = Ui::show(
|
||||||
|
(creating
|
||||||
|
? Box(Ui::CreateInviteLinkBox, done)
|
||||||
|
: Box(
|
||||||
|
Ui::EditInviteLinkBox,
|
||||||
|
Fields{
|
||||||
|
.link = data.link,
|
||||||
|
.expireDate = data.expireDate,
|
||||||
|
.usageLimit = data.usageLimit
|
||||||
|
},
|
||||||
|
done)),
|
||||||
|
Ui::LayerOption::KeepOther);
|
||||||
|
}
|
||||||
|
|
||||||
not_null<Ui::SettingsButton*> AddCreateLinkButton(
|
not_null<Ui::SettingsButton*> AddCreateLinkButton(
|
||||||
not_null<Ui::VerticalLayout*> container) {
|
not_null<Ui::VerticalLayout*> container) {
|
||||||
const auto result = container->add(
|
const auto result = container->add(
|
||||||
|
@ -314,241 +329,6 @@ not_null<Ui::SettingsButton*> AddCreateLinkButton(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditLinkBox(
|
|
||||||
not_null<Ui::GenericBox*> box,
|
|
||||||
not_null<PeerData*> peer,
|
|
||||||
const InviteLinkData &data) {
|
|
||||||
const auto link = data.link;
|
|
||||||
box->setTitle(link.isEmpty()
|
|
||||||
? tr::lng_group_invite_new_title()
|
|
||||||
: tr::lng_group_invite_edit_title());
|
|
||||||
|
|
||||||
using namespace Settings;
|
|
||||||
const auto container = box->verticalLayout();
|
|
||||||
|
|
||||||
AddSubsectionTitle(container, tr::lng_group_invite_expire_title());
|
|
||||||
const auto expiresWrap = container->add(object_ptr<Ui::VerticalLayout>(
|
|
||||||
container));
|
|
||||||
AddSkip(container);
|
|
||||||
|
|
||||||
AddDividerText(container, tr::lng_group_invite_expire_about());
|
|
||||||
AddSkip(container);
|
|
||||||
AddSubsectionTitle(container, tr::lng_group_invite_usage_title());
|
|
||||||
const auto usagesWrap = container->add(object_ptr<Ui::VerticalLayout>(
|
|
||||||
container));
|
|
||||||
AddSkip(container);
|
|
||||||
|
|
||||||
AddDividerText(container, tr::lng_group_invite_usage_about());
|
|
||||||
|
|
||||||
static const auto addButton = [](
|
|
||||||
not_null<Ui::VerticalLayout*> container,
|
|
||||||
const std::shared_ptr<Ui::RadiobuttonGroup> &group,
|
|
||||||
int value,
|
|
||||||
const QString &text) {
|
|
||||||
return container->add(
|
|
||||||
object_ptr<Ui::Radiobutton>(
|
|
||||||
container,
|
|
||||||
group,
|
|
||||||
value,
|
|
||||||
text),
|
|
||||||
st::inviteLinkLimitMargin);
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto now = base::unixtime::now();
|
|
||||||
const auto expire = data.expireDate ? data.expireDate : kMaxLimit;
|
|
||||||
const auto expireGroup = std::make_shared<Ui::RadiobuttonGroup>(expire);
|
|
||||||
const auto usage = data.usageLimit ? data.usageLimit : kMaxLimit;
|
|
||||||
const auto usageGroup = std::make_shared<Ui::RadiobuttonGroup>(usage);
|
|
||||||
|
|
||||||
using Buttons = base::flat_map<int, base::unique_qptr<Ui::Radiobutton>>;
|
|
||||||
struct State {
|
|
||||||
Buttons expireButtons;
|
|
||||||
Buttons usageButtons;
|
|
||||||
int expireValue = 0;
|
|
||||||
int usageValue = 0;
|
|
||||||
};
|
|
||||||
const auto state = container->lifetime().make_state<State>(State{
|
|
||||||
.expireValue = expire,
|
|
||||||
.usageValue = usage
|
|
||||||
});
|
|
||||||
const auto regenerate = [=] {
|
|
||||||
expireGroup->setValue(state->expireValue);
|
|
||||||
usageGroup->setValue(state->usageValue);
|
|
||||||
|
|
||||||
auto expires = std::vector{ kMaxLimit, -kHour, -kDay, -kDay * 7, 0 };
|
|
||||||
auto usages = std::vector{ kMaxLimit, 1, 10, 100, 0 };
|
|
||||||
auto defaults = State();
|
|
||||||
for (auto i = begin(expires); i != end(expires); ++i) {
|
|
||||||
if (*i == state->expireValue) {
|
|
||||||
break;
|
|
||||||
} else if (*i == kMaxLimit) {
|
|
||||||
continue;
|
|
||||||
} else if (!*i || (now - *i >= state->expireValue)) {
|
|
||||||
expires.insert(i, state->expireValue);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (auto i = begin(usages); i != end(usages); ++i) {
|
|
||||||
if (*i == state->usageValue) {
|
|
||||||
break;
|
|
||||||
} else if (*i == kMaxLimit) {
|
|
||||||
continue;
|
|
||||||
} else if (!*i || *i > state->usageValue) {
|
|
||||||
usages.insert(i, state->usageValue);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state->expireButtons.clear();
|
|
||||||
state->usageButtons.clear();
|
|
||||||
for (const auto limit : expires) {
|
|
||||||
const auto text = (limit == kMaxLimit)
|
|
||||||
? tr::lng_group_invite_expire_never(tr::now)
|
|
||||||
: !limit
|
|
||||||
? tr::lng_group_invite_expire_custom(tr::now)
|
|
||||||
: FormatExpireDate(limit);
|
|
||||||
state->expireButtons.emplace(
|
|
||||||
limit,
|
|
||||||
addButton(expiresWrap, expireGroup, limit, text));
|
|
||||||
}
|
|
||||||
for (const auto limit : usages) {
|
|
||||||
const auto text = (limit == kMaxLimit)
|
|
||||||
? tr::lng_group_invite_usage_any(tr::now)
|
|
||||||
: !limit
|
|
||||||
? tr::lng_group_invite_usage_custom(tr::now)
|
|
||||||
: QString("%L1").arg(limit);
|
|
||||||
state->usageButtons.emplace(
|
|
||||||
limit,
|
|
||||||
addButton(usagesWrap, usageGroup, limit, text));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto guard = Ui::MakeWeak(box);
|
|
||||||
expireGroup->setChangedCallback([=](int value) {
|
|
||||||
if (value) {
|
|
||||||
state->expireValue = value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
expireGroup->setValue(state->expireValue);
|
|
||||||
box->getDelegate()->show(Box([=](not_null<Ui::GenericBox*> box) {
|
|
||||||
const auto save = [=](TimeId result) {
|
|
||||||
if (!result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (guard) {
|
|
||||||
state->expireValue = result;
|
|
||||||
regenerate();
|
|
||||||
}
|
|
||||||
box->closeBox();
|
|
||||||
};
|
|
||||||
const auto now = base::unixtime::now();
|
|
||||||
const auto time = (state->expireValue == kMaxLimit)
|
|
||||||
? (now + kDay)
|
|
||||||
: (state->expireValue > now)
|
|
||||||
? state->expireValue
|
|
||||||
: (state->expireValue < 0)
|
|
||||||
? (now - state->expireValue)
|
|
||||||
: (now + kDay);
|
|
||||||
HistoryView::ChooseDateTimeBox(
|
|
||||||
box,
|
|
||||||
tr::lng_group_invite_expire_after(),
|
|
||||||
tr::lng_settings_save(),
|
|
||||||
save,
|
|
||||||
time);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
usageGroup->setChangedCallback([=](int value) {
|
|
||||||
if (value) {
|
|
||||||
state->usageValue = value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
usageGroup->setValue(state->usageValue);
|
|
||||||
box->getDelegate()->show(Box([=](not_null<Ui::GenericBox*> box) {
|
|
||||||
const auto height = st::boxPadding.bottom()
|
|
||||||
+ st::defaultInputField.heightMin
|
|
||||||
+ st::boxPadding.bottom();
|
|
||||||
box->setTitle(tr::lng_group_invite_expire_after());
|
|
||||||
const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
|
||||||
box,
|
|
||||||
height));
|
|
||||||
const auto input = Ui::CreateChild<Ui::NumberInput>(
|
|
||||||
wrap,
|
|
||||||
st::defaultInputField,
|
|
||||||
tr::lng_group_invite_custom_limit(),
|
|
||||||
(state->usageValue == kMaxLimit
|
|
||||||
? QString()
|
|
||||||
: QString::number(state->usageValue)),
|
|
||||||
200'000);
|
|
||||||
wrap->widthValue(
|
|
||||||
) | rpl::start_with_next([=](int width) {
|
|
||||||
input->resize(width, input->height());
|
|
||||||
input->moveToLeft(0, st::boxPadding.bottom());
|
|
||||||
}, input->lifetime());
|
|
||||||
box->setFocusCallback([=] {
|
|
||||||
input->setFocusFast();
|
|
||||||
});
|
|
||||||
|
|
||||||
const auto save = [=] {
|
|
||||||
const auto value = input->getLastText().toInt();
|
|
||||||
if (value <= 0) {
|
|
||||||
input->showError();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (guard) {
|
|
||||||
state->usageValue = value;
|
|
||||||
regenerate();
|
|
||||||
}
|
|
||||||
box->closeBox();
|
|
||||||
};
|
|
||||||
QObject::connect(input, &Ui::NumberInput::submitted, save);
|
|
||||||
box->addButton(tr::lng_settings_save(), save);
|
|
||||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
regenerate();
|
|
||||||
|
|
||||||
const auto &saveLabel = link.isEmpty()
|
|
||||||
? tr::lng_formatting_link_create
|
|
||||||
: tr::lng_settings_save;
|
|
||||||
box->addButton(saveLabel(), [=] {
|
|
||||||
const auto expireDate = (state->expireValue == kMaxLimit)
|
|
||||||
? 0
|
|
||||||
: (state->expireValue < 0)
|
|
||||||
? (base::unixtime::now() - state->expireValue)
|
|
||||||
: state->expireValue;
|
|
||||||
const auto usageLimit = (state->usageValue == kMaxLimit)
|
|
||||||
? 0
|
|
||||||
: state->usageValue;
|
|
||||||
const auto done = [=](const Api::InviteLink &result) {
|
|
||||||
box->closeBox();
|
|
||||||
};
|
|
||||||
if (link.isEmpty()) {
|
|
||||||
peer->session().api().inviteLinks().create(
|
|
||||||
peer,
|
|
||||||
done,
|
|
||||||
expireDate,
|
|
||||||
usageLimit);
|
|
||||||
} else {
|
|
||||||
peer->session().api().inviteLinks().edit(
|
|
||||||
peer,
|
|
||||||
link,
|
|
||||||
expireDate,
|
|
||||||
usageLimit,
|
|
||||||
done);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void CreateLinkBox(
|
|
||||||
not_null<Ui::GenericBox*> box,
|
|
||||||
not_null<PeerData*> peer) {
|
|
||||||
EditLinkBox(
|
|
||||||
box,
|
|
||||||
peer,
|
|
||||||
InviteLinkData{ .admin = peer->session().user() });
|
|
||||||
}
|
|
||||||
|
|
||||||
Row::Row(
|
Row::Row(
|
||||||
not_null<RowDelegate*> delegate,
|
not_null<RowDelegate*> delegate,
|
||||||
const InviteLinkData &data,
|
const InviteLinkData &data,
|
||||||
|
@ -724,9 +504,7 @@ base::unique_qptr<Ui::PopupMenu> Controller::createRowContextMenu(
|
||||||
ShareLinkBox(_peer, link);
|
ShareLinkBox(_peer, link);
|
||||||
});
|
});
|
||||||
result->addAction(tr::lng_group_invite_context_edit(tr::now), [=] {
|
result->addAction(tr::lng_group_invite_context_edit(tr::now), [=] {
|
||||||
Ui::show(
|
EditLink(_peer, data);
|
||||||
Box(EditLinkBox, _peer, data),
|
|
||||||
Ui::LayerOption::KeepOther);
|
|
||||||
});
|
});
|
||||||
result->addAction(tr::lng_group_invite_context_revoke(tr::now), [=] {
|
result->addAction(tr::lng_group_invite_context_revoke(tr::now), [=] {
|
||||||
const auto box = std::make_shared<QPointer<ConfirmBox>>();
|
const auto box = std::make_shared<QPointer<ConfirmBox>>();
|
||||||
|
@ -1024,7 +802,7 @@ void ManageInviteLinksBox(
|
||||||
|
|
||||||
const auto add = AddCreateLinkButton(container);
|
const auto add = AddCreateLinkButton(container);
|
||||||
add->setClickedCallback([=] {
|
add->setClickedCallback([=] {
|
||||||
box->getDelegate()->show(Box(CreateLinkBox, peer));
|
EditLink(peer, InviteLinkData{ .admin = peer->session().user() });
|
||||||
});
|
});
|
||||||
|
|
||||||
const auto list = AddLinksList(container, peer, false);
|
const auto list = AddLinksList(container, peer, false);
|
||||||
|
|
|
@ -21,9 +21,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/wrap/fade_wrap.h"
|
#include "ui/wrap/fade_wrap.h"
|
||||||
#include "ui/layers/generic_box.h"
|
#include "ui/layers/generic_box.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
|
#include "ui/boxes/calendar_box.h"
|
||||||
#include "platform/platform_specific.h"
|
#include "platform/platform_specific.h"
|
||||||
#include "core/file_utilities.h"
|
#include "core/file_utilities.h"
|
||||||
#include "boxes/calendar_box.h"
|
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "base/qt_adapters.h"
|
#include "base/qt_adapters.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
@ -463,8 +463,8 @@ void SettingsWidget::editDateLimit(
|
||||||
? base::unixtime::parse(min).date()
|
? base::unixtime::parse(min).date()
|
||||||
: QDate::currentDate();
|
: QDate::currentDate();
|
||||||
const auto month = highlighted;
|
const auto month = highlighted;
|
||||||
const auto shared = std::make_shared<QPointer<CalendarBox>>();
|
const auto shared = std::make_shared<QPointer<Ui::CalendarBox>>();
|
||||||
const auto finalize = [=](not_null<CalendarBox*> box) {
|
const auto finalize = [=](not_null<Ui::CalendarBox*> box) {
|
||||||
box->setMaxDate(max
|
box->setMaxDate(max
|
||||||
? base::unixtime::parse(max).date()
|
? base::unixtime::parse(max).date()
|
||||||
: QDate::currentDate());
|
: QDate::currentDate());
|
||||||
|
@ -484,7 +484,7 @@ void SettingsWidget::editDateLimit(
|
||||||
weak->closeBox();
|
weak->closeBox();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
auto box = Box<CalendarBox>(
|
auto box = Box<Ui::CalendarBox>(
|
||||||
month,
|
month,
|
||||||
highlighted,
|
highlighted,
|
||||||
callback,
|
callback,
|
||||||
|
|
|
@ -14,12 +14,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "base/event_filter.h"
|
#include "base/event_filter.h"
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "boxes/calendar_box.h"
|
|
||||||
#include "ui/widgets/input_fields.h"
|
#include "ui/widgets/input_fields.h"
|
||||||
#include "ui/widgets/labels.h"
|
#include "ui/widgets/labels.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/wrap/padding_wrap.h"
|
#include "ui/wrap/padding_wrap.h"
|
||||||
|
#include "ui/boxes/choose_date_time.h"
|
||||||
#include "chat_helpers/send_context_menu.h"
|
#include "chat_helpers/send_context_menu.h"
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
#include "styles/style_layers.h"
|
#include "styles/style_layers.h"
|
||||||
|
@ -30,550 +30,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kMinimalSchedule = TimeId(10);
|
|
||||||
|
|
||||||
tr::phrase<> MonthDay(int index) {
|
|
||||||
switch (index) {
|
|
||||||
case 1: return tr::lng_month_day1;
|
|
||||||
case 2: return tr::lng_month_day2;
|
|
||||||
case 3: return tr::lng_month_day3;
|
|
||||||
case 4: return tr::lng_month_day4;
|
|
||||||
case 5: return tr::lng_month_day5;
|
|
||||||
case 6: return tr::lng_month_day6;
|
|
||||||
case 7: return tr::lng_month_day7;
|
|
||||||
case 8: return tr::lng_month_day8;
|
|
||||||
case 9: return tr::lng_month_day9;
|
|
||||||
case 10: return tr::lng_month_day10;
|
|
||||||
case 11: return tr::lng_month_day11;
|
|
||||||
case 12: return tr::lng_month_day12;
|
|
||||||
}
|
|
||||||
Unexpected("Index in MonthDay.");
|
|
||||||
}
|
|
||||||
|
|
||||||
QString DayString(const QDate &date) {
|
|
||||||
return tr::lng_month_day(
|
|
||||||
tr::now,
|
|
||||||
lt_month,
|
|
||||||
MonthDay(date.month())(tr::now),
|
|
||||||
lt_day,
|
|
||||||
QString::number(date.day()));
|
|
||||||
}
|
|
||||||
|
|
||||||
QString TimeString(TimeId time) {
|
|
||||||
const auto parsed = base::unixtime::parse(time).time();
|
|
||||||
return QString("%1:%2"
|
|
||||||
).arg(parsed.hour()
|
|
||||||
).arg(parsed.minute(), 2, 10, QLatin1Char('0'));
|
|
||||||
}
|
|
||||||
|
|
||||||
int ProcessWheelEvent(not_null<QWheelEvent*> e) {
|
|
||||||
// Only a mouse wheel is accepted.
|
|
||||||
constexpr auto step = static_cast<int>(QWheelEvent::DefaultDeltasPerStep);
|
|
||||||
const auto delta = e->angleDelta().y();
|
|
||||||
const auto absDelta = std::abs(delta);
|
|
||||||
if (absDelta != step) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return (delta / absDelta);
|
|
||||||
}
|
|
||||||
|
|
||||||
class TimePart final : public Ui::MaskedInputField {
|
|
||||||
public:
|
|
||||||
using MaskedInputField::MaskedInputField;
|
|
||||||
|
|
||||||
void setMaxValue(int value);
|
|
||||||
void setWheelStep(int value);
|
|
||||||
|
|
||||||
rpl::producer<> erasePrevious() const;
|
|
||||||
rpl::producer<QChar> putNext() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void keyPressEvent(QKeyEvent *e) override;
|
|
||||||
void wheelEvent(QWheelEvent *e) override;
|
|
||||||
|
|
||||||
void correctValue(
|
|
||||||
const QString &was,
|
|
||||||
int wasCursor,
|
|
||||||
QString &now,
|
|
||||||
int &nowCursor) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
int _maxValue = 0;
|
|
||||||
int _maxDigits = 0;
|
|
||||||
int _wheelStep = 0;
|
|
||||||
rpl::event_stream<> _erasePrevious;
|
|
||||||
rpl::event_stream<QChar> _putNext;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
int Number(not_null<TimePart*> field) {
|
|
||||||
const auto text = field->getLastText();
|
|
||||||
auto ref = text.midRef(0);
|
|
||||||
while (!ref.isEmpty() && ref.at(0) == '0') {
|
|
||||||
ref = ref.mid(1);
|
|
||||||
}
|
|
||||||
return ref.toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
class TimeInput final : public Ui::RpWidget {
|
|
||||||
public:
|
|
||||||
TimeInput(QWidget *parent, const QString &value);
|
|
||||||
|
|
||||||
bool setFocusFast();
|
|
||||||
rpl::producer<QString> value() const;
|
|
||||||
rpl::producer<> submitRequests() const;
|
|
||||||
QString valueCurrent() const;
|
|
||||||
void showError();
|
|
||||||
|
|
||||||
int resizeGetHeight(int width) override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void paintEvent(QPaintEvent *e) override;
|
|
||||||
void mousePressEvent(QMouseEvent *e) override;
|
|
||||||
void mouseMoveEvent(QMouseEvent *e) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void setInnerFocus();
|
|
||||||
void putNext(const object_ptr<TimePart> &field, QChar ch);
|
|
||||||
void erasePrevious(const object_ptr<TimePart> &field);
|
|
||||||
void finishInnerAnimating();
|
|
||||||
void setErrorShown(bool error);
|
|
||||||
void setFocused(bool focused);
|
|
||||||
void startBorderAnimation();
|
|
||||||
template <typename Widget>
|
|
||||||
bool insideSeparator(QPoint position, const Widget &widget) const;
|
|
||||||
|
|
||||||
int hour() const;
|
|
||||||
int minute() const;
|
|
||||||
|
|
||||||
object_ptr<TimePart> _hour;
|
|
||||||
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>> _separator1;
|
|
||||||
object_ptr<TimePart> _minute;
|
|
||||||
rpl::variable<QString> _value;
|
|
||||||
rpl::event_stream<> _submitRequests;
|
|
||||||
|
|
||||||
style::cursor _cursor = style::cur_default;
|
|
||||||
Ui::Animations::Simple _a_borderShown;
|
|
||||||
int _borderAnimationStart = 0;
|
|
||||||
Ui::Animations::Simple _a_borderOpacity;
|
|
||||||
bool _borderVisible = false;
|
|
||||||
|
|
||||||
Ui::Animations::Simple _a_error;
|
|
||||||
bool _error = false;
|
|
||||||
Ui::Animations::Simple _a_focused;
|
|
||||||
bool _focused = false;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
QTime ValidateTime(const QString &value) {
|
|
||||||
const auto match = QRegularExpression(
|
|
||||||
"^(\\d{1,2})\\:(\\d\\d)$").match(value);
|
|
||||||
if (!match.hasMatch()) {
|
|
||||||
return QTime();
|
|
||||||
}
|
|
||||||
const auto readInt = [](const QString &value) {
|
|
||||||
auto ref = value.midRef(0);
|
|
||||||
while (!ref.isEmpty() && ref.at(0) == '0') {
|
|
||||||
ref = ref.mid(1);
|
|
||||||
}
|
|
||||||
return ref.toInt();
|
|
||||||
};
|
|
||||||
return QTime(readInt(match.captured(1)), readInt(match.captured(2)));
|
|
||||||
}
|
|
||||||
|
|
||||||
QString GetHour(const QString &value) {
|
|
||||||
if (const auto time = ValidateTime(value); time.isValid()) {
|
|
||||||
return QString::number(time.hour());
|
|
||||||
}
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString GetMinute(const QString &value) {
|
|
||||||
if (const auto time = ValidateTime(value); time.isValid()) {
|
|
||||||
return QString("%1").arg(time.minute(), 2, 10, QChar('0'));
|
|
||||||
}
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimePart::setMaxValue(int value) {
|
|
||||||
_maxValue = value;
|
|
||||||
_maxDigits = 0;
|
|
||||||
while (value > 0) {
|
|
||||||
++_maxDigits;
|
|
||||||
value /= 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimePart::setWheelStep(int value) {
|
|
||||||
_wheelStep = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<> TimePart::erasePrevious() const {
|
|
||||||
return _erasePrevious.events();
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<QChar> TimePart::putNext() const {
|
|
||||||
return _putNext.events();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimePart::keyPressEvent(QKeyEvent *e) {
|
|
||||||
const auto isBackspace = (e->key() == Qt::Key_Backspace);
|
|
||||||
const auto isBeginning = (cursorPosition() == 0);
|
|
||||||
if (isBackspace && isBeginning && !hasSelectedText()) {
|
|
||||||
_erasePrevious.fire({});
|
|
||||||
} else {
|
|
||||||
MaskedInputField::keyPressEvent(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimePart::wheelEvent(QWheelEvent *e) {
|
|
||||||
const auto direction = ProcessWheelEvent(e);
|
|
||||||
auto time = Number(this) + (direction * _wheelStep);
|
|
||||||
const auto max = _maxValue + 1;
|
|
||||||
if (time < 0) {
|
|
||||||
time += max;
|
|
||||||
} else if (time >= max) {
|
|
||||||
time -= max;
|
|
||||||
}
|
|
||||||
setText(QString::number(time));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimePart::correctValue(
|
|
||||||
const QString &was,
|
|
||||||
int wasCursor,
|
|
||||||
QString &now,
|
|
||||||
int &nowCursor) {
|
|
||||||
auto newText = QString();
|
|
||||||
auto newCursor = -1;
|
|
||||||
const auto oldCursor = nowCursor;
|
|
||||||
const auto oldLength = now.size();
|
|
||||||
auto accumulated = 0;
|
|
||||||
auto limit = 0;
|
|
||||||
for (; limit != oldLength; ++limit) {
|
|
||||||
if (now[limit].isDigit()) {
|
|
||||||
accumulated *= 10;
|
|
||||||
accumulated += (now[limit].unicode() - '0');
|
|
||||||
if (accumulated > _maxValue || limit == _maxDigits) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (auto i = 0; i != limit;) {
|
|
||||||
if (now[i].isDigit()) {
|
|
||||||
newText += now[i];
|
|
||||||
}
|
|
||||||
if (++i == oldCursor) {
|
|
||||||
newCursor = newText.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (newCursor < 0) {
|
|
||||||
newCursor = newText.size();
|
|
||||||
}
|
|
||||||
if (newText != now) {
|
|
||||||
now = newText;
|
|
||||||
setText(now);
|
|
||||||
startPlaceholderAnimation();
|
|
||||||
}
|
|
||||||
if (newCursor != nowCursor) {
|
|
||||||
nowCursor = newCursor;
|
|
||||||
setCursorPosition(nowCursor);
|
|
||||||
}
|
|
||||||
if (accumulated > _maxValue
|
|
||||||
|| (limit == _maxDigits && oldLength > _maxDigits)) {
|
|
||||||
if (oldCursor > limit) {
|
|
||||||
_putNext.fire('0' + (accumulated % 10));
|
|
||||||
} else {
|
|
||||||
_putNext.fire(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TimeInput::TimeInput(QWidget *parent, const QString &value)
|
|
||||||
: RpWidget(parent)
|
|
||||||
, _hour(
|
|
||||||
this,
|
|
||||||
st::scheduleTimeField,
|
|
||||||
rpl::never<QString>(),
|
|
||||||
GetHour(value))
|
|
||||||
, _separator1(
|
|
||||||
this,
|
|
||||||
object_ptr<Ui::FlatLabel>(
|
|
||||||
this,
|
|
||||||
QString(":"),
|
|
||||||
st::scheduleTimeSeparator),
|
|
||||||
st::scheduleTimeSeparatorPadding)
|
|
||||||
, _minute(
|
|
||||||
this,
|
|
||||||
st::scheduleTimeField,
|
|
||||||
rpl::never<QString>(),
|
|
||||||
GetMinute(value))
|
|
||||||
, _value(valueCurrent()) {
|
|
||||||
const auto focused = [=](const object_ptr<TimePart> &field) {
|
|
||||||
return [this, pointer = Ui::MakeWeak(field.data())]{
|
|
||||||
_borderAnimationStart = pointer->borderAnimationStart()
|
|
||||||
+ pointer->x()
|
|
||||||
- _hour->x();
|
|
||||||
setFocused(true);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const auto blurred = [=] {
|
|
||||||
setFocused(false);
|
|
||||||
};
|
|
||||||
const auto changed = [=] {
|
|
||||||
_value = valueCurrent();
|
|
||||||
};
|
|
||||||
connect(_hour, &Ui::MaskedInputField::focused, focused(_hour));
|
|
||||||
connect(_minute, &Ui::MaskedInputField::focused, focused(_minute));
|
|
||||||
connect(_hour, &Ui::MaskedInputField::blurred, blurred);
|
|
||||||
connect(_minute, &Ui::MaskedInputField::blurred, blurred);
|
|
||||||
connect(_hour, &Ui::MaskedInputField::changed, changed);
|
|
||||||
connect(_minute, &Ui::MaskedInputField::changed, changed);
|
|
||||||
_hour->setMaxValue(23);
|
|
||||||
_hour->setWheelStep(1);
|
|
||||||
_hour->putNext() | rpl::start_with_next([=](QChar ch) {
|
|
||||||
putNext(_minute, ch);
|
|
||||||
}, lifetime());
|
|
||||||
_minute->setMaxValue(59);
|
|
||||||
_minute->setWheelStep(10);
|
|
||||||
_minute->erasePrevious() | rpl::start_with_next([=] {
|
|
||||||
erasePrevious(_hour);
|
|
||||||
}, lifetime());
|
|
||||||
_separator1->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
||||||
setMouseTracking(true);
|
|
||||||
|
|
||||||
_value.changes(
|
|
||||||
) | rpl::start_with_next([=] {
|
|
||||||
setErrorShown(false);
|
|
||||||
}, lifetime());
|
|
||||||
|
|
||||||
const auto submitHour = [=] {
|
|
||||||
if (hour()) {
|
|
||||||
_minute->setFocus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const auto submitMinute = [=] {
|
|
||||||
if (minute()) {
|
|
||||||
if (hour()) {
|
|
||||||
_submitRequests.fire({});
|
|
||||||
} else {
|
|
||||||
_hour->setFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
connect(
|
|
||||||
_hour,
|
|
||||||
&Ui::MaskedInputField::submitted,
|
|
||||||
submitHour);
|
|
||||||
connect(
|
|
||||||
_minute,
|
|
||||||
&Ui::MaskedInputField::submitted,
|
|
||||||
submitMinute);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeInput::putNext(const object_ptr<TimePart> &field, QChar ch) {
|
|
||||||
field->setCursorPosition(0);
|
|
||||||
if (ch.unicode()) {
|
|
||||||
field->setText(ch + field->getLastText());
|
|
||||||
field->setCursorPosition(1);
|
|
||||||
}
|
|
||||||
field->setFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeInput::erasePrevious(const object_ptr<TimePart> &field) {
|
|
||||||
const auto text = field->getLastText();
|
|
||||||
if (!text.isEmpty()) {
|
|
||||||
field->setCursorPosition(text.size() - 1);
|
|
||||||
field->setText(text.mid(0, text.size() - 1));
|
|
||||||
}
|
|
||||||
field->setFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TimeInput::setFocusFast() {
|
|
||||||
if (hour()) {
|
|
||||||
_minute->setFocusFast();
|
|
||||||
} else {
|
|
||||||
_hour->setFocusFast();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int TimeInput::hour() const {
|
|
||||||
return Number(_hour);
|
|
||||||
}
|
|
||||||
|
|
||||||
int TimeInput::minute() const {
|
|
||||||
return Number(_minute);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString TimeInput::valueCurrent() const {
|
|
||||||
const auto result = QString("%1:%2"
|
|
||||||
).arg(hour()
|
|
||||||
).arg(minute(), 2, 10, QChar('0'));
|
|
||||||
return ValidateTime(result).isValid() ? result : QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<QString> TimeInput::value() const {
|
|
||||||
return _value.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<> TimeInput::submitRequests() const {
|
|
||||||
return _submitRequests.events();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeInput::paintEvent(QPaintEvent *e) {
|
|
||||||
Painter p(this);
|
|
||||||
|
|
||||||
const auto &_st = st::scheduleDateField;
|
|
||||||
const auto height = _st.heightMin;
|
|
||||||
if (_st.border) {
|
|
||||||
p.fillRect(0, height - _st.border, width(), _st.border, _st.borderFg);
|
|
||||||
}
|
|
||||||
auto errorDegree = _a_error.value(_error ? 1. : 0.);
|
|
||||||
auto focusedDegree = _a_focused.value(_focused ? 1. : 0.);
|
|
||||||
auto borderShownDegree = _a_borderShown.value(1.);
|
|
||||||
auto borderOpacity = _a_borderOpacity.value(_borderVisible ? 1. : 0.);
|
|
||||||
if (_st.borderActive && (borderOpacity > 0.)) {
|
|
||||||
auto borderStart = std::clamp(_borderAnimationStart, 0, width());
|
|
||||||
auto borderFrom = qRound(borderStart * (1. - borderShownDegree));
|
|
||||||
auto borderTo = borderStart + qRound((width() - borderStart) * borderShownDegree);
|
|
||||||
if (borderTo > borderFrom) {
|
|
||||||
auto borderFg = anim::brush(_st.borderFgActive, _st.borderFgError, errorDegree);
|
|
||||||
p.setOpacity(borderOpacity);
|
|
||||||
p.fillRect(borderFrom, height - _st.borderActive, borderTo - borderFrom, _st.borderActive, borderFg);
|
|
||||||
p.setOpacity(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Widget>
|
|
||||||
bool TimeInput::insideSeparator(QPoint position, const Widget &widget) const {
|
|
||||||
const auto x = position.x();
|
|
||||||
const auto y = position.y();
|
|
||||||
return (x >= widget->x() && x < widget->x() + widget->width())
|
|
||||||
&& (y >= _hour->y() && y < _hour->y() + _hour->height());
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeInput::mouseMoveEvent(QMouseEvent *e) {
|
|
||||||
const auto cursor = insideSeparator(e->pos(), _separator1)
|
|
||||||
? style::cur_text
|
|
||||||
: style::cur_default;
|
|
||||||
if (_cursor != cursor) {
|
|
||||||
_cursor = cursor;
|
|
||||||
setCursor(_cursor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeInput::mousePressEvent(QMouseEvent *e) {
|
|
||||||
const auto x = e->pos().x();
|
|
||||||
const auto focus1 = [&] {
|
|
||||||
if (_hour->getLastText().size() > 1) {
|
|
||||||
_minute->setFocus();
|
|
||||||
} else {
|
|
||||||
_hour->setFocus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (insideSeparator(e->pos(), _separator1)) {
|
|
||||||
focus1();
|
|
||||||
_borderAnimationStart = x - _hour->x();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int TimeInput::resizeGetHeight(int width) {
|
|
||||||
const auto &_st = st::scheduleTimeField;
|
|
||||||
const auto &font = _st.placeholderFont;
|
|
||||||
const auto addToWidth = st::scheduleTimeSeparatorPadding.left();
|
|
||||||
const auto hourWidth = _st.textMargins.left()
|
|
||||||
+ _st.placeholderMargins.left()
|
|
||||||
+ font->width(QString("23"))
|
|
||||||
+ _st.placeholderMargins.right()
|
|
||||||
+ _st.textMargins.right()
|
|
||||||
+ addToWidth;
|
|
||||||
const auto minuteWidth = _st.textMargins.left()
|
|
||||||
+ _st.placeholderMargins.left()
|
|
||||||
+ font->width(QString("59"))
|
|
||||||
+ _st.placeholderMargins.right()
|
|
||||||
+ _st.textMargins.right()
|
|
||||||
+ addToWidth;
|
|
||||||
const auto full = hourWidth
|
|
||||||
- addToWidth
|
|
||||||
+ _separator1->width()
|
|
||||||
+ minuteWidth
|
|
||||||
- addToWidth;
|
|
||||||
auto left = (width - full) / 2;
|
|
||||||
auto top = 0;
|
|
||||||
_hour->setGeometry(left, top, hourWidth, _hour->height());
|
|
||||||
left += hourWidth - addToWidth;
|
|
||||||
_separator1->resizeToNaturalWidth(width);
|
|
||||||
_separator1->move(left, top);
|
|
||||||
left += _separator1->width();
|
|
||||||
_minute->setGeometry(left, top, minuteWidth, _minute->height());
|
|
||||||
return st::scheduleDateField.heightMin;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeInput::showError() {
|
|
||||||
setErrorShown(true);
|
|
||||||
if (!_focused) {
|
|
||||||
setInnerFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeInput::setInnerFocus() {
|
|
||||||
if (hour()) {
|
|
||||||
_minute->setFocus();
|
|
||||||
} else {
|
|
||||||
_hour->setFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeInput::setErrorShown(bool error) {
|
|
||||||
if (_error != error) {
|
|
||||||
_error = error;
|
|
||||||
_a_error.start(
|
|
||||||
[=] { update(); },
|
|
||||||
_error ? 0. : 1.,
|
|
||||||
_error ? 1. : 0.,
|
|
||||||
st::scheduleDateField.duration);
|
|
||||||
startBorderAnimation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeInput::setFocused(bool focused) {
|
|
||||||
if (_focused != focused) {
|
|
||||||
_focused = focused;
|
|
||||||
_a_focused.start(
|
|
||||||
[=] { update(); },
|
|
||||||
_focused ? 0. : 1.,
|
|
||||||
_focused ? 1. : 0.,
|
|
||||||
st::scheduleDateField.duration);
|
|
||||||
startBorderAnimation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeInput::finishInnerAnimating() {
|
|
||||||
_hour->finishAnimating();
|
|
||||||
_minute->finishAnimating();
|
|
||||||
_a_borderOpacity.stop();
|
|
||||||
_a_borderShown.stop();
|
|
||||||
_a_error.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeInput::startBorderAnimation() {
|
|
||||||
auto borderVisible = (_error || _focused);
|
|
||||||
if (_borderVisible != borderVisible) {
|
|
||||||
_borderVisible = borderVisible;
|
|
||||||
const auto duration = st::scheduleDateField.duration;
|
|
||||||
if (_borderVisible) {
|
|
||||||
if (_a_borderOpacity.animating()) {
|
|
||||||
_a_borderOpacity.start([=] { update(); }, 0., 1., duration);
|
|
||||||
} else {
|
|
||||||
_a_borderShown.start([=] { update(); }, 0., 1., duration);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_a_borderOpacity.start([=] { update(); }, 1., 0., duration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FillSendUntilOnlineMenu(
|
void FillSendUntilOnlineMenu(
|
||||||
not_null<Ui::IconButton*> button,
|
not_null<Ui::IconButton*> button,
|
||||||
Fn<void()> callback) {
|
Fn<void()> callback) {
|
||||||
|
@ -601,138 +57,6 @@ bool CanScheduleUntilOnline(not_null<PeerData*> peer) {
|
||||||
&& (peer->asUser()->onlineTill > 0);
|
&& (peer->asUser()->onlineTill > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
|
||||||
not_null<Ui::GenericBox*> box,
|
|
||||||
rpl::producer<QString> title,
|
|
||||||
rpl::producer<QString> submit,
|
|
||||||
Fn<void(TimeId)> done,
|
|
||||||
TimeId time) {
|
|
||||||
box->setTitle(std::move(title));
|
|
||||||
box->setWidth(st::boxWideWidth);
|
|
||||||
|
|
||||||
const auto date = Ui::CreateChild<rpl::variable<QDate>>(
|
|
||||||
box.get(),
|
|
||||||
base::unixtime::parse(time).date());
|
|
||||||
const auto content = box->addRow(
|
|
||||||
object_ptr<Ui::FixedHeightWidget>(box, st::scheduleHeight));
|
|
||||||
const auto dayInput = Ui::CreateChild<Ui::InputField>(
|
|
||||||
content,
|
|
||||||
st::scheduleDateField);
|
|
||||||
const auto timeInput = Ui::CreateChild<TimeInput>(
|
|
||||||
content,
|
|
||||||
TimeString(time));
|
|
||||||
const auto at = Ui::CreateChild<Ui::FlatLabel>(
|
|
||||||
content,
|
|
||||||
tr::lng_schedule_at(),
|
|
||||||
st::scheduleAtLabel);
|
|
||||||
|
|
||||||
date->value(
|
|
||||||
) | rpl::start_with_next([=](QDate date) {
|
|
||||||
dayInput->setText(DayString(date));
|
|
||||||
timeInput->setFocusFast();
|
|
||||||
}, dayInput->lifetime());
|
|
||||||
|
|
||||||
const auto minDate = QDate::currentDate();
|
|
||||||
const auto maxDate = minDate.addYears(1).addDays(-1);
|
|
||||||
|
|
||||||
const auto &dayViewport = dayInput->rawTextEdit()->viewport();
|
|
||||||
base::install_event_filter(dayViewport, [=](not_null<QEvent*> event) {
|
|
||||||
if (event->type() == QEvent::Wheel) {
|
|
||||||
const auto e = static_cast<QWheelEvent*>(event.get());
|
|
||||||
const auto direction = ProcessWheelEvent(e);
|
|
||||||
if (!direction) {
|
|
||||||
return base::EventFilterResult::Continue;
|
|
||||||
}
|
|
||||||
const auto d = date->current().addDays(direction);
|
|
||||||
*date = std::clamp(d, minDate, maxDate);
|
|
||||||
return base::EventFilterResult::Cancel;
|
|
||||||
}
|
|
||||||
return base::EventFilterResult::Continue;
|
|
||||||
});
|
|
||||||
|
|
||||||
content->widthValue(
|
|
||||||
) | rpl::start_with_next([=](int width) {
|
|
||||||
const auto paddings = width
|
|
||||||
- at->width()
|
|
||||||
- 2 * st::scheduleAtSkip
|
|
||||||
- st::scheduleDateWidth
|
|
||||||
- st::scheduleTimeWidth;
|
|
||||||
const auto left = paddings / 2;
|
|
||||||
dayInput->resizeToWidth(st::scheduleDateWidth);
|
|
||||||
dayInput->moveToLeft(left, st::scheduleDateTop, width);
|
|
||||||
at->moveToLeft(
|
|
||||||
left + st::scheduleDateWidth + st::scheduleAtSkip,
|
|
||||||
st::scheduleAtTop,
|
|
||||||
width);
|
|
||||||
timeInput->resizeToWidth(st::scheduleTimeWidth);
|
|
||||||
timeInput->moveToLeft(
|
|
||||||
width - left - st::scheduleTimeWidth,
|
|
||||||
st::scheduleDateTop,
|
|
||||||
width);
|
|
||||||
}, content->lifetime());
|
|
||||||
|
|
||||||
const auto calendar =
|
|
||||||
content->lifetime().make_state<QPointer<CalendarBox>>();
|
|
||||||
QObject::connect(dayInput, &Ui::InputField::focused, [=] {
|
|
||||||
if (*calendar) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto chosen = [=](QDate chosen) {
|
|
||||||
*date = chosen;
|
|
||||||
(*calendar)->closeBox();
|
|
||||||
};
|
|
||||||
const auto finalize = [=](not_null<CalendarBox*> box) {
|
|
||||||
box->setMinDate(minDate);
|
|
||||||
box->setMaxDate(maxDate);
|
|
||||||
};
|
|
||||||
*calendar = box->getDelegate()->show(Box<CalendarBox>(
|
|
||||||
date->current(),
|
|
||||||
date->current(),
|
|
||||||
crl::guard(box, chosen),
|
|
||||||
finalize));
|
|
||||||
(*calendar)->boxClosing(
|
|
||||||
) | rpl::start_with_next(crl::guard(timeInput, [=] {
|
|
||||||
timeInput->setFocusFast();
|
|
||||||
}), (*calendar)->lifetime());
|
|
||||||
});
|
|
||||||
|
|
||||||
const auto collect = [=] {
|
|
||||||
const auto timeValue = timeInput->valueCurrent().split(':');
|
|
||||||
if (timeValue.size() != 2) {
|
|
||||||
timeInput->showError();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const auto time = QTime(timeValue[0].toInt(), timeValue[1].toInt());
|
|
||||||
if (!time.isValid()) {
|
|
||||||
timeInput->showError();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const auto result = base::unixtime::serialize(
|
|
||||||
QDateTime(date->current(), time));
|
|
||||||
if (result <= base::unixtime::now() + kMinimalSchedule) {
|
|
||||||
timeInput->showError();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
const auto save = [=] {
|
|
||||||
if (const auto result = collect()) {
|
|
||||||
done(result);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
timeInput->submitRequests(
|
|
||||||
) | rpl::start_with_next(
|
|
||||||
save,
|
|
||||||
timeInput->lifetime());
|
|
||||||
|
|
||||||
auto result = ChooseDateTimeBoxDescriptor();
|
|
||||||
box->setFocusCallback([=] { timeInput->setFocusFast(); });
|
|
||||||
result.submit = box->addButton(std::move(submit), save);
|
|
||||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScheduleBox(
|
void ScheduleBox(
|
||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
SendMenu::Type type,
|
SendMenu::Type type,
|
||||||
|
@ -752,7 +76,7 @@ void ScheduleBox(
|
||||||
box->closeBox();
|
box->closeBox();
|
||||||
copy(result);
|
copy(result);
|
||||||
};
|
};
|
||||||
auto descriptor = ChooseDateTimeBox(
|
auto descriptor = Ui::ChooseDateTimeBox(
|
||||||
box,
|
box,
|
||||||
(type == SendMenu::Type::Reminder
|
(type == SendMenu::Type::Reminder
|
||||||
? tr::lng_remind_title()
|
? tr::lng_remind_title()
|
||||||
|
|
|
@ -22,18 +22,6 @@ namespace HistoryView {
|
||||||
[[nodiscard]] TimeId DefaultScheduleTime();
|
[[nodiscard]] TimeId DefaultScheduleTime();
|
||||||
[[nodiscard]] bool CanScheduleUntilOnline(not_null<PeerData*> peer);
|
[[nodiscard]] bool CanScheduleUntilOnline(not_null<PeerData*> peer);
|
||||||
|
|
||||||
struct ChooseDateTimeBoxDescriptor {
|
|
||||||
QPointer<Ui::RoundButton> submit;
|
|
||||||
Fn<TimeId()> collect;
|
|
||||||
};
|
|
||||||
|
|
||||||
ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
|
||||||
not_null<Ui::GenericBox*> box,
|
|
||||||
rpl::producer<QString> title,
|
|
||||||
rpl::producer<QString> submit,
|
|
||||||
Fn<void(TimeId)> done,
|
|
||||||
TimeId time);
|
|
||||||
|
|
||||||
void ScheduleBox(
|
void ScheduleBox(
|
||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
SendMenu::Type type,
|
SendMenu::Type type,
|
||||||
|
|
|
@ -5,15 +5,16 @@ the official desktop application for the Telegram messaging service.
|
||||||
For license and copyright information please follow this link:
|
For license and copyright information please follow this link:
|
||||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "boxes/calendar_box.h"
|
#include "ui/boxes/calendar_box.h"
|
||||||
|
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "lang/lang_keys.h"
|
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
#include "ui/ui_utility.h"
|
#include "ui/ui_utility.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "styles/style_dialogs.h"
|
#include "styles/style_dialogs.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kDaysInWeek = 7;
|
constexpr auto kDaysInWeek = 7;
|
||||||
|
@ -229,7 +230,7 @@ private:
|
||||||
const style::CalendarSizes &_st;
|
const style::CalendarSizes &_st;
|
||||||
not_null<Context*> _context;
|
not_null<Context*> _context;
|
||||||
|
|
||||||
std::map<int, std::unique_ptr<Ui::RippleAnimation>> _ripples;
|
std::map<int, std::unique_ptr<RippleAnimation>> _ripples;
|
||||||
|
|
||||||
Fn<void(QDate)> _dateChosenCallback;
|
Fn<void(QDate)> _dateChosenCallback;
|
||||||
|
|
||||||
|
@ -257,7 +258,7 @@ void CalendarBox::Inner::monthChanged(QDate month) {
|
||||||
_ripples.clear();
|
_ripples.clear();
|
||||||
resizeToCurrent();
|
resizeToCurrent();
|
||||||
update();
|
update();
|
||||||
Ui::SendSynteticMouseEvent(this, QEvent::MouseMove, Qt::NoButton);
|
SendSynteticMouseEvent(this, QEvent::MouseMove, Qt::NoButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CalendarBox::Inner::resizeToCurrent() {
|
void CalendarBox::Inner::resizeToCurrent() {
|
||||||
|
@ -389,9 +390,9 @@ void CalendarBox::Inner::mousePressEvent(QMouseEvent *e) {
|
||||||
auto cell = QRect(rowsLeft() + col * _st.cellSize.width(), rowsTop() + row * _st.cellSize.height(), _st.cellSize.width(), _st.cellSize.height());
|
auto cell = QRect(rowsLeft() + col * _st.cellSize.width(), rowsTop() + row * _st.cellSize.height(), _st.cellSize.width(), _st.cellSize.height());
|
||||||
auto it = _ripples.find(_selected);
|
auto it = _ripples.find(_selected);
|
||||||
if (it == _ripples.cend()) {
|
if (it == _ripples.cend()) {
|
||||||
auto mask = Ui::RippleAnimation::ellipseMask(QSize(_st.cellInner, _st.cellInner));
|
auto mask = RippleAnimation::ellipseMask(QSize(_st.cellInner, _st.cellInner));
|
||||||
auto update = [this, cell] { rtlupdate(cell); };
|
auto update = [this, cell] { rtlupdate(cell); };
|
||||||
it = _ripples.emplace(_selected, std::make_unique<Ui::RippleAnimation>(st::defaultRippleAnimation, std::move(mask), std::move(update))).first;
|
it = _ripples.emplace(_selected, std::make_unique<RippleAnimation>(st::defaultRippleAnimation, std::move(mask), std::move(update))).first;
|
||||||
}
|
}
|
||||||
auto ripplePosition = QPoint(cell.x() + (_st.cellSize.width() - _st.cellInner) / 2, cell.y() + (_st.cellSize.height() - _st.cellInner) / 2);
|
auto ripplePosition = QPoint(cell.x() + (_st.cellSize.width() - _st.cellInner) / 2, cell.y() + (_st.cellSize.height() - _st.cellInner) / 2);
|
||||||
it->second->add(e->pos() - ripplePosition);
|
it->second->add(e->pos() - ripplePosition);
|
||||||
|
@ -611,3 +612,5 @@ void CalendarBox::wheelEvent(QWheelEvent *e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
CalendarBox::~CalendarBox() = default;
|
CalendarBox::~CalendarBox() = default;
|
||||||
|
|
||||||
|
} // namespace Ui
|
|
@ -7,17 +7,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "abstract_box.h"
|
#include "ui/layers/box_content.h"
|
||||||
|
#include "base/observer.h"
|
||||||
|
|
||||||
namespace style {
|
namespace style {
|
||||||
struct CalendarSizes;
|
struct CalendarSizes;
|
||||||
} // namespace style
|
} // namespace style
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class IconButton;
|
|
||||||
} // namespace Ui
|
|
||||||
|
|
||||||
class CalendarBox : public Ui::BoxContent, private base::Subscriber {
|
class IconButton;
|
||||||
|
|
||||||
|
class CalendarBox : public BoxContent, private base::Subscriber {
|
||||||
public:
|
public:
|
||||||
CalendarBox(
|
CalendarBox(
|
||||||
QWidget*,
|
QWidget*,
|
||||||
|
@ -67,10 +68,12 @@ private:
|
||||||
|
|
||||||
class Title;
|
class Title;
|
||||||
object_ptr<Title> _title;
|
object_ptr<Title> _title;
|
||||||
object_ptr<Ui::IconButton> _previous;
|
object_ptr<IconButton> _previous;
|
||||||
object_ptr<Ui::IconButton> _next;
|
object_ptr<IconButton> _next;
|
||||||
|
|
||||||
Fn<void(QDate date)> _callback;
|
Fn<void(QDate date)> _callback;
|
||||||
FnMut<void(not_null<CalendarBox*>)> _finalize;
|
FnMut<void(not_null<CalendarBox*>)> _finalize;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // namespace Ui
|
702
Telegram/SourceFiles/ui/boxes/choose_date_time.cpp
Normal file
702
Telegram/SourceFiles/ui/boxes/choose_date_time.cpp
Normal file
|
@ -0,0 +1,702 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "ui/boxes/choose_date_time.h"
|
||||||
|
|
||||||
|
#include "base/unixtime.h"
|
||||||
|
#include "base/event_filter.h"
|
||||||
|
#include "ui/boxes/calendar_box.h"
|
||||||
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/input_fields.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "styles/style_layers.h"
|
||||||
|
#include "styles/style_boxes.h"
|
||||||
|
|
||||||
|
#include <QtCore/QRegularExpression>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kMinimalSchedule = TimeId(10);
|
||||||
|
|
||||||
|
tr::phrase<> MonthDay(int index) {
|
||||||
|
switch (index) {
|
||||||
|
case 1: return tr::lng_month_day1;
|
||||||
|
case 2: return tr::lng_month_day2;
|
||||||
|
case 3: return tr::lng_month_day3;
|
||||||
|
case 4: return tr::lng_month_day4;
|
||||||
|
case 5: return tr::lng_month_day5;
|
||||||
|
case 6: return tr::lng_month_day6;
|
||||||
|
case 7: return tr::lng_month_day7;
|
||||||
|
case 8: return tr::lng_month_day8;
|
||||||
|
case 9: return tr::lng_month_day9;
|
||||||
|
case 10: return tr::lng_month_day10;
|
||||||
|
case 11: return tr::lng_month_day11;
|
||||||
|
case 12: return tr::lng_month_day12;
|
||||||
|
}
|
||||||
|
Unexpected("Index in MonthDay.");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DayString(const QDate &date) {
|
||||||
|
return tr::lng_month_day(
|
||||||
|
tr::now,
|
||||||
|
lt_month,
|
||||||
|
MonthDay(date.month())(tr::now),
|
||||||
|
lt_day,
|
||||||
|
QString::number(date.day()));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TimeString(TimeId time) {
|
||||||
|
const auto parsed = base::unixtime::parse(time).time();
|
||||||
|
return QString("%1:%2"
|
||||||
|
).arg(parsed.hour()
|
||||||
|
).arg(parsed.minute(), 2, 10, QLatin1Char('0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
int ProcessWheelEvent(not_null<QWheelEvent*> e) {
|
||||||
|
// Only a mouse wheel is accepted.
|
||||||
|
constexpr auto step = static_cast<int>(QWheelEvent::DefaultDeltasPerStep);
|
||||||
|
const auto delta = e->angleDelta().y();
|
||||||
|
const auto absDelta = std::abs(delta);
|
||||||
|
if (absDelta != step) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (delta / absDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TimePart final : public MaskedInputField {
|
||||||
|
public:
|
||||||
|
using MaskedInputField::MaskedInputField;
|
||||||
|
|
||||||
|
void setMaxValue(int value);
|
||||||
|
void setWheelStep(int value);
|
||||||
|
|
||||||
|
rpl::producer<> erasePrevious() const;
|
||||||
|
rpl::producer<QChar> putNext() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void keyPressEvent(QKeyEvent *e) override;
|
||||||
|
void wheelEvent(QWheelEvent *e) override;
|
||||||
|
|
||||||
|
void correctValue(
|
||||||
|
const QString &was,
|
||||||
|
int wasCursor,
|
||||||
|
QString &now,
|
||||||
|
int &nowCursor) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int _maxValue = 0;
|
||||||
|
int _maxDigits = 0;
|
||||||
|
int _wheelStep = 0;
|
||||||
|
rpl::event_stream<> _erasePrevious;
|
||||||
|
rpl::event_stream<QChar> _putNext;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
int Number(not_null<TimePart*> field) {
|
||||||
|
const auto text = field->getLastText();
|
||||||
|
auto ref = text.midRef(0);
|
||||||
|
while (!ref.isEmpty() && ref.at(0) == '0') {
|
||||||
|
ref = ref.mid(1);
|
||||||
|
}
|
||||||
|
return ref.toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TimeInput final : public RpWidget {
|
||||||
|
public:
|
||||||
|
TimeInput(QWidget *parent, const QString &value);
|
||||||
|
|
||||||
|
bool setFocusFast();
|
||||||
|
rpl::producer<QString> value() const;
|
||||||
|
rpl::producer<> submitRequests() const;
|
||||||
|
QString valueCurrent() const;
|
||||||
|
void showError();
|
||||||
|
|
||||||
|
int resizeGetHeight(int width) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent *e) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setInnerFocus();
|
||||||
|
void putNext(const object_ptr<TimePart> &field, QChar ch);
|
||||||
|
void erasePrevious(const object_ptr<TimePart> &field);
|
||||||
|
void finishInnerAnimating();
|
||||||
|
void setErrorShown(bool error);
|
||||||
|
void setFocused(bool focused);
|
||||||
|
void startBorderAnimation();
|
||||||
|
template <typename Widget>
|
||||||
|
bool insideSeparator(QPoint position, const Widget &widget) const;
|
||||||
|
|
||||||
|
int hour() const;
|
||||||
|
int minute() const;
|
||||||
|
|
||||||
|
object_ptr<TimePart> _hour;
|
||||||
|
object_ptr<PaddingWrap<FlatLabel>> _separator1;
|
||||||
|
object_ptr<TimePart> _minute;
|
||||||
|
rpl::variable<QString> _value;
|
||||||
|
rpl::event_stream<> _submitRequests;
|
||||||
|
|
||||||
|
style::cursor _cursor = style::cur_default;
|
||||||
|
Animations::Simple _a_borderShown;
|
||||||
|
int _borderAnimationStart = 0;
|
||||||
|
Animations::Simple _a_borderOpacity;
|
||||||
|
bool _borderVisible = false;
|
||||||
|
|
||||||
|
Animations::Simple _a_error;
|
||||||
|
bool _error = false;
|
||||||
|
Animations::Simple _a_focused;
|
||||||
|
bool _focused = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
QTime ValidateTime(const QString &value) {
|
||||||
|
const auto match = QRegularExpression(
|
||||||
|
"^(\\d{1,2})\\:(\\d\\d)$").match(value);
|
||||||
|
if (!match.hasMatch()) {
|
||||||
|
return QTime();
|
||||||
|
}
|
||||||
|
const auto readInt = [](const QString &value) {
|
||||||
|
auto ref = value.midRef(0);
|
||||||
|
while (!ref.isEmpty() && ref.at(0) == '0') {
|
||||||
|
ref = ref.mid(1);
|
||||||
|
}
|
||||||
|
return ref.toInt();
|
||||||
|
};
|
||||||
|
return QTime(readInt(match.captured(1)), readInt(match.captured(2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString GetHour(const QString &value) {
|
||||||
|
if (const auto time = ValidateTime(value); time.isValid()) {
|
||||||
|
return QString::number(time.hour());
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString GetMinute(const QString &value) {
|
||||||
|
if (const auto time = ValidateTime(value); time.isValid()) {
|
||||||
|
return QString("%1").arg(time.minute(), 2, 10, QChar('0'));
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimePart::setMaxValue(int value) {
|
||||||
|
_maxValue = value;
|
||||||
|
_maxDigits = 0;
|
||||||
|
while (value > 0) {
|
||||||
|
++_maxDigits;
|
||||||
|
value /= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimePart::setWheelStep(int value) {
|
||||||
|
_wheelStep = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> TimePart::erasePrevious() const {
|
||||||
|
return _erasePrevious.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<QChar> TimePart::putNext() const {
|
||||||
|
return _putNext.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimePart::keyPressEvent(QKeyEvent *e) {
|
||||||
|
const auto isBackspace = (e->key() == Qt::Key_Backspace);
|
||||||
|
const auto isBeginning = (cursorPosition() == 0);
|
||||||
|
if (isBackspace && isBeginning && !hasSelectedText()) {
|
||||||
|
_erasePrevious.fire({});
|
||||||
|
} else {
|
||||||
|
MaskedInputField::keyPressEvent(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimePart::wheelEvent(QWheelEvent *e) {
|
||||||
|
const auto direction = ProcessWheelEvent(e);
|
||||||
|
auto time = Number(this) + (direction * _wheelStep);
|
||||||
|
const auto max = _maxValue + 1;
|
||||||
|
if (time < 0) {
|
||||||
|
time += max;
|
||||||
|
} else if (time >= max) {
|
||||||
|
time -= max;
|
||||||
|
}
|
||||||
|
setText(QString::number(time));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimePart::correctValue(
|
||||||
|
const QString &was,
|
||||||
|
int wasCursor,
|
||||||
|
QString &now,
|
||||||
|
int &nowCursor) {
|
||||||
|
auto newText = QString();
|
||||||
|
auto newCursor = -1;
|
||||||
|
const auto oldCursor = nowCursor;
|
||||||
|
const auto oldLength = now.size();
|
||||||
|
auto accumulated = 0;
|
||||||
|
auto limit = 0;
|
||||||
|
for (; limit != oldLength; ++limit) {
|
||||||
|
if (now[limit].isDigit()) {
|
||||||
|
accumulated *= 10;
|
||||||
|
accumulated += (now[limit].unicode() - '0');
|
||||||
|
if (accumulated > _maxValue || limit == _maxDigits) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto i = 0; i != limit;) {
|
||||||
|
if (now[i].isDigit()) {
|
||||||
|
newText += now[i];
|
||||||
|
}
|
||||||
|
if (++i == oldCursor) {
|
||||||
|
newCursor = newText.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newCursor < 0) {
|
||||||
|
newCursor = newText.size();
|
||||||
|
}
|
||||||
|
if (newText != now) {
|
||||||
|
now = newText;
|
||||||
|
setText(now);
|
||||||
|
startPlaceholderAnimation();
|
||||||
|
}
|
||||||
|
if (newCursor != nowCursor) {
|
||||||
|
nowCursor = newCursor;
|
||||||
|
setCursorPosition(nowCursor);
|
||||||
|
}
|
||||||
|
if (accumulated > _maxValue
|
||||||
|
|| (limit == _maxDigits && oldLength > _maxDigits)) {
|
||||||
|
if (oldCursor > limit) {
|
||||||
|
_putNext.fire('0' + (accumulated % 10));
|
||||||
|
} else {
|
||||||
|
_putNext.fire(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeInput::TimeInput(QWidget *parent, const QString &value)
|
||||||
|
: RpWidget(parent)
|
||||||
|
, _hour(
|
||||||
|
this,
|
||||||
|
st::scheduleTimeField,
|
||||||
|
rpl::never<QString>(),
|
||||||
|
GetHour(value))
|
||||||
|
, _separator1(
|
||||||
|
this,
|
||||||
|
object_ptr<FlatLabel>(
|
||||||
|
this,
|
||||||
|
QString(":"),
|
||||||
|
st::scheduleTimeSeparator),
|
||||||
|
st::scheduleTimeSeparatorPadding)
|
||||||
|
, _minute(
|
||||||
|
this,
|
||||||
|
st::scheduleTimeField,
|
||||||
|
rpl::never<QString>(),
|
||||||
|
GetMinute(value))
|
||||||
|
, _value(valueCurrent()) {
|
||||||
|
const auto focused = [=](const object_ptr<TimePart> &field) {
|
||||||
|
return [this, pointer = MakeWeak(field.data())]{
|
||||||
|
_borderAnimationStart = pointer->borderAnimationStart()
|
||||||
|
+ pointer->x()
|
||||||
|
- _hour->x();
|
||||||
|
setFocused(true);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const auto blurred = [=] {
|
||||||
|
setFocused(false);
|
||||||
|
};
|
||||||
|
const auto changed = [=] {
|
||||||
|
_value = valueCurrent();
|
||||||
|
};
|
||||||
|
connect(_hour, &MaskedInputField::focused, focused(_hour));
|
||||||
|
connect(_minute, &MaskedInputField::focused, focused(_minute));
|
||||||
|
connect(_hour, &MaskedInputField::blurred, blurred);
|
||||||
|
connect(_minute, &MaskedInputField::blurred, blurred);
|
||||||
|
connect(_hour, &MaskedInputField::changed, changed);
|
||||||
|
connect(_minute, &MaskedInputField::changed, changed);
|
||||||
|
_hour->setMaxValue(23);
|
||||||
|
_hour->setWheelStep(1);
|
||||||
|
_hour->putNext() | rpl::start_with_next([=](QChar ch) {
|
||||||
|
putNext(_minute, ch);
|
||||||
|
}, lifetime());
|
||||||
|
_minute->setMaxValue(59);
|
||||||
|
_minute->setWheelStep(10);
|
||||||
|
_minute->erasePrevious() | rpl::start_with_next([=] {
|
||||||
|
erasePrevious(_hour);
|
||||||
|
}, lifetime());
|
||||||
|
_separator1->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
setMouseTracking(true);
|
||||||
|
|
||||||
|
_value.changes(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
setErrorShown(false);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
const auto submitHour = [=] {
|
||||||
|
if (hour()) {
|
||||||
|
_minute->setFocus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const auto submitMinute = [=] {
|
||||||
|
if (minute()) {
|
||||||
|
if (hour()) {
|
||||||
|
_submitRequests.fire({});
|
||||||
|
} else {
|
||||||
|
_hour->setFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
connect(
|
||||||
|
_hour,
|
||||||
|
&MaskedInputField::submitted,
|
||||||
|
submitHour);
|
||||||
|
connect(
|
||||||
|
_minute,
|
||||||
|
&MaskedInputField::submitted,
|
||||||
|
submitMinute);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::putNext(const object_ptr<TimePart> &field, QChar ch) {
|
||||||
|
field->setCursorPosition(0);
|
||||||
|
if (ch.unicode()) {
|
||||||
|
field->setText(ch + field->getLastText());
|
||||||
|
field->setCursorPosition(1);
|
||||||
|
}
|
||||||
|
field->setFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::erasePrevious(const object_ptr<TimePart> &field) {
|
||||||
|
const auto text = field->getLastText();
|
||||||
|
if (!text.isEmpty()) {
|
||||||
|
field->setCursorPosition(text.size() - 1);
|
||||||
|
field->setText(text.mid(0, text.size() - 1));
|
||||||
|
}
|
||||||
|
field->setFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimeInput::setFocusFast() {
|
||||||
|
if (hour()) {
|
||||||
|
_minute->setFocusFast();
|
||||||
|
} else {
|
||||||
|
_hour->setFocusFast();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TimeInput::hour() const {
|
||||||
|
return Number(_hour);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TimeInput::minute() const {
|
||||||
|
return Number(_minute);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TimeInput::valueCurrent() const {
|
||||||
|
const auto result = QString("%1:%2"
|
||||||
|
).arg(hour()
|
||||||
|
).arg(minute(), 2, 10, QChar('0'));
|
||||||
|
return ValidateTime(result).isValid() ? result : QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<QString> TimeInput::value() const {
|
||||||
|
return _value.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> TimeInput::submitRequests() const {
|
||||||
|
return _submitRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::paintEvent(QPaintEvent *e) {
|
||||||
|
Painter p(this);
|
||||||
|
|
||||||
|
const auto &_st = st::scheduleDateField;
|
||||||
|
const auto height = _st.heightMin;
|
||||||
|
if (_st.border) {
|
||||||
|
p.fillRect(0, height - _st.border, width(), _st.border, _st.borderFg);
|
||||||
|
}
|
||||||
|
auto errorDegree = _a_error.value(_error ? 1. : 0.);
|
||||||
|
auto focusedDegree = _a_focused.value(_focused ? 1. : 0.);
|
||||||
|
auto borderShownDegree = _a_borderShown.value(1.);
|
||||||
|
auto borderOpacity = _a_borderOpacity.value(_borderVisible ? 1. : 0.);
|
||||||
|
if (_st.borderActive && (borderOpacity > 0.)) {
|
||||||
|
auto borderStart = std::clamp(_borderAnimationStart, 0, width());
|
||||||
|
auto borderFrom = qRound(borderStart * (1. - borderShownDegree));
|
||||||
|
auto borderTo = borderStart + qRound((width() - borderStart) * borderShownDegree);
|
||||||
|
if (borderTo > borderFrom) {
|
||||||
|
auto borderFg = anim::brush(_st.borderFgActive, _st.borderFgError, errorDegree);
|
||||||
|
p.setOpacity(borderOpacity);
|
||||||
|
p.fillRect(borderFrom, height - _st.borderActive, borderTo - borderFrom, _st.borderActive, borderFg);
|
||||||
|
p.setOpacity(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Widget>
|
||||||
|
bool TimeInput::insideSeparator(QPoint position, const Widget &widget) const {
|
||||||
|
const auto x = position.x();
|
||||||
|
const auto y = position.y();
|
||||||
|
return (x >= widget->x() && x < widget->x() + widget->width())
|
||||||
|
&& (y >= _hour->y() && y < _hour->y() + _hour->height());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::mouseMoveEvent(QMouseEvent *e) {
|
||||||
|
const auto cursor = insideSeparator(e->pos(), _separator1)
|
||||||
|
? style::cur_text
|
||||||
|
: style::cur_default;
|
||||||
|
if (_cursor != cursor) {
|
||||||
|
_cursor = cursor;
|
||||||
|
setCursor(_cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::mousePressEvent(QMouseEvent *e) {
|
||||||
|
const auto x = e->pos().x();
|
||||||
|
const auto focus1 = [&] {
|
||||||
|
if (_hour->getLastText().size() > 1) {
|
||||||
|
_minute->setFocus();
|
||||||
|
} else {
|
||||||
|
_hour->setFocus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (insideSeparator(e->pos(), _separator1)) {
|
||||||
|
focus1();
|
||||||
|
_borderAnimationStart = x - _hour->x();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TimeInput::resizeGetHeight(int width) {
|
||||||
|
const auto &_st = st::scheduleTimeField;
|
||||||
|
const auto &font = _st.placeholderFont;
|
||||||
|
const auto addToWidth = st::scheduleTimeSeparatorPadding.left();
|
||||||
|
const auto hourWidth = _st.textMargins.left()
|
||||||
|
+ _st.placeholderMargins.left()
|
||||||
|
+ font->width(QString("23"))
|
||||||
|
+ _st.placeholderMargins.right()
|
||||||
|
+ _st.textMargins.right()
|
||||||
|
+ addToWidth;
|
||||||
|
const auto minuteWidth = _st.textMargins.left()
|
||||||
|
+ _st.placeholderMargins.left()
|
||||||
|
+ font->width(QString("59"))
|
||||||
|
+ _st.placeholderMargins.right()
|
||||||
|
+ _st.textMargins.right()
|
||||||
|
+ addToWidth;
|
||||||
|
const auto full = hourWidth
|
||||||
|
- addToWidth
|
||||||
|
+ _separator1->width()
|
||||||
|
+ minuteWidth
|
||||||
|
- addToWidth;
|
||||||
|
auto left = (width - full) / 2;
|
||||||
|
auto top = 0;
|
||||||
|
_hour->setGeometry(left, top, hourWidth, _hour->height());
|
||||||
|
left += hourWidth - addToWidth;
|
||||||
|
_separator1->resizeToNaturalWidth(width);
|
||||||
|
_separator1->move(left, top);
|
||||||
|
left += _separator1->width();
|
||||||
|
_minute->setGeometry(left, top, minuteWidth, _minute->height());
|
||||||
|
return st::scheduleDateField.heightMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::showError() {
|
||||||
|
setErrorShown(true);
|
||||||
|
if (!_focused) {
|
||||||
|
setInnerFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::setInnerFocus() {
|
||||||
|
if (hour()) {
|
||||||
|
_minute->setFocus();
|
||||||
|
} else {
|
||||||
|
_hour->setFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::setErrorShown(bool error) {
|
||||||
|
if (_error != error) {
|
||||||
|
_error = error;
|
||||||
|
_a_error.start(
|
||||||
|
[=] { update(); },
|
||||||
|
_error ? 0. : 1.,
|
||||||
|
_error ? 1. : 0.,
|
||||||
|
st::scheduleDateField.duration);
|
||||||
|
startBorderAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::setFocused(bool focused) {
|
||||||
|
if (_focused != focused) {
|
||||||
|
_focused = focused;
|
||||||
|
_a_focused.start(
|
||||||
|
[=] { update(); },
|
||||||
|
_focused ? 0. : 1.,
|
||||||
|
_focused ? 1. : 0.,
|
||||||
|
st::scheduleDateField.duration);
|
||||||
|
startBorderAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::finishInnerAnimating() {
|
||||||
|
_hour->finishAnimating();
|
||||||
|
_minute->finishAnimating();
|
||||||
|
_a_borderOpacity.stop();
|
||||||
|
_a_borderShown.stop();
|
||||||
|
_a_error.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeInput::startBorderAnimation() {
|
||||||
|
auto borderVisible = (_error || _focused);
|
||||||
|
if (_borderVisible != borderVisible) {
|
||||||
|
_borderVisible = borderVisible;
|
||||||
|
const auto duration = st::scheduleDateField.duration;
|
||||||
|
if (_borderVisible) {
|
||||||
|
if (_a_borderOpacity.animating()) {
|
||||||
|
_a_borderOpacity.start([=] { update(); }, 0., 1., duration);
|
||||||
|
} else {
|
||||||
|
_a_borderShown.start([=] { update(); }, 0., 1., duration);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_a_borderOpacity.start([=] { update(); }, 1., 0., duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||||
|
not_null<GenericBox*> box,
|
||||||
|
rpl::producer<QString> title,
|
||||||
|
rpl::producer<QString> submit,
|
||||||
|
Fn<void(TimeId)> done,
|
||||||
|
TimeId time) {
|
||||||
|
box->setTitle(std::move(title));
|
||||||
|
box->setWidth(st::boxWideWidth);
|
||||||
|
|
||||||
|
const auto date = CreateChild<rpl::variable<QDate>>(
|
||||||
|
box.get(),
|
||||||
|
base::unixtime::parse(time).date());
|
||||||
|
const auto content = box->addRow(
|
||||||
|
object_ptr<FixedHeightWidget>(box, st::scheduleHeight));
|
||||||
|
const auto dayInput = CreateChild<InputField>(
|
||||||
|
content,
|
||||||
|
st::scheduleDateField);
|
||||||
|
const auto timeInput = CreateChild<TimeInput>(
|
||||||
|
content,
|
||||||
|
TimeString(time));
|
||||||
|
const auto at = CreateChild<FlatLabel>(
|
||||||
|
content,
|
||||||
|
tr::lng_schedule_at(),
|
||||||
|
st::scheduleAtLabel);
|
||||||
|
|
||||||
|
date->value(
|
||||||
|
) | rpl::start_with_next([=](QDate date) {
|
||||||
|
dayInput->setText(DayString(date));
|
||||||
|
timeInput->setFocusFast();
|
||||||
|
}, dayInput->lifetime());
|
||||||
|
|
||||||
|
const auto minDate = QDate::currentDate();
|
||||||
|
const auto maxDate = minDate.addYears(1).addDays(-1);
|
||||||
|
|
||||||
|
const auto &dayViewport = dayInput->rawTextEdit()->viewport();
|
||||||
|
base::install_event_filter(dayViewport, [=](not_null<QEvent*> event) {
|
||||||
|
if (event->type() == QEvent::Wheel) {
|
||||||
|
const auto e = static_cast<QWheelEvent*>(event.get());
|
||||||
|
const auto direction = ProcessWheelEvent(e);
|
||||||
|
if (!direction) {
|
||||||
|
return base::EventFilterResult::Continue;
|
||||||
|
}
|
||||||
|
const auto d = date->current().addDays(direction);
|
||||||
|
*date = std::clamp(d, minDate, maxDate);
|
||||||
|
return base::EventFilterResult::Cancel;
|
||||||
|
}
|
||||||
|
return base::EventFilterResult::Continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
content->widthValue(
|
||||||
|
) | rpl::start_with_next([=](int width) {
|
||||||
|
const auto paddings = width
|
||||||
|
- at->width()
|
||||||
|
- 2 * st::scheduleAtSkip
|
||||||
|
- st::scheduleDateWidth
|
||||||
|
- st::scheduleTimeWidth;
|
||||||
|
const auto left = paddings / 2;
|
||||||
|
dayInput->resizeToWidth(st::scheduleDateWidth);
|
||||||
|
dayInput->moveToLeft(left, st::scheduleDateTop, width);
|
||||||
|
at->moveToLeft(
|
||||||
|
left + st::scheduleDateWidth + st::scheduleAtSkip,
|
||||||
|
st::scheduleAtTop,
|
||||||
|
width);
|
||||||
|
timeInput->resizeToWidth(st::scheduleTimeWidth);
|
||||||
|
timeInput->moveToLeft(
|
||||||
|
width - left - st::scheduleTimeWidth,
|
||||||
|
st::scheduleDateTop,
|
||||||
|
width);
|
||||||
|
}, content->lifetime());
|
||||||
|
|
||||||
|
const auto calendar =
|
||||||
|
content->lifetime().make_state<QPointer<CalendarBox>>();
|
||||||
|
QObject::connect(dayInput, &InputField::focused, [=] {
|
||||||
|
if (*calendar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto chosen = [=](QDate chosen) {
|
||||||
|
*date = chosen;
|
||||||
|
(*calendar)->closeBox();
|
||||||
|
};
|
||||||
|
const auto finalize = [=](not_null<CalendarBox*> box) {
|
||||||
|
box->setMinDate(minDate);
|
||||||
|
box->setMaxDate(maxDate);
|
||||||
|
};
|
||||||
|
*calendar = box->getDelegate()->show(Box<CalendarBox>(
|
||||||
|
date->current(),
|
||||||
|
date->current(),
|
||||||
|
crl::guard(box, chosen),
|
||||||
|
finalize));
|
||||||
|
(*calendar)->boxClosing(
|
||||||
|
) | rpl::start_with_next(crl::guard(timeInput, [=] {
|
||||||
|
timeInput->setFocusFast();
|
||||||
|
}), (*calendar)->lifetime());
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto collect = [=] {
|
||||||
|
const auto timeValue = timeInput->valueCurrent().split(':');
|
||||||
|
if (timeValue.size() != 2) {
|
||||||
|
timeInput->showError();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const auto time = QTime(timeValue[0].toInt(), timeValue[1].toInt());
|
||||||
|
if (!time.isValid()) {
|
||||||
|
timeInput->showError();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const auto result = base::unixtime::serialize(
|
||||||
|
QDateTime(date->current(), time));
|
||||||
|
if (result <= base::unixtime::now() + kMinimalSchedule) {
|
||||||
|
timeInput->showError();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
const auto save = [=] {
|
||||||
|
if (const auto result = collect()) {
|
||||||
|
done(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
timeInput->submitRequests(
|
||||||
|
) | rpl::start_with_next(
|
||||||
|
save,
|
||||||
|
timeInput->lifetime());
|
||||||
|
|
||||||
|
auto result = ChooseDateTimeBoxDescriptor();
|
||||||
|
box->setFocusCallback([=] { timeInput->setFocusFast(); });
|
||||||
|
result.submit = box->addButton(std::move(submit), save);
|
||||||
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
28
Telegram/SourceFiles/ui/boxes/choose_date_time.h
Normal file
28
Telegram/SourceFiles/ui/boxes/choose_date_time.h
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/layers/generic_box.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
class RoundButton;
|
||||||
|
|
||||||
|
struct ChooseDateTimeBoxDescriptor {
|
||||||
|
QPointer<RoundButton> submit;
|
||||||
|
Fn<TimeId()> collect;
|
||||||
|
};
|
||||||
|
|
||||||
|
ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||||
|
not_null<GenericBox*> box,
|
||||||
|
rpl::producer<QString> title,
|
||||||
|
rpl::producer<QString> submit,
|
||||||
|
Fn<void(TimeId)> done,
|
||||||
|
TimeId time);
|
||||||
|
|
||||||
|
} // namespace Ui
|
290
Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp
Normal file
290
Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp
Normal file
|
@ -0,0 +1,290 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "ui/boxes/edit_invite_link.h"
|
||||||
|
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "ui/boxes/choose_date_time.h"
|
||||||
|
#include "ui/widgets/labels.h"
|
||||||
|
#include "ui/widgets/input_fields.h"
|
||||||
|
#include "ui/widgets/checkbox.h"
|
||||||
|
#include "base/unixtime.h"
|
||||||
|
#include "styles/style_settings.h"
|
||||||
|
#include "styles/style_layers.h"
|
||||||
|
#include "styles/style_info.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kMaxLimit = std::numeric_limits<int>::max();
|
||||||
|
constexpr auto kHour = 3600;
|
||||||
|
constexpr auto kDay = 86400;
|
||||||
|
|
||||||
|
[[nodiscard]] QString FormatExpireDate(TimeId date) {
|
||||||
|
if (date > 0) {
|
||||||
|
return langDateTime(base::unixtime::parse(date));
|
||||||
|
} else if (-date < kDay) {
|
||||||
|
return tr::lng_group_call_duration_hours(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
(-date / kHour));
|
||||||
|
} else if (-date < 7 * kDay) {
|
||||||
|
return tr::lng_group_call_duration_days(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
(-date / kDay));
|
||||||
|
} else {
|
||||||
|
return tr::lng_local_storage_limit_weeks(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
(-date / (7 * kDay)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void EditInviteLinkBox(
|
||||||
|
not_null<GenericBox*> box,
|
||||||
|
const InviteLinkFields &data,
|
||||||
|
Fn<void(InviteLinkFields)> done) {
|
||||||
|
const auto link = data.link;
|
||||||
|
box->setTitle(link.isEmpty()
|
||||||
|
? tr::lng_group_invite_new_title()
|
||||||
|
: tr::lng_group_invite_edit_title());
|
||||||
|
|
||||||
|
const auto container = box->verticalLayout();
|
||||||
|
const auto addTitle = [&](rpl::producer<QString> text) {
|
||||||
|
container->add(
|
||||||
|
object_ptr<FlatLabel>(
|
||||||
|
container,
|
||||||
|
std::move(text),
|
||||||
|
st::settingsSubsectionTitle),
|
||||||
|
st::settingsSubsectionTitlePadding);
|
||||||
|
};
|
||||||
|
const auto addDivider = [&](
|
||||||
|
rpl::producer<QString> text,
|
||||||
|
style::margins margins = style::margins()) {
|
||||||
|
container->add(
|
||||||
|
object_ptr<DividerLabel>(
|
||||||
|
container,
|
||||||
|
object_ptr<FlatLabel>(
|
||||||
|
container,
|
||||||
|
std::move(text),
|
||||||
|
st::boxDividerLabel),
|
||||||
|
st::settingsDividerLabelPadding),
|
||||||
|
margins);
|
||||||
|
};
|
||||||
|
|
||||||
|
addTitle(tr::lng_group_invite_expire_title());
|
||||||
|
const auto expiresWrap = container->add(
|
||||||
|
object_ptr<VerticalLayout>(container),
|
||||||
|
style::margins(0, 0, 0, st::settingsSectionSkip));
|
||||||
|
addDivider(
|
||||||
|
tr::lng_group_invite_expire_about(),
|
||||||
|
style::margins(0, 0, 0, st::settingsSectionSkip));
|
||||||
|
|
||||||
|
addTitle(tr::lng_group_invite_usage_title());
|
||||||
|
const auto usagesWrap = container->add(
|
||||||
|
object_ptr<VerticalLayout>(container),
|
||||||
|
style::margins(0, 0, 0, st::settingsSectionSkip));
|
||||||
|
addDivider(tr::lng_group_invite_usage_about());
|
||||||
|
|
||||||
|
static const auto addButton = [](
|
||||||
|
not_null<VerticalLayout*> container,
|
||||||
|
const std::shared_ptr<RadiobuttonGroup> &group,
|
||||||
|
int value,
|
||||||
|
const QString &text) {
|
||||||
|
return container->add(
|
||||||
|
object_ptr<Radiobutton>(
|
||||||
|
container,
|
||||||
|
group,
|
||||||
|
value,
|
||||||
|
text),
|
||||||
|
st::inviteLinkLimitMargin);
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto now = base::unixtime::now();
|
||||||
|
const auto expire = data.expireDate ? data.expireDate : kMaxLimit;
|
||||||
|
const auto expireGroup = std::make_shared<RadiobuttonGroup>(expire);
|
||||||
|
const auto usage = data.usageLimit ? data.usageLimit : kMaxLimit;
|
||||||
|
const auto usageGroup = std::make_shared<RadiobuttonGroup>(usage);
|
||||||
|
|
||||||
|
using Buttons = base::flat_map<int, base::unique_qptr<Radiobutton>>;
|
||||||
|
struct State {
|
||||||
|
Buttons expireButtons;
|
||||||
|
Buttons usageButtons;
|
||||||
|
int expireValue = 0;
|
||||||
|
int usageValue = 0;
|
||||||
|
};
|
||||||
|
const auto state = container->lifetime().make_state<State>(State{
|
||||||
|
.expireValue = expire,
|
||||||
|
.usageValue = usage
|
||||||
|
});
|
||||||
|
const auto regenerate = [=] {
|
||||||
|
expireGroup->setValue(state->expireValue);
|
||||||
|
usageGroup->setValue(state->usageValue);
|
||||||
|
|
||||||
|
auto expires = std::vector{ kMaxLimit, -kHour, -kDay, -kDay * 7, 0 };
|
||||||
|
auto usages = std::vector{ kMaxLimit, 1, 10, 100, 0 };
|
||||||
|
auto defaults = State();
|
||||||
|
for (auto i = begin(expires); i != end(expires); ++i) {
|
||||||
|
if (*i == state->expireValue) {
|
||||||
|
break;
|
||||||
|
} else if (*i == kMaxLimit) {
|
||||||
|
continue;
|
||||||
|
} else if (!*i || (now - *i >= state->expireValue)) {
|
||||||
|
expires.insert(i, state->expireValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto i = begin(usages); i != end(usages); ++i) {
|
||||||
|
if (*i == state->usageValue) {
|
||||||
|
break;
|
||||||
|
} else if (*i == kMaxLimit) {
|
||||||
|
continue;
|
||||||
|
} else if (!*i || *i > state->usageValue) {
|
||||||
|
usages.insert(i, state->usageValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state->expireButtons.clear();
|
||||||
|
state->usageButtons.clear();
|
||||||
|
for (const auto limit : expires) {
|
||||||
|
const auto text = (limit == kMaxLimit)
|
||||||
|
? tr::lng_group_invite_expire_never(tr::now)
|
||||||
|
: !limit
|
||||||
|
? tr::lng_group_invite_expire_custom(tr::now)
|
||||||
|
: FormatExpireDate(limit);
|
||||||
|
state->expireButtons.emplace(
|
||||||
|
limit,
|
||||||
|
addButton(expiresWrap, expireGroup, limit, text));
|
||||||
|
}
|
||||||
|
for (const auto limit : usages) {
|
||||||
|
const auto text = (limit == kMaxLimit)
|
||||||
|
? tr::lng_group_invite_usage_any(tr::now)
|
||||||
|
: !limit
|
||||||
|
? tr::lng_group_invite_usage_custom(tr::now)
|
||||||
|
: QString("%L1").arg(limit);
|
||||||
|
state->usageButtons.emplace(
|
||||||
|
limit,
|
||||||
|
addButton(usagesWrap, usageGroup, limit, text));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto guard = MakeWeak(box);
|
||||||
|
expireGroup->setChangedCallback([=](int value) {
|
||||||
|
if (value) {
|
||||||
|
state->expireValue = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expireGroup->setValue(state->expireValue);
|
||||||
|
box->getDelegate()->show(Box([=](not_null<GenericBox*> box) {
|
||||||
|
const auto save = [=](TimeId result) {
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (guard) {
|
||||||
|
state->expireValue = result;
|
||||||
|
regenerate();
|
||||||
|
}
|
||||||
|
box->closeBox();
|
||||||
|
};
|
||||||
|
const auto now = base::unixtime::now();
|
||||||
|
const auto time = (state->expireValue == kMaxLimit)
|
||||||
|
? (now + kDay)
|
||||||
|
: (state->expireValue > now)
|
||||||
|
? state->expireValue
|
||||||
|
: (state->expireValue < 0)
|
||||||
|
? (now - state->expireValue)
|
||||||
|
: (now + kDay);
|
||||||
|
ChooseDateTimeBox(
|
||||||
|
box,
|
||||||
|
tr::lng_group_invite_expire_after(),
|
||||||
|
tr::lng_settings_save(),
|
||||||
|
save,
|
||||||
|
time);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
usageGroup->setChangedCallback([=](int value) {
|
||||||
|
if (value) {
|
||||||
|
state->usageValue = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
usageGroup->setValue(state->usageValue);
|
||||||
|
box->getDelegate()->show(Box([=](not_null<GenericBox*> box) {
|
||||||
|
const auto height = st::boxPadding.bottom()
|
||||||
|
+ st::defaultInputField.heightMin
|
||||||
|
+ st::boxPadding.bottom();
|
||||||
|
box->setTitle(tr::lng_group_invite_expire_after());
|
||||||
|
const auto wrap = box->addRow(object_ptr<FixedHeightWidget>(
|
||||||
|
box,
|
||||||
|
height));
|
||||||
|
const auto input = CreateChild<NumberInput>(
|
||||||
|
wrap,
|
||||||
|
st::defaultInputField,
|
||||||
|
tr::lng_group_invite_custom_limit(),
|
||||||
|
(state->usageValue == kMaxLimit
|
||||||
|
? QString()
|
||||||
|
: QString::number(state->usageValue)),
|
||||||
|
200'000);
|
||||||
|
wrap->widthValue(
|
||||||
|
) | rpl::start_with_next([=](int width) {
|
||||||
|
input->resize(width, input->height());
|
||||||
|
input->moveToLeft(0, st::boxPadding.bottom());
|
||||||
|
}, input->lifetime());
|
||||||
|
box->setFocusCallback([=] {
|
||||||
|
input->setFocusFast();
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto save = [=] {
|
||||||
|
const auto value = input->getLastText().toInt();
|
||||||
|
if (value <= 0) {
|
||||||
|
input->showError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (guard) {
|
||||||
|
state->usageValue = value;
|
||||||
|
regenerate();
|
||||||
|
}
|
||||||
|
box->closeBox();
|
||||||
|
};
|
||||||
|
QObject::connect(input, &NumberInput::submitted, save);
|
||||||
|
box->addButton(tr::lng_settings_save(), save);
|
||||||
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
regenerate();
|
||||||
|
|
||||||
|
const auto &saveLabel = link.isEmpty()
|
||||||
|
? tr::lng_formatting_link_create
|
||||||
|
: tr::lng_settings_save;
|
||||||
|
box->addButton(saveLabel(), [=] {
|
||||||
|
const auto expireDate = (state->expireValue == kMaxLimit)
|
||||||
|
? 0
|
||||||
|
: (state->expireValue < 0)
|
||||||
|
? (base::unixtime::now() - state->expireValue)
|
||||||
|
: state->expireValue;
|
||||||
|
const auto usageLimit = (state->usageValue == kMaxLimit)
|
||||||
|
? 0
|
||||||
|
: state->usageValue;
|
||||||
|
done(InviteLinkFields{
|
||||||
|
.link = link,
|
||||||
|
.expireDate = expireDate,
|
||||||
|
.usageLimit = usageLimit
|
||||||
|
});
|
||||||
|
});
|
||||||
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateInviteLinkBox(
|
||||||
|
not_null<GenericBox*> box,
|
||||||
|
Fn<void(InviteLinkFields)> done) {
|
||||||
|
EditInviteLinkBox(box, InviteLinkFields(), std::move(done));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
29
Telegram/SourceFiles/ui/boxes/edit_invite_link.h
Normal file
29
Telegram/SourceFiles/ui/boxes/edit_invite_link.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/layers/generic_box.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
struct InviteLinkFields {
|
||||||
|
QString link;
|
||||||
|
TimeId expireDate = 0;
|
||||||
|
int usageLimit = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void EditInviteLinkBox(
|
||||||
|
not_null<Ui::GenericBox*> box,
|
||||||
|
const InviteLinkFields &data,
|
||||||
|
Fn<void(InviteLinkFields)> done);
|
||||||
|
|
||||||
|
void CreateInviteLinkBox(
|
||||||
|
not_null<Ui::GenericBox*> box,
|
||||||
|
Fn<void(InviteLinkFields)> done);
|
||||||
|
|
||||||
|
} // namespace Ui
|
|
@ -833,35 +833,6 @@ largeEmojiOutline: 1px;
|
||||||
largeEmojiPadding: margins(0px, 0px, 0px, 0px);
|
largeEmojiPadding: margins(0px, 0px, 0px, 0px);
|
||||||
largeEmojiSkip: 4px;
|
largeEmojiSkip: 4px;
|
||||||
|
|
||||||
scheduleHeight: 95px;
|
|
||||||
scheduleDateTop: 38px;
|
|
||||||
scheduleDateField: InputField(defaultInputField) {
|
|
||||||
textMargins: margins(2px, 0px, 2px, 0px);
|
|
||||||
placeholderScale: 0.;
|
|
||||||
heightMin: 30px;
|
|
||||||
textAlign: align(top);
|
|
||||||
font: font(14px);
|
|
||||||
}
|
|
||||||
scheduleTimeField: InputField(scheduleDateField) {
|
|
||||||
border: 0px;
|
|
||||||
borderActive: 0px;
|
|
||||||
heightMin: 28px;
|
|
||||||
placeholderFont: font(14px);
|
|
||||||
placeholderFgActive: placeholderFgActive;
|
|
||||||
}
|
|
||||||
scheduleDateWidth: 136px;
|
|
||||||
scheduleTimeWidth: 72px;
|
|
||||||
scheduleAtSkip: 24px;
|
|
||||||
scheduleAtTop: 42px;
|
|
||||||
scheduleAtLabel: FlatLabel(defaultFlatLabel) {
|
|
||||||
}
|
|
||||||
scheduleTimeSeparator: FlatLabel(defaultFlatLabel) {
|
|
||||||
style: TextStyle(defaultTextStyle) {
|
|
||||||
font: font(14px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scheduleTimeSeparatorPadding: margins(2px, 0px, 2px, 0px);
|
|
||||||
|
|
||||||
youtubeIcon: icon {
|
youtubeIcon: icon {
|
||||||
{ "media_youtube_play_bg", youtubePlayIconBg },
|
{ "media_youtube_play_bg", youtubePlayIconBg },
|
||||||
{ "media_youtube_play", youtubePlayIconFg, point(24px, 12px) },
|
{ "media_youtube_play", youtubePlayIconFg, point(24px, 12px) },
|
||||||
|
|
|
@ -40,7 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/toast/toast.h"
|
#include "ui/toast/toast.h"
|
||||||
#include "ui/toasts/common_toasts.h"
|
#include "ui/toasts/common_toasts.h"
|
||||||
#include "calls/calls_instance.h" // Core::App().calls().inCall().
|
#include "calls/calls_instance.h" // Core::App().calls().inCall().
|
||||||
#include "boxes/calendar_box.h"
|
#include "ui/boxes/calendar_box.h"
|
||||||
#include "boxes/confirm_box.h"
|
#include "boxes/confirm_box.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
@ -1051,7 +1051,7 @@ void SessionController::showJumpToDate(Dialogs::Key chat, QDate requestedDate) {
|
||||||
auto callback = [=](const QDate &date) {
|
auto callback = [=](const QDate &date) {
|
||||||
session().api().jumpToDate(chat, date);
|
session().api().jumpToDate(chat, date);
|
||||||
};
|
};
|
||||||
auto box = Box<CalendarBox>(
|
auto box = Box<Ui::CalendarBox>(
|
||||||
month,
|
month,
|
||||||
highlighted,
|
highlighted,
|
||||||
std::move(callback));
|
std::move(callback));
|
||||||
|
|
|
@ -64,6 +64,12 @@ PRIVATE
|
||||||
platform/mac/file_bookmark_mac.mm
|
platform/mac/file_bookmark_mac.mm
|
||||||
platform/platform_file_bookmark.h
|
platform/platform_file_bookmark.h
|
||||||
|
|
||||||
|
ui/boxes/calendar_box.cpp
|
||||||
|
ui/boxes/calendar_box.h
|
||||||
|
ui/boxes/choose_date_time.cpp
|
||||||
|
ui/boxes/choose_date_time.h
|
||||||
|
ui/boxes/edit_invite_link.cpp
|
||||||
|
ui/boxes/edit_invite_link.h
|
||||||
ui/chat/attach/attach_album_thumbnail.cpp
|
ui/chat/attach/attach_album_thumbnail.cpp
|
||||||
ui/chat/attach/attach_album_thumbnail.h
|
ui/chat/attach/attach_album_thumbnail.h
|
||||||
ui/chat/attach/attach_album_preview.cpp
|
ui/chat/attach/attach_album_preview.cpp
|
||||||
|
|
Loading…
Add table
Reference in a new issue