From f2ce6e946b1488b10442b9e93242c5c61522a13a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kamo=C5=84?= Date: Fri, 7 May 2021 09:22:30 +0200 Subject: [PATCH] [EGD-6707] Add single number message notification This PR provides implementation of home screen notification for `notSeen` messages received from a single number. The "single-number: `notSeen` message notification distinct from "multiple-number" notification with the following features: - displaying formatted contact name - `onActivated` it switches to respective thread window (instead of all thread window) in `ApplicationMessages` The PR also introduced some `ActiveNotificationsModel` code refactor to align the implementation with `Single Responsibility Principle` --- .../ApplicationDesktop.cpp | 9 +- .../ApplicationDesktop.hpp | 2 +- .../models/ActiveNotificationsModel.cpp | 246 ++++++++++++------ .../models/ActiveNotificationsModel.hpp | 1 - .../windows/DesktopMainWindow.cpp | 15 +- .../windows/DesktopMainWindow.hpp | 2 +- .../ApplicationMessages.hpp | 27 +- .../application-messages/CMakeLists.txt | 1 + .../application-messages/Constants.hpp | 21 ++ .../notifications/NotificationsModel.cpp | 26 +- .../threads/QueryThreadGetByNumber.cpp | 6 +- .../threads/QueryThreadGetByNumber.hpp | 8 +- 12 files changed, 233 insertions(+), 131 deletions(-) create mode 100644 module-apps/application-messages/Constants.hpp diff --git a/module-apps/application-desktop/ApplicationDesktop.cpp b/module-apps/application-desktop/ApplicationDesktop.cpp index 369aaf6e77627ebe0c442462d3d55bfb11156e4b..fa79095209d4283dd39741af54e11ccb23219341 100644 --- a/module-apps/application-desktop/ApplicationDesktop.cpp +++ b/module-apps/application-desktop/ApplicationDesktop.cpp @@ -43,8 +43,8 @@ namespace app std::string parent, sys::phone_modes::PhoneMode mode, StartInBackground startInBackground) - : Application(std::move(name), std::move(parent), mode, startInBackground), lockHandler(this), - dbNotificationHandler(this) + : Application(std::move(name), std::move(parent), mode, startInBackground), AsyncCallbackReceiver(this), + lockHandler(this), dbNotificationHandler(this) { using namespace gui::top_bar; topBarManager->enableIndicators({Indicator::Signal, @@ -171,7 +171,10 @@ namespace app // handle database response if (resp != nullptr) { - if (auto msg = dynamic_cast(resp)) { + if (auto command = callbackStorage->getCallback(resp); command->execute()) { + handled = true; + } + else if (auto msg = dynamic_cast(resp)) { auto result = msg->getResult(); if (dbNotificationHandler.handle(result.get())) { handled = true; diff --git a/module-apps/application-desktop/ApplicationDesktop.hpp b/module-apps/application-desktop/ApplicationDesktop.hpp index 4ac738c8e576090adc1b5e4e71211fc890323da5..6bfdea345282620cc2c5c115dfe6082486b85fcb 100644 --- a/module-apps/application-desktop/ApplicationDesktop.hpp +++ b/module-apps/application-desktop/ApplicationDesktop.hpp @@ -23,7 +23,7 @@ namespace gui namespace app { - class ApplicationDesktop : public Application + class ApplicationDesktop : public Application, public AsyncCallbackReceiver { public: bool need_sim_select = false; diff --git a/module-apps/application-desktop/models/ActiveNotificationsModel.cpp b/module-apps/application-desktop/models/ActiveNotificationsModel.cpp index 267cae35cd4c50b5b14ed40f064d25c6dffa38a2..f78fa3b310cc8df49356d2b3391d21f274004f19 100644 --- a/module-apps/application-desktop/models/ActiveNotificationsModel.cpp +++ b/module-apps/application-desktop/models/ActiveNotificationsModel.cpp @@ -2,14 +2,169 @@ // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "ActiveNotificationsModel.hpp" +#include #include #include -#include #include #include +#include +#include +#include +#include using namespace gui; +namespace +{ + using Notification = const notifications::NotificationWithContact *; + void setSMSFocusChangedCallback(NotificationListItem *item, ActiveNotificationsModel *model) + { + item->focusChangedCallback = [model](gui::Item &_item) { + if (_item.focus) { + model->setParentBottomBar( + {}, utils::translate("app_desktop_show"), utils::translate("app_desktop_clear")); + return true; + } + return false; + }; + } + + auto createSMSActivatedCallback(app::Application *app) + { + return [app]([[maybe_unused]] gui::Item &_item) { + return app::manager::Controller::sendAction( + app, app::manager::actions::Launch, std::make_unique(app::name_messages)); + }; + } + + auto createSMSActivatedCallback(app::Application *app, const ContactRecord &record) + { + Expects(not record.numbers.empty()); + return [app, number = record.numbers[0].number]([[maybe_unused]] gui::Item &_item) { + auto query = std::make_unique(number); + auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMSThread); + + auto queryCallback = [app](db::QueryResult *msg) -> bool { + Expects(typeid(*msg) == typeid(db::query::ThreadGetByNumberResult)); + auto result = static_cast(msg); + auto data = std::make_unique(std::make_shared(result->getThread())); + auto request = std::make_shared( + app->GetName(), app::name_messages, gui::name::window::thread_view, std::move(data)); + return app->bus.sendUnicast(std::move(request), app::manager::ApplicationManager::ServiceName); + }; + task->setCallback(std::move(queryCallback)); + task->execute(app, static_cast(app)); + return true; + }; + } + + void setSMSActivatedCallback(NotificationListItem *item, Notification provider, app::Application *app) + { + if (provider->hasRecord() && not provider->getRecord().numbers.empty()) { + item->activatedCallback = createSMSActivatedCallback(app, provider->getRecord()); + } + else { + item->activatedCallback = createSMSActivatedCallback(app); + } + } + + void setSMSOnInputCallback(NotificationListItem *item, app::Application *app) + { + item->inputCallback = [app]([[maybe_unused]] Item &item, const InputEvent &inputEvent) { + if (inputEvent.isShortRelease(KeyCode::KEY_RF)) { + DBServiceAPI::GetQuery( + app, + db::Interface::Name::Notifications, + std::make_unique(NotificationsRecord::Key::Sms)); + return true; + } + return false; + }; + } + + void setCallFocusChangedCallback(NotificationListItem *item, Notification provider, ActiveNotificationsModel *model) + { + item->focusChangedCallback = [model, canCall = provider->hasRecord()](gui::Item &_item) { + if (_item.focus) { + UTF8 bottomBarLeftText = canCall ? UTF8{utils::translate("common_call")} : UTF8{}; + model->setParentBottomBar( + bottomBarLeftText, utils::translate("app_desktop_show"), utils::translate("app_desktop_clear")); + } + return true; + }; + } + + void setCallActivatedCallback(NotificationListItem *item, app::Application *app) + { + item->activatedCallback = [app]([[maybe_unused]] gui::Item &_item) { + return app::manager::Controller::sendAction(app, app::manager::actions::ShowCallLog); + }; + } + + auto createCallOnRightFunctionalCallback(app::Application *app) -> std::function + { + return [app]() { + DBServiceAPI::GetQuery(app, + db::Interface::Name::Notifications, + std::make_unique(NotificationsRecord::Key::Calls)); + }; + } + auto createCallOnLeftFunctionalCallback(app::Application *app, Notification provider) -> std::function + { + if (!provider->hasRecord()) { + return nullptr; + } + if (const auto &record = provider->getRecord(); !record.numbers.empty()) { + return [app, number = record.numbers[0].number]() { + app::manager::Controller::sendAction( + app, app::manager::actions::Call, std::make_unique(number)); + }; + } + return nullptr; + } + + void setCallOnInputCallback(NotificationListItem *item, Notification provider, app::Application *app) + { + auto onRightFunctionalKeyCallback = createCallOnRightFunctionalCallback(app); + auto onLeftFunctionalKeyCallback = createCallOnLeftFunctionalCallback(app, provider); + + item->inputCallback = [keyRightFunctionalCb = std::move(onRightFunctionalKeyCallback), + keyLeftFunctionalCb = std::move(onLeftFunctionalKeyCallback)]( + [[maybe_unused]] Item &item, const InputEvent &inputEvent) { + if (inputEvent.isShortRelease()) { + if (inputEvent.is(KeyCode::KEY_RF) && keyRightFunctionalCb != nullptr) { + keyRightFunctionalCb(); + return true; + } + else if (inputEvent.is(KeyCode::KEY_LF) && keyLeftFunctionalCb != nullptr) { + keyLeftFunctionalCb(); + return true; + } + } + return false; + }; + } + + void setTetheringActivatedCallback(NotificationListItem *item, app::Application *app) + { + item->activatedCallback = [app]([[maybe_unused]] gui::Item &_item) { + return app->bus.sendUnicast(std::make_shared(sys::phone_modes::Tethering::Off), + service::name::system_manager); + }; + } + + void setTetheringFocusChangedCallback(NotificationListItem *item, ActiveNotificationsModel *model) + { + item->focusChangedCallback = [model](gui::Item &_item) { + if (_item.focus) { + model->setParentBottomBar({}, utils::translate("common_disconnect"), {}); + return true; + } + return false; + }; + } +} // namespace + ActiveNotificationsModel::ActiveNotificationsModel(AppWindow *parent) : parent(parent) {} @@ -19,83 +174,24 @@ void ActiveNotificationsModel::setParentBottomBar(const UTF8 &left, const UTF8 & parent->setBottomBarText(center, BottomBar::Side::CENTER); parent->setBottomBarText(right, BottomBar::Side::RIGHT); } + auto ActiveNotificationsModel::create(const notifications::NotSeenSMSNotification *notification) -> NotificationListItem * { auto item = NotificationsModel::create(notification); - item->focusChangedCallback = [this](gui::Item &_item) { - if (_item.focus) { - setParentBottomBar({}, utils::translate("app_desktop_show"), utils::translate("app_desktop_clear")); - return true; - } - return false; - }; - item->activatedCallback = [this]([[maybe_unused]] gui::Item &_item) { - return app::manager::Controller::sendAction(parent->getApplication(), - app::manager::actions::Launch, - std::make_unique(app::name_messages)); - }; - item->inputCallback = [this]([[maybe_unused]] Item &item, const InputEvent &inputEvent) { - if (inputEvent.isShortRelease(KeyCode::KEY_RF)) { - DBServiceAPI::GetQuery(parent->getApplication(), - db::Interface::Name::Notifications, - std::make_unique(NotificationsRecord::Key::Sms)); - return true; - } - return false; - }; + setSMSFocusChangedCallback(item, this); + setSMSActivatedCallback(item, notification, parent->getApplication()); + setSMSOnInputCallback(item, parent->getApplication()); item->setDismissible(true); return item; } auto ActiveNotificationsModel::create(const notifications::NotSeenCallNotification *notification) -> NotificationListItem * { - auto item = NotificationsModel::create(notification); - item->activatedCallback = [this]([[maybe_unused]] gui::Item &_item) { - return app::manager::Controller::sendAction(parent->getApplication(), app::manager::actions::ShowCallLog); - }; - - std::function onRightFunctionalCallback = [this]() { - DBServiceAPI::GetQuery(parent->getApplication(), - db::Interface::Name::Notifications, - std::make_unique(NotificationsRecord::Key::Calls)); - }; - std::function onKeyLeftFunctionalCallback = nullptr; - - if (notification->hasRecord()) { - if (const auto &record = notification->getRecord(); !record.numbers.empty()) { - onKeyLeftFunctionalCallback = [this, number = record.numbers[0].number]() { - app::manager::Controller::sendAction(parent->getApplication(), - app::manager::actions::Dial, - std::make_unique(number)); - }; - } - } - item->inputCallback = [keyRightFunctionalCb = std::move(onRightFunctionalCallback), - keyLeftFunctionalCb = std::move(onKeyLeftFunctionalCallback)]([[maybe_unused]] Item &item, - const InputEvent &inputEvent) { - if (inputEvent.isShortRelease()) { - if (inputEvent.is(KeyCode::KEY_RF)) { - keyRightFunctionalCb(); - return true; - } - else if (inputEvent.is(KeyCode::KEY_LF) && keyLeftFunctionalCb != nullptr) { - keyLeftFunctionalCb(); - return true; - } - } - return false; - }; - - item->focusChangedCallback = [this, canCall = notification->hasRecord()](gui::Item &_item) { - if (_item.focus) { - UTF8 bottomBarLeftText = canCall ? UTF8{utils::translate("common_call")} : UTF8{}; - setParentBottomBar( - bottomBarLeftText, utils::translate("app_desktop_show"), utils::translate("app_desktop_clear")); - } - return true; - }; - + auto item = NotificationsModel::create(notification); + setCallFocusChangedCallback(item, notification, this); + setCallActivatedCallback(item, parent->getApplication()); + setCallOnInputCallback(item, notification, parent->getApplication()); item->setDismissible(true); return item; } @@ -104,17 +200,7 @@ auto ActiveNotificationsModel::create(const notifications::TetheringNotification -> NotificationListItem * { auto item = NotificationsModel::create(notification); - item->activatedCallback = [this]([[maybe_unused]] gui::Item &_item) { - return parent->getApplication()->bus.sendUnicast( - std::make_shared(sys::phone_modes::Tethering::Off), - service::name::system_manager); - }; - item->focusChangedCallback = [this](gui::Item &_item) { - if (_item.focus) { - setParentBottomBar({}, utils::translate("common_disconnect"), {}); - return true; - } - return false; - }; + setTetheringActivatedCallback(item, parent->getApplication()); + setTetheringFocusChangedCallback(item, this); return item; } diff --git a/module-apps/application-desktop/models/ActiveNotificationsModel.hpp b/module-apps/application-desktop/models/ActiveNotificationsModel.hpp index 8dc67cc919e3bc4c0a5df36deac3c47d53263af1..84ade6f2246dc22dc13beb8ced4047b0e38adf84 100644 --- a/module-apps/application-desktop/models/ActiveNotificationsModel.hpp +++ b/module-apps/application-desktop/models/ActiveNotificationsModel.hpp @@ -11,7 +11,6 @@ namespace gui { private: AppWindow *parent = nullptr; - public: explicit ActiveNotificationsModel(AppWindow *parent); void setParentBottomBar(const UTF8 &left, const UTF8 ¢er, const UTF8 &right); diff --git a/module-apps/application-desktop/windows/DesktopMainWindow.cpp b/module-apps/application-desktop/windows/DesktopMainWindow.cpp index f93cb9153725cc2c6677fef23790c92a751e8935..06e6b7e0944f66f7f5d9a2a346a4c78d9da626be 100644 --- a/module-apps/application-desktop/windows/DesktopMainWindow.cpp +++ b/module-apps/application-desktop/windows/DesktopMainWindow.cpp @@ -53,14 +53,9 @@ namespace gui listview::ScrollBarType::Fixed); notificationsList->emptyListCallback = [this]() { setFocusItem(nullptr); - setVisibleState(); - }; - notificationsList->notEmptyListCallback = [this]() { - if (focusItem == nullptr) { - setVisibleState(); - } + setActiveState(); }; - + notificationsList->notEmptyListCallback = [this]() { setActiveState(); }; setVisibleState(); } @@ -203,8 +198,11 @@ namespace gui buildInterface(); } - auto DesktopMainWindow::setActiveState() -> bool + void DesktopMainWindow::setActiveState() { + if (focusItem != nullptr) { + return; + } bottomBar->setText(BottomBar::Side::CENTER, utils::translate("app_desktop_menu")); bottomBar->setText(BottomBar::Side::LEFT, utils::translate("app_desktop_calls")); const auto hasDismissibleNotification = notificationsModel->hasDismissibleNotification(); @@ -227,7 +225,6 @@ namespace gui } return false; }; - return true; } app::ApplicationDesktop *DesktopMainWindow::getAppDesktop() const diff --git a/module-apps/application-desktop/windows/DesktopMainWindow.hpp b/module-apps/application-desktop/windows/DesktopMainWindow.hpp index bf8a4abc7302ca8ad22f252a79565a91a5595569..d48760bd60773b81189fb04b0ecd79b70cfed7a5 100644 --- a/module-apps/application-desktop/windows/DesktopMainWindow.hpp +++ b/module-apps/application-desktop/windows/DesktopMainWindow.hpp @@ -30,7 +30,7 @@ namespace gui // method hides or show widgets and sets bars according to provided state void setVisibleState(); - auto setActiveState() -> bool; + void setActiveState(); bool processLongReleaseEvent(const InputEvent &inputEvent); bool processShortReleaseEvent(const InputEvent &inputEvent); app::ApplicationDesktop *getAppDesktop() const; diff --git a/module-apps/application-messages/ApplicationMessages.hpp b/module-apps/application-messages/ApplicationMessages.hpp index b8db49eed315f049d8bde44afa7903593b4fb153..6e172067886d5cc8f9d965f5fc10cc73affe6802 100644 --- a/module-apps/application-messages/ApplicationMessages.hpp +++ b/module-apps/application-messages/ApplicationMessages.hpp @@ -8,40 +8,25 @@ #include #include #include +#include "Constants.hpp" namespace gui { // fw declarations class OptionWindow; class Text; - namespace name - { - namespace window - { - inline constexpr auto dialog_yes_no = "DialogYesNo"; - inline constexpr auto dialog_confirm = "DialogConfirm"; - inline constexpr auto dialog = "Dialog"; - inline constexpr auto new_sms = "NewSMS"; - inline constexpr auto thread_sms_search = "SMSSearch"; - inline constexpr auto sms_templates = "SMSTemplates"; - inline constexpr auto thread_view = "ThreadViewWindow"; - - }; // namespace window - }; // namespace name -}; // namespace gui +} // namespace gui namespace app { - inline constexpr auto name_messages = "ApplicationMessages"; - class ApplicationMessages : public app::Application, public app::AsyncCallbackReceiver { public: - ApplicationMessages(std::string name = name_messages, - std::string parent = {}, - sys::phone_modes::PhoneMode mode = sys::phone_modes::PhoneMode::Connected, - StartInBackground startInBackground = {false}); + explicit ApplicationMessages(std::string name = name_messages, + std::string parent = {}, + sys::phone_modes::PhoneMode mode = sys::phone_modes::PhoneMode::Connected, + StartInBackground startInBackground = {false}); sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) override; sys::ReturnCodes InitHandler() override; diff --git a/module-apps/application-messages/CMakeLists.txt b/module-apps/application-messages/CMakeLists.txt index 603dfd00060f0369db417b367504a83895968983..a7e7e007d7268c31defa4bfe9f86c80b1f4e8e5f 100644 --- a/module-apps/application-messages/CMakeLists.txt +++ b/module-apps/application-messages/CMakeLists.txt @@ -40,6 +40,7 @@ target_sources( ${PROJECT_NAME} PUBLIC "ApplicationMessages.hpp" + "Constants.hpp" "data/MessagesStyle.hpp" "models/ThreadsModel.hpp" "models/SMSThreadModel.hpp" diff --git a/module-apps/application-messages/Constants.hpp b/module-apps/application-messages/Constants.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2e0cff112e8b36a1136d4a9ea071e562d7f3fc69 --- /dev/null +++ b/module-apps/application-messages/Constants.hpp @@ -0,0 +1,21 @@ +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md + +#pragma once + +namespace app +{ + inline constexpr auto name_messages = "ApplicationMessages"; +} + +namespace gui::name::window +{ + inline constexpr auto dialog_yes_no = "DialogYesNo"; + inline constexpr auto dialog_confirm = "DialogConfirm"; + inline constexpr auto dialog = "Dialog"; + inline constexpr auto new_sms = "NewSMS"; + inline constexpr auto thread_sms_search = "SMSSearch"; + inline constexpr auto sms_templates = "SMSTemplates"; + inline constexpr auto thread_view = "ThreadViewWindow"; + +} // namespace gui::name::window diff --git a/module-apps/notifications/NotificationsModel.cpp b/module-apps/notifications/NotificationsModel.cpp index ba468928c5d55f76de557ec1104bc653c8a35fdf..aaa5a1a5b75b3c798e8a3672fd8296b6e7fe8d37 100644 --- a/module-apps/notifications/NotificationsModel.cpp +++ b/module-apps/notifications/NotificationsModel.cpp @@ -6,6 +6,22 @@ using namespace gui; +namespace +{ + void setNotificationText(NotificationListItem *item, + const notifications::NotificationWithContact *notification, + const std::string &text) + { + if (notification->hasRecord()) { + const auto &record = notification->getRecord(); + item->setName(record.getFormattedName()); + } + else { + item->setName(utils::translate(text), true); + } + } +} // namespace + unsigned int NotificationsModel::requestRecordsCount() { return internalData.size(); @@ -55,7 +71,7 @@ auto NotificationsModel::create(const notifications::NotSeenSMSNotification *not { auto item = new NotificationWithEventCounter(notifications::NotificationType::NotSeenSms, utils::to_string(notification->getValue())); - item->setName(utils::translate("app_desktop_unread_messages"), true); + setNotificationText(item, notification, "app_desktop_unread_messages"); item->deleteByList = false; return item; } @@ -63,13 +79,7 @@ auto NotificationsModel::create(const notifications::NotSeenCallNotification *no { auto item = new NotificationWithEventCounter(notifications::NotificationType::NotSeenCall, utils::to_string(notification->getValue())); - if (notification->hasRecord()) { - const auto &record = notification->getRecord(); - item->setName(record.getFormattedName()); - } - else { - item->setName(utils::translate("app_desktop_missed_calls"), true); - } + setNotificationText(item, notification, "app_desktop_missed_calls"); item->deleteByList = false; return item; } diff --git a/module-db/queries/messages/threads/QueryThreadGetByNumber.cpp b/module-db/queries/messages/threads/QueryThreadGetByNumber.cpp index 43811359da088d42052c9a86843954ecbe57a28e..383e874d204a4c2a97195ff9c6270374b1e5ee66 100644 --- a/module-db/queries/messages/threads/QueryThreadGetByNumber.cpp +++ b/module-db/queries/messages/threads/QueryThreadGetByNumber.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "QueryThreadGetByNumber.hpp" @@ -10,8 +10,8 @@ namespace db::query { - ThreadGetByNumber::ThreadGetByNumber(utils::PhoneNumber::View number) - : Query(Query::Type::Read), number(std::move(number)) + ThreadGetByNumber::ThreadGetByNumber(const utils::PhoneNumber::View &number) + : Query(Query::Type::Read), number(number) {} auto ThreadGetByNumber::getNumber() const -> const utils::PhoneNumber::View & diff --git a/module-db/queries/messages/threads/QueryThreadGetByNumber.hpp b/module-db/queries/messages/threads/QueryThreadGetByNumber.hpp index 13010847bd3404bfaa92e826262f30ab95aaace3..6395c8b508b6893bcc3d4078c5b5b2b4e84bf3ae 100644 --- a/module-db/queries/messages/threads/QueryThreadGetByNumber.hpp +++ b/module-db/queries/messages/threads/QueryThreadGetByNumber.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #pragma once @@ -17,8 +17,8 @@ namespace db::query utils::PhoneNumber::View number; public: - ThreadGetByNumber(utils::PhoneNumber::View number); - auto getNumber() const -> const utils::PhoneNumber::View &; + explicit ThreadGetByNumber(const utils::PhoneNumber::View &number); + [[nodiscard]] auto getNumber() const -> const utils::PhoneNumber::View &; [[nodiscard]] auto debugInfo() const -> std::string override; }; @@ -28,7 +28,7 @@ namespace db::query ThreadRecord thread; public: - ThreadGetByNumberResult(ThreadRecord record); + explicit ThreadGetByNumberResult(ThreadRecord record); [[nodiscard]] auto getThread() -> ThreadRecord; [[nodiscard]] auto debugInfo() const -> std::string override; };