diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index fccf3ddcde..2a14bab17c 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -203,8 +203,6 @@ PRIVATE boxes/background_box.h boxes/background_preview_box.cpp boxes/background_preview_box.h - boxes/calendar_box.cpp - boxes/calendar_box.h boxes/change_phone_box.cpp boxes/change_phone_box.h boxes/confirm_box.cpp diff --git a/Telegram/SourceFiles/api/api_invite_links.cpp b/Telegram/SourceFiles/api/api_invite_links.cpp index c01f54d551..34ad40cf95 100644 --- a/Telegram/SourceFiles/api/api_invite_links.cpp +++ b/Telegram/SourceFiles/api/api_invite_links.cpp @@ -431,9 +431,12 @@ auto InviteLinks::parse( void InviteLinks::requestMoreLinks( not_null peer, const QString &last, + bool revoked, Fn done) { + using Flag = MTPmessages_GetExportedChatInvites::Flag; _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, MTPInputUser(), // admin_id, MTP_string(last), diff --git a/Telegram/SourceFiles/api/api_invite_links.h b/Telegram/SourceFiles/api/api_invite_links.h index 096038c06d..cec3a9c030 100644 --- a/Telegram/SourceFiles/api/api_invite_links.h +++ b/Telegram/SourceFiles/api/api_invite_links.h @@ -81,6 +81,7 @@ public: void requestMoreLinks( not_null peer, const QString &last, + bool revoked, Fn done); private: diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 82d7dfda61..53d827c6c2 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -905,3 +905,32 @@ pollResultsShowMore: SettingsButton(defaultSettingsButton) { 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); diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp index 2ef65dcef8..df67a62556 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp @@ -20,10 +20,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/text/text_utilities.h" #include "ui/text/text_options.h" +#include "ui/boxes/calendar_box.h" #include "ui/special_buttons.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "settings/settings_privacy_security.h" -#include "boxes/calendar_box.h" #include "boxes/confirm_box.h" #include "boxes/passcode_box.h" #include "boxes/peers/edit_peer_permissions_box.h" @@ -691,7 +691,7 @@ void EditRestrictedBox::showRestrictUntil() { : base::unixtime::parse(getRealUntilValue()).date(); auto month = highlighted; _restrictUntilBox = Ui::show( - Box( + Box( month, highlighted, [this](const QDate &date) { diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.h b/Telegram/SourceFiles/boxes/peers/edit_participant_box.h index 689b08a7b8..371d7b98bb 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.h @@ -18,6 +18,7 @@ class LinkButton; class Checkbox; class Radiobutton; class RadiobuttonGroup; +class CalendarBox; template class SlideWrap; } // namespace Ui @@ -26,7 +27,6 @@ namespace Core { struct CloudPasswordResult; } // namespace Core -class CalendarBox; class PasscodeBox; class EditParticipantBox : public Ui::BoxContent { @@ -162,7 +162,7 @@ private: std::shared_ptr _untilGroup; std::vector> _untilVariants; - QPointer _restrictUntilBox; + QPointer _restrictUntilBox; static constexpr auto kUntilOneDay = -1; static constexpr auto kUntilOneWeek = -2; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp index 37c814cb24..10b3443188 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_histories.h" #include "main/main_session.h" #include "api/api_invite_links.h" +#include "ui/boxes/edit_invite_link.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/slide_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/toast/toast.h" #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.h" #include "lang/lang_keys.h" @@ -53,9 +53,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { constexpr auto kPreloadPages = 2; -constexpr auto kMaxLimit = std::numeric_limits::max(); -constexpr auto kHour = 3600; -constexpr auto kDay = 86400; enum class Color { 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) { return XXH64(link.data(), link.size() * sizeof(ushort), 0); } @@ -282,6 +258,45 @@ void ShareLinkBox(not_null peer, const QString &link) { std::move(filterCallback))); } +void EditLink(not_null peer, const InviteLinkData &data) { + const auto creating = data.link.isEmpty(); + const auto box = std::make_shared>(); + 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 AddCreateLinkButton( not_null container) { const auto result = container->add( @@ -314,241 +329,6 @@ not_null AddCreateLinkButton( return result; } -void EditLinkBox( - not_null box, - not_null 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( - 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( - container)); - AddSkip(container); - - AddDividerText(container, tr::lng_group_invite_usage_about()); - - static const auto addButton = []( - not_null container, - const std::shared_ptr &group, - int value, - const QString &text) { - return container->add( - object_ptr( - 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(expire); - const auto usage = data.usageLimit ? data.usageLimit : kMaxLimit; - const auto usageGroup = std::make_shared(usage); - - using Buttons = base::flat_map>; - struct State { - Buttons expireButtons; - Buttons usageButtons; - int expireValue = 0; - int usageValue = 0; - }; - const auto state = container->lifetime().make_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 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 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( - box, - height)); - const auto input = Ui::CreateChild( - 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 box, - not_null peer) { - EditLinkBox( - box, - peer, - InviteLinkData{ .admin = peer->session().user() }); -} - Row::Row( not_null delegate, const InviteLinkData &data, @@ -724,9 +504,7 @@ base::unique_qptr Controller::createRowContextMenu( ShareLinkBox(_peer, link); }); result->addAction(tr::lng_group_invite_context_edit(tr::now), [=] { - Ui::show( - Box(EditLinkBox, _peer, data), - Ui::LayerOption::KeepOther); + EditLink(_peer, data); }); result->addAction(tr::lng_group_invite_context_revoke(tr::now), [=] { const auto box = std::make_shared>(); @@ -1024,7 +802,7 @@ void ManageInviteLinksBox( const auto add = AddCreateLinkButton(container); add->setClickedCallback([=] { - box->getDelegate()->show(Box(CreateLinkBox, peer)); + EditLink(peer, InviteLinkData{ .admin = peer->session().user() }); }); const auto list = AddLinksList(container, peer, false); diff --git a/Telegram/SourceFiles/export/view/export_view_settings.cpp b/Telegram/SourceFiles/export/view/export_view_settings.cpp index 7fefa66396..956cfb7802 100644 --- a/Telegram/SourceFiles/export/view/export_view_settings.cpp +++ b/Telegram/SourceFiles/export/view/export_view_settings.cpp @@ -21,9 +21,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/fade_wrap.h" #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" +#include "ui/boxes/calendar_box.h" #include "platform/platform_specific.h" #include "core/file_utilities.h" -#include "boxes/calendar_box.h" #include "base/unixtime.h" #include "base/qt_adapters.h" #include "main/main_session.h" @@ -463,8 +463,8 @@ void SettingsWidget::editDateLimit( ? base::unixtime::parse(min).date() : QDate::currentDate(); const auto month = highlighted; - const auto shared = std::make_shared>(); - const auto finalize = [=](not_null box) { + const auto shared = std::make_shared>(); + const auto finalize = [=](not_null box) { box->setMaxDate(max ? base::unixtime::parse(max).date() : QDate::currentDate()); @@ -484,7 +484,7 @@ void SettingsWidget::editDateLimit( weak->closeBox(); } }); - auto box = Box( + auto box = Box( month, highlighted, callback, diff --git a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp index c832129a45..e22bffb1c7 100644 --- a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp +++ b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp @@ -14,12 +14,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "base/event_filter.h" #include "base/unixtime.h" -#include "boxes/calendar_box.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" #include "ui/wrap/padding_wrap.h" +#include "ui/boxes/choose_date_time.h" #include "chat_helpers/send_context_menu.h" #include "styles/style_info.h" #include "styles/style_layers.h" @@ -30,550 +30,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { 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 e) { - // Only a mouse wheel is accepted. - constexpr auto step = static_cast(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 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 _putNext; - -}; - -int Number(not_null 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 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 &field, QChar ch); - void erasePrevious(const object_ptr &field); - void finishInnerAnimating(); - void setErrorShown(bool error); - void setFocused(bool focused); - void startBorderAnimation(); - template - bool insideSeparator(QPoint position, const Widget &widget) const; - - int hour() const; - int minute() const; - - object_ptr _hour; - object_ptr> _separator1; - object_ptr _minute; - rpl::variable _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 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(), - GetHour(value)) -, _separator1( - this, - object_ptr( - this, - QString(":"), - st::scheduleTimeSeparator), - st::scheduleTimeSeparatorPadding) -, _minute( - this, - st::scheduleTimeField, - rpl::never(), - GetMinute(value)) -, _value(valueCurrent()) { - const auto focused = [=](const object_ptr &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 &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 &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 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 -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( not_null button, Fn callback) { @@ -601,138 +57,6 @@ bool CanScheduleUntilOnline(not_null peer) { && (peer->asUser()->onlineTill > 0); } -ChooseDateTimeBoxDescriptor ChooseDateTimeBox( - not_null box, - rpl::producer title, - rpl::producer submit, - Fn done, - TimeId time) { - box->setTitle(std::move(title)); - box->setWidth(st::boxWideWidth); - - const auto date = Ui::CreateChild>( - box.get(), - base::unixtime::parse(time).date()); - const auto content = box->addRow( - object_ptr(box, st::scheduleHeight)); - const auto dayInput = Ui::CreateChild( - content, - st::scheduleDateField); - const auto timeInput = Ui::CreateChild( - content, - TimeString(time)); - const auto at = Ui::CreateChild( - 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 event) { - if (event->type() == QEvent::Wheel) { - const auto e = static_cast(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>(); - QObject::connect(dayInput, &Ui::InputField::focused, [=] { - if (*calendar) { - return; - } - const auto chosen = [=](QDate chosen) { - *date = chosen; - (*calendar)->closeBox(); - }; - const auto finalize = [=](not_null box) { - box->setMinDate(minDate); - box->setMaxDate(maxDate); - }; - *calendar = box->getDelegate()->show(Box( - 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( not_null box, SendMenu::Type type, @@ -752,7 +76,7 @@ void ScheduleBox( box->closeBox(); copy(result); }; - auto descriptor = ChooseDateTimeBox( + auto descriptor = Ui::ChooseDateTimeBox( box, (type == SendMenu::Type::Reminder ? tr::lng_remind_title() diff --git a/Telegram/SourceFiles/history/view/history_view_schedule_box.h b/Telegram/SourceFiles/history/view/history_view_schedule_box.h index 2ad25e4ee0..30ee8bd49d 100644 --- a/Telegram/SourceFiles/history/view/history_view_schedule_box.h +++ b/Telegram/SourceFiles/history/view/history_view_schedule_box.h @@ -22,18 +22,6 @@ namespace HistoryView { [[nodiscard]] TimeId DefaultScheduleTime(); [[nodiscard]] bool CanScheduleUntilOnline(not_null peer); -struct ChooseDateTimeBoxDescriptor { - QPointer submit; - Fn collect; -}; - -ChooseDateTimeBoxDescriptor ChooseDateTimeBox( - not_null box, - rpl::producer title, - rpl::producer submit, - Fn done, - TimeId time); - void ScheduleBox( not_null box, SendMenu::Type type, diff --git a/Telegram/SourceFiles/boxes/calendar_box.cpp b/Telegram/SourceFiles/ui/boxes/calendar_box.cpp similarity index 97% rename from Telegram/SourceFiles/boxes/calendar_box.cpp rename to Telegram/SourceFiles/ui/boxes/calendar_box.cpp index 6019df5c4d..8c4095f922 100644 --- a/Telegram/SourceFiles/boxes/calendar_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/calendar_box.cpp @@ -5,15 +5,16 @@ 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 "boxes/calendar_box.h" +#include "ui/boxes/calendar_box.h" #include "ui/widgets/buttons.h" -#include "lang/lang_keys.h" #include "ui/effects/ripple_animation.h" #include "ui/ui_utility.h" +#include "lang/lang_keys.h" #include "styles/style_boxes.h" #include "styles/style_dialogs.h" +namespace Ui { namespace { constexpr auto kDaysInWeek = 7; @@ -229,7 +230,7 @@ private: const style::CalendarSizes &_st; not_null _context; - std::map> _ripples; + std::map> _ripples; Fn _dateChosenCallback; @@ -257,7 +258,7 @@ void CalendarBox::Inner::monthChanged(QDate month) { _ripples.clear(); resizeToCurrent(); update(); - Ui::SendSynteticMouseEvent(this, QEvent::MouseMove, Qt::NoButton); + SendSynteticMouseEvent(this, QEvent::MouseMove, Qt::NoButton); } 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 it = _ripples.find(_selected); 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); }; - it = _ripples.emplace(_selected, std::make_unique(st::defaultRippleAnimation, std::move(mask), std::move(update))).first; + it = _ripples.emplace(_selected, std::make_unique(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); it->second->add(e->pos() - ripplePosition); @@ -611,3 +612,5 @@ void CalendarBox::wheelEvent(QWheelEvent *e) { } CalendarBox::~CalendarBox() = default; + +} // namespace Ui diff --git a/Telegram/SourceFiles/boxes/calendar_box.h b/Telegram/SourceFiles/ui/boxes/calendar_box.h similarity index 88% rename from Telegram/SourceFiles/boxes/calendar_box.h rename to Telegram/SourceFiles/ui/boxes/calendar_box.h index 391f25bedb..b5e398b6b7 100644 --- a/Telegram/SourceFiles/boxes/calendar_box.h +++ b/Telegram/SourceFiles/ui/boxes/calendar_box.h @@ -7,17 +7,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "abstract_box.h" +#include "ui/layers/box_content.h" +#include "base/observer.h" namespace style { struct CalendarSizes; } // namespace style namespace Ui { -class IconButton; -} // namespace Ui -class CalendarBox : public Ui::BoxContent, private base::Subscriber { +class IconButton; + +class CalendarBox : public BoxContent, private base::Subscriber { public: CalendarBox( QWidget*, @@ -67,10 +68,12 @@ private: class Title; object_ptr _title; - object_ptr<Ui::IconButton> _previous; - object_ptr<Ui::IconButton> _next; + object_ptr<IconButton> _previous; + object_ptr<IconButton> _next; Fn<void(QDate date)> _callback; FnMut<void(not_null<CalendarBox*>)> _finalize; }; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp b/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp new file mode 100644 index 0000000000..a9d413e974 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp @@ -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 diff --git a/Telegram/SourceFiles/ui/boxes/choose_date_time.h b/Telegram/SourceFiles/ui/boxes/choose_date_time.h new file mode 100644 index 0000000000..87b42c28e5 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/choose_date_time.h @@ -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 diff --git a/Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp b/Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp new file mode 100644 index 0000000000..3ffa6417a8 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp @@ -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 diff --git a/Telegram/SourceFiles/ui/boxes/edit_invite_link.h b/Telegram/SourceFiles/ui/boxes/edit_invite_link.h new file mode 100644 index 0000000000..82b3f17547 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/edit_invite_link.h @@ -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 diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index dab0e9ba86..e112415b2e 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -833,35 +833,6 @@ largeEmojiOutline: 1px; largeEmojiPadding: margins(0px, 0px, 0px, 0px); 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 { { "media_youtube_play_bg", youtubePlayIconBg }, { "media_youtube_play", youtubePlayIconFg, point(24px, 12px) }, diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 148db3026c..c0aef5a3fc 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -40,7 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/toasts/common_toasts.h" #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 "mainwidget.h" #include "mainwindow.h" @@ -1051,7 +1051,7 @@ void SessionController::showJumpToDate(Dialogs::Key chat, QDate requestedDate) { auto callback = [=](const QDate &date) { session().api().jumpToDate(chat, date); }; - auto box = Box<CalendarBox>( + auto box = Box<Ui::CalendarBox>( month, highlighted, std::move(callback)); diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index fd12d7f3ac..0c9020a3ac 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -64,6 +64,12 @@ PRIVATE platform/mac/file_bookmark_mac.mm 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.h ui/chat/attach/attach_album_preview.cpp