diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index de3ccf75bd..fbe9396a38 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1166,9 +1166,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_channel_invite_private" = "This channel is private.\nPlease join it to continue viewing its content."; "lng_group_invite_create" = "Create an invite link"; // #TODO links legacy -"lng_group_invite_about" = "Telegram users will be able to join\nyour group by following this link."; // #TODO links legacy -"lng_group_invite_about_channel" = "Telegram users will be able to join\nyour channel by following this link."; // #TODO links legacy -"lng_group_invite_create_new" = "Revoke invite link"; // #TODO links legacy "lng_group_invite_about_new" = "Your previous link will be deactivated and we'll generate a new invite link for you."; "lng_group_invite_copied" = "Invite link copied to clipboard."; "lng_group_invite_no_room" = "Unable to join this group because there are too many members in it already."; @@ -1186,12 +1183,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_invite_manage_about" = "You can create additional invite links that have limited time or number of usages."; "lng_group_invite_title" = "Invite links"; "lng_group_invite_add" = "Create a New Link"; +"lng_group_invite_add_about" = "You can generate invite links that will expire after they've been used."; "lng_group_invite_expires_at" = "This link expires {when}."; "lng_group_invite_created_by" = "Link created by"; "lng_group_invite_context_copy" = "Copy"; "lng_group_invite_context_share" = "Share"; "lng_group_invite_context_edit" = "Edit"; "lng_group_invite_context_revoke" = "Revoke"; +"lng_group_invite_context_delete" = "Delete"; +"lng_group_invite_context_delete_all" = "Delete all"; +"lng_group_invite_revoked_title" = "Revoked links"; "lng_group_invite_revoke_about" = "Are you sure you want to revoke that invite link?"; "lng_group_invite_link_expired" = "Expired"; "lng_group_invite_link_revoked" = "Revoked"; @@ -1199,10 +1200,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_invite_new_title" = "New Link"; "lng_group_invite_expire_title" = "Limit by time period"; "lng_group_invite_expire_about" = "You can make the link expire after a certain time."; -"lng_group_invite_expire_custom" = "Set custom duration"; -"lng_group_invite_usage_title" = "Limit number of uses."; +"lng_group_invite_expire_never" = "No limit"; +"lng_group_invite_expire_custom" = "Custom"; +"lng_group_invite_usage_title" = "Limit number of uses"; "lng_group_invite_usage_about" = "You can make the link expire after it has been used for a certain number of times."; -"lng_group_invite_usage_custom" = "Enter custom limit"; +"lng_group_invite_expire_after" = "Expire after"; +"lng_group_invite_custom_limit" = "Enter custom limit"; +"lng_group_invite_usage_any" = "No limit"; +"lng_group_invite_usage_custom" = "Custom"; "lng_channel_public_link_copied" = "Link copied to clipboard."; "lng_context_about_private_link" = "This link will only work for members of this chat."; diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index 0ede048a02..8b2e3a6f76 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -252,7 +252,7 @@ private: QPointer> _aboutSponsored; QPointer _host; - QPointer _port; + QPointer _port; QPointer _user; QPointer _password; QPointer _secret; @@ -928,11 +928,12 @@ void ProxyBox::setupSocketAddress(const ProxyData &data) { st::connectionHostInputField, tr::lng_connection_host_ph(), data.host); - _port = Ui::CreateChild( + _port = Ui::CreateChild( address, st::connectionPortInputField, tr::lng_connection_port_ph(), - data.port ? QString::number(data.port) : QString()); + data.port ? QString::number(data.port) : QString(), + 65535); address->widthValue( ) | rpl::start_with_next([=](int width) { _port->moveToRight(0, 0); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 9ebdc480cd..5671917e02 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -1004,7 +1004,10 @@ void Controller::fillManageSection() { }); }) | rpl::flatten_latest( ) | ToPositiveNumberString(), - [=] { Ui::show(Box(ManageInviteLinksBox, _peer)); }, + [=] { Ui::show( + Box(ManageInviteLinksBox, _peer), + Ui::LayerOption::KeepOther); + }, st::infoIconInviteLinks); } if (canViewAdmins) { diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp index 28815f8424..37c814cb24 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp @@ -20,11 +20,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/abstract_button.h" #include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/input_fields.h" #include "ui/controls/invite_link_label.h" #include "ui/controls/invite_link_buttons.h" #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" @@ -40,6 +43,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "api/api_common.h" #include "styles/style_info.h" +#include "styles/style_layers.h" // st::boxDividerLabel +#include "styles/style_settings.h" // st::settingsDividerLabelPadding #include @@ -48,8 +53,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { constexpr auto kPreloadPages = 2; -constexpr auto kExpireSoonNominator = 3; -constexpr auto kExpireSoonDenominator = 4; +constexpr auto kMaxLimit = std::numeric_limits::max(); +constexpr auto kHour = 3600; +constexpr auto kDay = 86400; enum class Color { Permanent, @@ -124,6 +130,27 @@ 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); } @@ -291,9 +318,226 @@ void EditLinkBox( not_null box, not_null peer, const InviteLinkData &data) { - box->setTitle(data.link.isEmpty() + 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( @@ -380,7 +624,7 @@ class Controller final , public RowDelegate , public base::has_weak_ptr { public: - explicit Controller(not_null peer); + Controller(not_null peer, bool revoked); void prepare() override; void rowClicked(not_null row) override; @@ -406,6 +650,7 @@ private: not_null row); not_null _peer; + bool _revoked = false; base::unique_qptr _menu; std::array _icons; @@ -413,8 +658,9 @@ private: }; -Controller::Controller(not_null peer) -: _peer(peer) { +Controller::Controller(not_null peer, bool revoked) +: _peer(peer) +, _revoked(revoked) { style::PaletteChanged( ) | rpl::start_with_next([=] { for (auto &image : _icons) { @@ -559,6 +805,7 @@ void Controller::rowPaintIcon( auto hq = PainterHighQualityEnabler(p); auto pen = QPen((*bg)->c); pen.setWidth(stroke); + pen.setCapStyle(Qt::RoundCap); p.setPen(pen); p.setBrush(Qt::NoBrush); @@ -568,7 +815,7 @@ void Controller::rowPaintIcon( margins, margins, margins, - }), 0, kFullArcLength * progress); + }), (kFullArcLength / 4), kFullArcLength * (1. - progress)); } } @@ -746,6 +993,26 @@ void AddPermanentLinkBlock( })); } +not_null AddLinksList( + not_null container, + not_null peer, + bool revoked) { + const auto delegate = container->lifetime().make_state< + PeerListContentDelegateSimple + >(); + const auto controller = container->lifetime().make_state( + peer, + revoked); + controller->setStyleOverrides(&st::inviteLinkList); + const auto content = container->add(object_ptr( + container, + controller)); + delegate->setContent(content); + controller->setDelegate(delegate); + + return content; +} + void ManageInviteLinksBox( not_null box, not_null peer) { @@ -760,14 +1027,34 @@ void ManageInviteLinksBox( box->getDelegate()->show(Box(CreateLinkBox, peer)); }); - const auto delegate = box->lifetime().make_state< - PeerListContentDelegateSimple - >(); - const auto controller = box->lifetime().make_state(peer); - controller->setStyleOverrides(&st::inviteLinkList); - const auto content = container->add(object_ptr( + const auto list = AddLinksList(container, peer, false); + const auto dividerAbout = container->add(object_ptr>( container, - controller)); - delegate->setContent(content); - controller->setDelegate(delegate); + object_ptr( + container, + object_ptr( + container, + tr::lng_group_invite_add_about(), + st::boxDividerLabel), + st::settingsDividerLabelPadding))); + const auto divider = container->add(object_ptr>( + container, + object_ptr(container))); + const auto header = container->add(object_ptr>( + container, + object_ptr( + container, + tr::lng_group_invite_revoked_title(), + st::settingsSubsectionTitle), + st::inviteLinkRevokedTitlePadding)); + const auto revoked = AddLinksList(container, peer, true); + + rpl::combine( + list->heightValue(), + revoked->heightValue() + ) | rpl::start_with_next([=](int list, int revoked) { + dividerAbout->toggle(!list, anim::type::instant); + divider->toggle(list > 0 && revoked > 0, anim::type::instant); + header->toggle(revoked > 0, anim::type::instant); + }, header->lifetime()); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp index a6a261173b..6cdda9579a 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp @@ -117,9 +117,6 @@ private: rpl::producer &&text, not_null st); - void createInviteLink(); - void revokeInviteLink(const QString &link); - void fillPrivaciesButtons( not_null parent, std::optional savedValue = std::nullopt); @@ -185,7 +182,10 @@ void Controller::createContent() { _wrap.get(), tr::lng_group_invite_manage(), rpl::single(QString()), - [=] { Ui::show(Box(ManageInviteLinksBox, _peer)); }, + [=] { Ui::show( + Box(ManageInviteLinksBox, _peer), + Ui::LayerOption::KeepOther); + }, st::manageGroupButton, &st::infoIconInviteLinks)); AddSkip(_wrap.get()); @@ -529,31 +529,6 @@ void Controller::showUsernameResult( _usernameResultTexts.fire(std::move(text)); } -void Controller::createInviteLink() { - const auto callback = crl::guard(this, [=](Fn &&close) { - close(); - _peer->session().api().inviteLinks().create(_peer->migrateToOrMe()); - }); - auto box = Box( - (_isGroup - ? tr::lng_group_invite_about - : tr::lng_group_invite_about_channel)(tr::now), - std::move(callback)); - Ui::show(std::move(box), Ui::LayerOption::KeepOther); -} - -void Controller::revokeInviteLink(const QString &link) { - const auto callback = crl::guard(this, [=](Fn &&close) { - close(); - const auto peer = _peer->migrateToOrMe(); - peer->session().api().inviteLinks().revoke(peer, link); - }); - auto box = Box( - tr::lng_group_invite_about_new(tr::now), - std::move(callback)); - Ui::show(std::move(box), Ui::LayerOption::KeepOther); -} - object_ptr Controller::createInviteLinkBlock() { Expects(_wrap != nullptr); diff --git a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp index 52b05ae4a4..c832129a45 100644 --- a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp +++ b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp @@ -596,19 +596,18 @@ TimeId DefaultScheduleTime() { bool CanScheduleUntilOnline(not_null peer) { return !peer->isSelf() - && peer->isUser() - && !peer->asUser()->isBot() - && (peer->asUser()->onlineTill > 0); + && peer->isUser() + && !peer->asUser()->isBot() + && (peer->asUser()->onlineTill > 0); } -void ScheduleBox( +ChooseDateTimeBoxDescriptor ChooseDateTimeBox( not_null box, - SendMenu::Type type, - Fn done, + rpl::producer title, + rpl::producer submit, + Fn done, TimeId time) { - box->setTitle((type == SendMenu::Type::Reminder) - ? tr::lng_remind_title() - : tr::lng_schedule_title()); + box->setTitle(std::move(title)); box->setWidth(st::boxWideWidth); const auto date = Ui::CreateChild>( @@ -716,46 +715,67 @@ void ScheduleBox( } return result; }; - const auto save = [=](bool silent, bool untilOnline = false) { + 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, + Fn done, + TimeId time) { + const auto save = [=](bool silent, TimeId scheduleDate) { + if (!scheduleDate) { + return; + } // Pro tip: Hold Ctrl key to send a silent scheduled message! auto ctrl = (QGuiApplication::keyboardModifiers() == Qt::ControlModifier); auto result = Api::SendOptions(); result.silent = silent || ctrl; - result.scheduled = untilOnline - ? Data::ScheduledMessages::kScheduledUntilOnlineTimestamp - : collect(); - if (!result.scheduled) { - return; - } - - auto copy = done; + result.scheduled = scheduleDate; + const auto copy = done; box->closeBox(); copy(result); }; - timeInput->submitRequests( - ) | rpl::start_with_next([=] { - save(false); - }, timeInput->lifetime()); + auto descriptor = ChooseDateTimeBox( + box, + (type == SendMenu::Type::Reminder + ? tr::lng_remind_title() + : tr::lng_schedule_title()), + tr::lng_schedule_button(), + [=](TimeId result) { save(false, result); }, + time); - box->setFocusCallback([=] { timeInput->setFocusFast(); }); - const auto submit = box->addButton(tr::lng_schedule_button(), [=] { - save(false); - }); SendMenu::SetupMenuAndShortcuts( - submit.data(), + descriptor.submit.data(), [=] { return SendMenu::Type::SilentOnly; }, - [=] { save(true); }, + [=] { save(true, descriptor.collect()); }, nullptr); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); if (type == SendMenu::Type::ScheduledToUser) { const auto sendUntilOnline = box->addTopButton(st::infoTopBarMenu); + const auto timestamp + = Data::ScheduledMessages::kScheduledUntilOnlineTimestamp; FillSendUntilOnlineMenu( sendUntilOnline.data(), - [=] { save(false, true); }); + [=] { save(false, timestamp); }); } - } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_schedule_box.h b/Telegram/SourceFiles/history/view/history_view_schedule_box.h index da640faaa0..2ad25e4ee0 100644 --- a/Telegram/SourceFiles/history/view/history_view_schedule_box.h +++ b/Telegram/SourceFiles/history/view/history_view_schedule_box.h @@ -21,6 +21,19 @@ 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/info/info.style b/Telegram/SourceFiles/info/info.style index 818d022958..f02207cda2 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -917,3 +917,5 @@ inviteLinkIconSkip: 7px; inviteLinkIconStroke: 2px; inviteLinkIcon: icon {{ "info/edit/links_link", mediaviewFileExtFg }}; inviteLinkThreeDotsSkip: 8px; +inviteLinkRevokedTitlePadding: margins(22px, 16px, 10px, 9px); +inviteLinkLimitMargin: margins(22px, 8px, 22px, 8px); diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 912f0b48a6..a5fb993721 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 912f0b48a648e7f12e7bcba82a0f0ec326aab8dd +Subproject commit a5fb99372184d5f3c00e5e851aaa16d8d28d2ce1