#include "app_keys.h" #include "settings_app.h" #include #include "platform/identity.h" #include "ui/fonts.h" #include "ui/widgets.h" #include "version.h" namespace roro { using Kind = SettingsMenu::Kind; using Row = SettingsMenu::Row; void SettingsApp::onEnter() { page_ = Page::Menu; list_.setCount(menu_.count()); } void SettingsApp::warn(const std::string& text) { d_.bus.publish(Event::withText(EventType::Notification, text.c_str(), static_cast(NotificationLevel::Warning))); } bool SettingsApp::onKey(const KeyEvent& e) { requestRedraw(); switch (page_) { case Page::Menu: return onMenuKey(e); case Page::Text: return onTextKey(e); case Page::Choice: return onChoiceKey(e); case Page::About: return onAboutKey(e); case Page::Wifi: if (!wifiPage_.onKey(e)) page_ = Page::Menu; return true; case Page::Firmware: if (!firmwarePage_.onKey(e)) page_ = Page::Menu; return true; case Page::Debug: if (!debugPage_.onKey(e)) page_ = Page::Menu; return true; } return false; } bool SettingsApp::onMenuKey(const KeyEvent& e) { int i = list_.selected(); switch (e.key) { case Key::Up: list_.up(); return true; case Key::Down: list_.down(); return true; case Key::Left: case Key::Right: if (menu_.kind(i) == Kind::Slider) menu_.adjust(i, e.key == Key::Right ? 1 : -1); if (menu_.kind(i) == Kind::Toggle) menu_.toggle(i); return true; case Key::Select: break; default: return false; // Back leaves Settings } editingRow_ = i; switch (menu_.kind(i)) { case Kind::Text: editor_ = LineEditor(menu_.maxBytes(i)); editor_.setText(menu_.value(i)); page_ = Page::Text; break; case Kind::Choice: choices_.setCount(static_cast(menu_.choices(i).size())); choices_.select(menu_.currentChoice(i) < 0 ? 0 : menu_.currentChoice(i)); page_ = Page::Choice; break; case Kind::Toggle: menu_.toggle(i); break; case Kind::Slider: break; case Kind::Page: switch (menu_.row(i)) { case Row::Wifi: page_ = Page::Wifi; wifiPage_.enter(); break; case Row::Firmware: page_ = Page::Firmware; firmwarePage_.enter(); break; case Row::DebugConsole: page_ = Page::Debug; debugPage_.enter(); break; default: page_ = Page::About; break; } break; } return true; } bool SettingsApp::onTextKey(const KeyEvent& e) { switch (e.key) { case Key::Char: editor_.insert(e.ch); break; case Key::Delete: editor_.backspace(); break; case Key::Left: editor_.left(); break; case Key::Right: editor_.right(); break; case Key::Select: { std::string error = menu_.setText(editingRow_, editor_.text()); if (error.empty()) page_ = Page::Menu; else warn(error); break; } case Key::Back: page_ = Page::Menu; break; // cancel default: break; } return true; } bool SettingsApp::onChoiceKey(const KeyEvent& e) { switch (e.key) { case Key::Up: choices_.up(); break; case Key::Down: choices_.down(); break; case Key::Select: { std::string error = menu_.choose(editingRow_, choices_.selected()); if (error.empty()) page_ = Page::Menu; else warn(error); break; } case Key::Back: page_ = Page::Menu; break; default: break; } return true; } bool SettingsApp::onAboutKey(const KeyEvent& e) { if (e.key == Key::Back) page_ = Page::Menu; if (e.key == Key::Char && e.ch == 'w') d_.apps.open("demo"); // hidden widget demo return true; } void SettingsApp::help(std::vector& out) const { switch (page_) { case Page::Menu: keys::add(out, keys::kSettings); break; case Page::Text: keys::add(out, keys::kText); break; case Page::Choice: keys::add(out, keys::kSettingsChoice); break; case Page::About: break; case Page::Wifi: wifiPage_.help(out); break; case Page::Firmware: firmwarePage_.help(out); break; case Page::Debug: debugPage_.help(out); break; } } const char* SettingsApp::helpTitle() const { switch (page_) { case Page::Wifi: return wifiPage_.helpTitle(); case Page::Firmware: return firmwarePage_.helpTitle(); case Page::Debug: return debugPage_.helpTitle(); case Page::About: return "About"; default: return nullptr; } } void SettingsApp::update(uint32_t nowMs) { // Live values on About and Firmware. bool live = page_ == Page::About || page_ == Page::Firmware || page_ == Page::Debug || (page_ == Page::Wifi && wifiPage_.live()); if (live && nowMs - lastRefreshMs_ >= 500) { lastRefreshMs_ = nowMs; requestRedraw(); } } std::vector SettingsApp::aboutLines() const { std::vector lines; char buf[64]; snprintf(buf, sizeof(buf), "%s %s", kProductName, versionString()); lines.push_back(buf); lines.push_back("Node " + identity::nodeId()); const auto& b = d_.battery.estimator(); snprintf(buf, sizeof(buf), "Battery %d%% (%d mV)", b.percent(), b.millivolts()); lines.push_back(buf); snprintf(buf, sizeof(buf), "Memory %u KB free (min %u KB)", ESP.getFreeHeap() / 1024, ESP.getMinFreeHeap() / 1024); lines.push_back(buf); uint32_t up = millis() / 1000; snprintf(buf, sizeof(buf), "Uptime %luh %02lum %02lus", (unsigned long)(up / 3600), (unsigned long)(up / 60 % 60), (unsigned long)(up % 60)); lines.push_back(buf); lines.push_back("Clock " + d_.clock.displayTime()); lines.push_back("License GPL-3.0"); return lines; } void SettingsApp::draw(Canvas& c) { const auto& area = theme::kContent; switch (page_) { case Page::Menu: widgets::list( c, list_, area, [this](int i) { return menu_.label(i); }, [this](int i) { auto kind = menu_.kind(i); if (kind == Kind::Page) return std::string(">"); if (kind == Kind::Slider) return "< " + menu_.value(i) + " >"; return menu_.value(i); }); break; case Page::Text: c.setFont(&fonts::body); c.setTextColor(theme::kMuted); c.drawString(menu_.label(editingRow_).c_str(), 4, area.y + 4); widgets::lineEditor(c, editor_, {4, area.y + 22, area.w - 8, 0}); c.drawString((std::to_string(editor_.text().size()) + " of " + std::to_string(editor_.maxBytes()) + " bytes").c_str(), 4, area.y + 44); break; case Page::Choice: { auto options = menu_.choices(editingRow_); int current = menu_.currentChoice(editingRow_); widgets::list(c, choices_, area, [&](int i) { return (i == current ? "* " : " ") + options[i]; }); break; } case Page::About: widgets::textLines(c, aboutLines(), 0, area); break; case Page::Wifi: wifiPage_.draw(c); break; case Page::Firmware: firmwarePage_.draw(c); break; case Page::Debug: debugPage_.draw(c); break; } } } // namespace roro