Use common activity pattern
This commit is contained in:
@@ -110,6 +110,7 @@ STR_TIME_TO_SLEEP: "Time to Sleep"
|
|||||||
STR_SHOW_HIDDEN_FILES: "Show Hidden Files"
|
STR_SHOW_HIDDEN_FILES: "Show Hidden Files"
|
||||||
STR_SHOW_FILE_EXTENSIONS: "Show File Extensions"
|
STR_SHOW_FILE_EXTENSIONS: "Show File Extensions"
|
||||||
STR_USE_CLOCK: "Use Clock"
|
STR_USE_CLOCK: "Use Clock"
|
||||||
|
STR_USE_WEATHER: "Use Weather"
|
||||||
STR_CLOCK_SETTINGS: "Clock Settings"
|
STR_CLOCK_SETTINGS: "Clock Settings"
|
||||||
STR_CLOCK_SETTINGS_WARNING: "Uses more battery; clock may drift"
|
STR_CLOCK_SETTINGS_WARNING: "Uses more battery; clock may drift"
|
||||||
STR_CLOCK: "Clock"
|
STR_CLOCK: "Clock"
|
||||||
@@ -379,6 +380,7 @@ STR_AUTO_TURN_ENABLED: "Auto Turn Enabled: "
|
|||||||
STR_AUTO_TURN_PAGES_PER_MIN: "Auto Turn (Pages Per Minute)"
|
STR_AUTO_TURN_PAGES_PER_MIN: "Auto Turn (Pages Per Minute)"
|
||||||
STR_WEATHER: "Weather"
|
STR_WEATHER: "Weather"
|
||||||
STR_WEATHER_LOCATION: "Location"
|
STR_WEATHER_LOCATION: "Location"
|
||||||
|
STR_WEATHER_UNITS: "Units"
|
||||||
STR_WEATHER_NO_LOCATION: "No location set for weather"
|
STR_WEATHER_NO_LOCATION: "No location set for weather"
|
||||||
STR_WEATHER_FETCH_FAILED: "Failed to fetch weather"
|
STR_WEATHER_FETCH_FAILED: "Failed to fetch weather"
|
||||||
STR_WEATHER_SETTINGS: "Weather Settings"
|
STR_WEATHER_SETTINGS: "Weather Settings"
|
||||||
|
|||||||
@@ -271,6 +271,8 @@ class CrossPointSettings {
|
|||||||
// Use clock and keep the LP timer running during deep sleep (GPIO13 HIGH)
|
// Use clock and keep the LP timer running during deep sleep (GPIO13 HIGH)
|
||||||
// so time can be accurately restored on wake. Increases sleep current by ~3-4 mA.
|
// so time can be accurately restored on wake. Increases sleep current by ~3-4 mA.
|
||||||
uint8_t useClock = 0;
|
uint8_t useClock = 0;
|
||||||
|
// Show the Weather home screen menu item (1 = enabled, 0 = hidden)
|
||||||
|
uint8_t useWeather = 1;
|
||||||
|
|
||||||
~CrossPointSettings() = default;
|
~CrossPointSettings() = default;
|
||||||
|
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ inline const std::vector<SettingInfo> list = {
|
|||||||
StrId::STR_TZ_EST, StrId::STR_TZ_CST, StrId::STR_TZ_MST, StrId::STR_TZ_PST},
|
StrId::STR_TZ_EST, StrId::STR_TZ_CST, StrId::STR_TZ_MST, StrId::STR_TZ_PST},
|
||||||
"timeZone", StrId::STR_CAT_SYSTEM),
|
"timeZone", StrId::STR_CAT_SYSTEM),
|
||||||
SettingInfo::Toggle(StrId::STR_USE_CLOCK, &CrossPointSettings::useClock, "useClock", StrId::STR_CAT_SYSTEM),
|
SettingInfo::Toggle(StrId::STR_USE_CLOCK, &CrossPointSettings::useClock, "useClock", StrId::STR_CAT_SYSTEM),
|
||||||
|
SettingInfo::Toggle(StrId::STR_USE_WEATHER, &CrossPointSettings::useWeather, "useWeather", StrId::STR_CAT_SYSTEM),
|
||||||
|
|
||||||
// --- KOReader Sync (web-only, uses KOReaderCredentialStore) ---
|
// --- KOReader Sync (web-only, uses KOReaderCredentialStore) ---
|
||||||
SettingInfo::DynamicString(
|
SettingInfo::DynamicString(
|
||||||
|
|||||||
@@ -100,7 +100,10 @@ int getHomeCoverRenderHeight(const HomeScreenLayout& layout) {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int HomeActivity::getMenuItemCount() const {
|
int HomeActivity::getMenuItemCount() const {
|
||||||
int count = 5; // File Browser, Recents, File transfer, Weather, Settings
|
int count = 4; // File Browser, Recents, File transfer, Settings
|
||||||
|
if (SETTINGS.useWeather) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
if (!recentBooks.empty()) {
|
if (!recentBooks.empty()) {
|
||||||
count += recentBooks.size();
|
count += recentBooks.size();
|
||||||
}
|
}
|
||||||
@@ -283,12 +286,13 @@ void HomeActivity::loop() {
|
|||||||
int idx = 0;
|
int idx = 0;
|
||||||
int menuSelectedIndex = selectorIndex - static_cast<int>(recentBooks.size());
|
int menuSelectedIndex = selectorIndex - static_cast<int>(recentBooks.size());
|
||||||
const bool hasGlobalBookmarks = !GLOBAL_BOOKMARKS.isEmpty();
|
const bool hasGlobalBookmarks = !GLOBAL_BOOKMARKS.isEmpty();
|
||||||
|
const bool hasWeather = SETTINGS.useWeather;
|
||||||
const int fileBrowserIdx = idx++;
|
const int fileBrowserIdx = idx++;
|
||||||
const int recentsIdx = idx++;
|
const int recentsIdx = idx++;
|
||||||
const int globalBookmarksIdx = hasGlobalBookmarks ? idx++ : -1;
|
const int globalBookmarksIdx = hasGlobalBookmarks ? idx++ : -1;
|
||||||
const int opdsLibraryIdx = hasOpdsUrl ? idx++ : -1;
|
const int opdsLibraryIdx = hasOpdsUrl ? idx++ : -1;
|
||||||
const int fileTransferIdx = idx++;
|
const int fileTransferIdx = idx++;
|
||||||
const int weatherIdx = idx++;
|
const int weatherIdx = hasWeather ? idx++ : -1;
|
||||||
const int settingsIdx = idx;
|
const int settingsIdx = idx;
|
||||||
|
|
||||||
if (selectorIndex < recentBooks.size()) {
|
if (selectorIndex < recentBooks.size()) {
|
||||||
@@ -321,9 +325,17 @@ void HomeActivity::render(RenderLock&&) {
|
|||||||
GUI.drawHeader(renderer, Rect{contentRect.x, metrics.topPadding, contentRect.width, metrics.homeTopPadding}, nullptr);
|
GUI.drawHeader(renderer, Rect{contentRect.x, metrics.topPadding, contentRect.width, metrics.homeTopPadding}, nullptr);
|
||||||
|
|
||||||
// Build menu items dynamically
|
// Build menu items dynamically
|
||||||
|
const char* weatherMenuLabel = SETTINGS.useWeather ? tr(STR_WEATHER) : tr(STR_SETTINGS_TITLE);
|
||||||
|
const UIIcon weatherMenuIcon = SETTINGS.useWeather ? Weather : Settings;
|
||||||
|
|
||||||
std::vector<const char*> menuItems = {tr(STR_BROWSE_FILES), tr(STR_MENU_RECENT_BOOKS), tr(STR_FILE_TRANSFER),
|
std::vector<const char*> menuItems = {tr(STR_BROWSE_FILES), tr(STR_MENU_RECENT_BOOKS), tr(STR_FILE_TRANSFER),
|
||||||
tr(STR_WEATHER), tr(STR_SETTINGS_TITLE)};
|
weatherMenuLabel, tr(STR_SETTINGS_TITLE)};
|
||||||
std::vector<UIIcon> menuIcons = {Folder, Recent, Transfer, Weather, Settings};
|
std::vector<UIIcon> menuIcons = {Folder, Recent, Transfer, weatherMenuIcon, Settings};
|
||||||
|
|
||||||
|
if (!SETTINGS.useWeather) {
|
||||||
|
menuItems.erase(menuItems.begin() + 3);
|
||||||
|
menuIcons.erase(menuIcons.begin() + 3);
|
||||||
|
}
|
||||||
|
|
||||||
int insertAfterRecents = 2;
|
int insertAfterRecents = 2;
|
||||||
if (!GLOBAL_BOOKMARKS.isEmpty()) {
|
if (!GLOBAL_BOOKMARKS.isEmpty()) {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "DetectTimezoneActivity.h"
|
#include "DetectTimezoneActivity.h"
|
||||||
#include "MappedInputManager.h"
|
|
||||||
#include "SyncTimeActivity.h"
|
#include "SyncTimeActivity.h"
|
||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
@@ -19,94 +18,48 @@ const StrId timeZoneNames[CrossPointSettings::TIMEZONE_COUNT] = {
|
|||||||
StrId::STR_TZ_EST, StrId::STR_TZ_CST, StrId::STR_TZ_MST, StrId::STR_TZ_PST};
|
StrId::STR_TZ_EST, StrId::STR_TZ_CST, StrId::STR_TZ_MST, StrId::STR_TZ_PST};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::string ClockSettingsActivity::MenuItem::getTitle() const {
|
void ClockSettingsActivity::buildMenuItems() {
|
||||||
const auto t = I18N.get(labelId);
|
menuItems.push_back(SettingInfo::Separator(StrId::STR_SETTINGS_TITLE));
|
||||||
return isSeparator ? UITheme::makeSeparatorTitle(t) : t;
|
menuItems.push_back(
|
||||||
|
SettingInfo::Toggle(StrId::STR_USE_CLOCK, &CrossPointSettings::useClock, "useClock", StrId::STR_CAT_SYSTEM));
|
||||||
|
menuItems.push_back(SettingInfo::Enum(StrId::STR_CLOCK_FORMAT, &CrossPointSettings::clockFormat12h,
|
||||||
|
{StrId::STR_24H, StrId::STR_12H}, "clockFormat12h", StrId::STR_CAT_SYSTEM));
|
||||||
|
menuItems.push_back(
|
||||||
|
SettingInfo::Enum(StrId::STR_TIMEZONE, &CrossPointSettings::timeZone,
|
||||||
|
{StrId::STR_TZ_UTC, StrId::STR_TZ_CET, StrId::STR_TZ_EET, StrId::STR_TZ_MSK,
|
||||||
|
StrId::STR_TZ_UTC_PLUS4, StrId::STR_TZ_IST, StrId::STR_TZ_UTC_PLUS7, StrId::STR_TZ_UTC_PLUS8,
|
||||||
|
StrId::STR_TZ_UTC_PLUS9, StrId::STR_TZ_AEST, StrId::STR_TZ_NZST, StrId::STR_TZ_UTC_MINUS3,
|
||||||
|
StrId::STR_TZ_EST, StrId::STR_TZ_CST, StrId::STR_TZ_MST, StrId::STR_TZ_PST},
|
||||||
|
"timeZone", StrId::STR_CAT_SYSTEM));
|
||||||
|
|
||||||
|
menuItems.push_back(SettingInfo::Action(StrId::STR_DETECT_TIMEZONE, SettingAction::DetectTimezone)
|
||||||
|
.withSubcategory(StrId::STR_READER_TOOLS));
|
||||||
|
menuItems.push_back(SettingInfo::Action(StrId::STR_SYNC_TIME, SettingAction::SyncTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ClockSettingsActivity::MenuItem> ClockSettingsActivity::buildMenuItems() {
|
void ClockSettingsActivity::onActionSelected(int index) {
|
||||||
std::vector<MenuItem> items;
|
const auto& item = menuItems[index];
|
||||||
items.reserve(7);
|
if (item.nameId == StrId::STR_DETECT_TIMEZONE) {
|
||||||
// Settings
|
auto resultHandler = [](const ActivityResult&) { SETTINGS.saveToFile(); };
|
||||||
items.push_back(MenuItem::separator(StrId::STR_SETTINGS_TITLE));
|
startActivityForResult(std::make_unique<DetectTimezoneActivity>(renderer, mappedInput), resultHandler);
|
||||||
items.push_back({Action::USE_CLOCK, StrId::STR_USE_CLOCK});
|
} else if (item.nameId == StrId::STR_SYNC_TIME) {
|
||||||
items.push_back({Action::CLOCK_FORMAT, StrId::STR_CLOCK_FORMAT});
|
auto resultHandler = [](const ActivityResult&) { SETTINGS.saveToFile(); };
|
||||||
items.push_back({Action::TIMEZONE, StrId::STR_TIMEZONE});
|
startActivityForResult(std::make_unique<SyncTimeActivity>(renderer, mappedInput), resultHandler);
|
||||||
|
|
||||||
// Tools
|
|
||||||
items.push_back(MenuItem::separator(StrId::STR_READER_TOOLS));
|
|
||||||
items.push_back({Action::DETECT_TIMEZONE, StrId::STR_DETECT_TIMEZONE});
|
|
||||||
items.push_back({Action::SYNC_TIME, StrId::STR_SYNC_TIME});
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClockSettingsActivity::onEnter() {
|
|
||||||
Activity::onEnter();
|
|
||||||
const auto pred = UITheme::makeSelectablePredicate(static_cast<int>(menuItems.size()),
|
|
||||||
[this](int i) { return menuItems[i].getTitle(); });
|
|
||||||
buttonNavigator.setSelectablePredicate(pred, static_cast<int>(menuItems.size()));
|
|
||||||
if (!pred(selectedIndex)) {
|
|
||||||
selectedIndex = buttonNavigator.nextIndex(selectedIndex);
|
|
||||||
}
|
}
|
||||||
requestUpdate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClockSettingsActivity::onExit() { Activity::onExit(); }
|
void ClockSettingsActivity::onSettingToggled(int index) {
|
||||||
|
const auto& item = menuItems[index];
|
||||||
void ClockSettingsActivity::loop() {
|
if (item.nameId == StrId::STR_USE_CLOCK) {
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
|
||||||
handleSelection();
|
|
||||||
requestUpdate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
buttonNavigator.onNextRelease([this] {
|
|
||||||
selectedIndex = buttonNavigator.nextIndex(selectedIndex);
|
|
||||||
requestUpdate();
|
|
||||||
});
|
|
||||||
|
|
||||||
buttonNavigator.onPreviousRelease([this] {
|
|
||||||
selectedIndex = buttonNavigator.previousIndex(selectedIndex);
|
|
||||||
requestUpdate();
|
|
||||||
});
|
|
||||||
|
|
||||||
buttonNavigator.onNextContinuous([this] {
|
|
||||||
selectedIndex = buttonNavigator.nextIndex(selectedIndex);
|
|
||||||
requestUpdate();
|
|
||||||
});
|
|
||||||
|
|
||||||
buttonNavigator.onPreviousContinuous([this] {
|
|
||||||
selectedIndex = buttonNavigator.previousIndex(selectedIndex);
|
|
||||||
requestUpdate();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClockSettingsActivity::handleSelection() {
|
|
||||||
const auto action = menuItems[selectedIndex].action;
|
|
||||||
if (action == Action::USE_CLOCK) {
|
|
||||||
SETTINGS.useClock = (SETTINGS.useClock + 1) % 2;
|
|
||||||
if (!SETTINGS.useClock) {
|
if (!SETTINGS.useClock) {
|
||||||
SETTINGS.statusBarClock = 0;
|
SETTINGS.statusBarClock = 0;
|
||||||
}
|
}
|
||||||
SETTINGS.saveToFile();
|
SETTINGS.saveToFile();
|
||||||
} else if (action == Action::CLOCK_FORMAT) {
|
} else if (item.nameId == StrId::STR_TIMEZONE) {
|
||||||
SETTINGS.clockFormat12h = (SETTINGS.clockFormat12h + 1) % 2;
|
|
||||||
SETTINGS.saveToFile();
|
|
||||||
} else if (action == Action::TIMEZONE) {
|
|
||||||
SETTINGS.timeZone = (SETTINGS.timeZone + 1) % CrossPointSettings::TIMEZONE_COUNT;
|
|
||||||
HalClock::applyTimezone(SETTINGS.timeZone);
|
HalClock::applyTimezone(SETTINGS.timeZone);
|
||||||
SETTINGS.saveToFile();
|
SETTINGS.saveToFile();
|
||||||
} else if (action == Action::SYNC_TIME) {
|
} else if (item.nameId == StrId::STR_CLOCK_FORMAT) {
|
||||||
auto resultHandler = [](const ActivityResult&) { SETTINGS.saveToFile(); };
|
SETTINGS.saveToFile();
|
||||||
startActivityForResult(std::make_unique<SyncTimeActivity>(renderer, mappedInput), resultHandler);
|
|
||||||
} else if (action == Action::DETECT_TIMEZONE) {
|
|
||||||
auto resultHandler = [](const ActivityResult&) { SETTINGS.saveToFile(); };
|
|
||||||
startActivityForResult(std::make_unique<DetectTimezoneActivity>(renderer, mappedInput), resultHandler);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,28 +82,7 @@ void ClockSettingsActivity::render(RenderLock&&) {
|
|||||||
const int contentHeight = contentRect.height - (metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight +
|
const int contentHeight = contentRect.height - (metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight +
|
||||||
metrics.verticalSpacing * 2);
|
metrics.verticalSpacing * 2);
|
||||||
|
|
||||||
GUI.drawList(
|
drawMenuList(Rect{contentRect.x, contentTop, contentRect.width, contentHeight});
|
||||||
renderer, Rect(contentRect.x, contentTop, contentRect.width, contentHeight), static_cast<int>(menuItems.size()),
|
|
||||||
selectedIndex, [this](int index) { return menuItems[index].getTitle(); }, nullptr, nullptr,
|
|
||||||
[this](int index) {
|
|
||||||
const auto action = menuItems[index].action;
|
|
||||||
switch (action) {
|
|
||||||
case Action::USE_CLOCK:
|
|
||||||
return std::string(SETTINGS.useClock ? tr(STR_STATE_ON) : tr(STR_STATE_OFF));
|
|
||||||
case Action::CLOCK_FORMAT:
|
|
||||||
return std::string(SETTINGS.clockFormat12h ? tr(STR_12H) : tr(STR_24H));
|
|
||||||
case Action::TIMEZONE: {
|
|
||||||
const auto tzIndex = static_cast<size_t>(SETTINGS.timeZone);
|
|
||||||
if (tzIndex < (sizeof(timeZoneNames) / sizeof(timeZoneNames[0]))) {
|
|
||||||
return std::string(I18N.get(timeZoneNames[tzIndex]));
|
|
||||||
}
|
|
||||||
return std::string(tr(STR_TZ_UTC));
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return std::string("");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
|
|
||||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
||||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|||||||
@@ -1,35 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <I18n.h>
|
|
||||||
|
|
||||||
#include <functional>
|
#include "../MenuListActivity.h"
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "activities/Activity.h"
|
|
||||||
#include "util/ButtonNavigator.h"
|
|
||||||
|
|
||||||
class ClockSettingsActivity final : public Activity {
|
|
||||||
enum class Action { USE_CLOCK, CLOCK_FORMAT, TIMEZONE, SYNC_TIME, DETECT_TIMEZONE, NONE };
|
|
||||||
|
|
||||||
struct MenuItem {
|
|
||||||
Action action;
|
|
||||||
StrId labelId;
|
|
||||||
bool isSeparator = false;
|
|
||||||
static MenuItem separator(StrId label) { return {Action::NONE, label, true}; }
|
|
||||||
[[nodiscard]] std::string getTitle() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
ButtonNavigator buttonNavigator;
|
|
||||||
int selectedIndex = 0;
|
|
||||||
const std::vector<MenuItem> menuItems;
|
|
||||||
|
|
||||||
static std::vector<MenuItem> buildMenuItems();
|
|
||||||
void handleSelection();
|
|
||||||
|
|
||||||
|
class ClockSettingsActivity final : public MenuListActivity {
|
||||||
public:
|
public:
|
||||||
explicit ClockSettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
explicit ClockSettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||||
: Activity("ClockSettings", renderer, mappedInput), menuItems(buildMenuItems()) {}
|
: MenuListActivity("ClockSettings", renderer, mappedInput) {
|
||||||
void onEnter() override;
|
buildMenuItems();
|
||||||
void onExit() override;
|
}
|
||||||
void loop() override;
|
|
||||||
void render(RenderLock&&) override;
|
void render(RenderLock&&) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void buildMenuItems();
|
||||||
|
void onActionSelected(int index) override;
|
||||||
|
void onSettingToggled(int index) override;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,29 +13,29 @@
|
|||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
|
||||||
namespace {
|
void WeatherSettingsActivity::buildMenuItems() {
|
||||||
const StrId menuNames[] = {
|
menuItems.push_back(SettingInfo::Separator(StrId::STR_SETTINGS_TITLE));
|
||||||
StrId::STR_WEATHER_LOCATION, // 0: Search city
|
menuItems.push_back(SettingInfo::Toggle(StrId::STR_USE_WEATHER, &CrossPointSettings::useWeather, "useWeather",
|
||||||
StrId::STR_WEATHER_LATITUDE, // 1: Manual latitude
|
StrId::STR_CAT_SYSTEM));
|
||||||
StrId::STR_WEATHER_LONGITUDE, // 2: Manual longitude
|
|
||||||
StrId::STR_WEATHER_TEMP_UNIT, // 3: Temperature unit
|
|
||||||
StrId::STR_WEATHER_WIND_UNIT, // 4: Wind speed unit
|
|
||||||
StrId::STR_WEATHER_PRECIP_UNIT, // 5: Precipitation unit
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void WeatherSettingsActivity::onEnter() {
|
menuItems.push_back(SettingInfo::Separator(StrId::STR_WEATHER_LOCATION));
|
||||||
Activity::onEnter();
|
menuItems.push_back(SettingInfo::Action(StrId::STR_WEATHER_LOCATION, SettingAction::None));
|
||||||
selectedIndex = 0;
|
menuItems.push_back(SettingInfo::Action(StrId::STR_WEATHER_LATITUDE, SettingAction::None));
|
||||||
showingSearchResults = false;
|
menuItems.push_back(SettingInfo::Action(StrId::STR_WEATHER_LONGITUDE, SettingAction::None));
|
||||||
requestUpdate();
|
|
||||||
|
menuItems.push_back(SettingInfo::Separator(StrId::STR_WEATHER_UNITS));
|
||||||
|
menuItems.push_back(SettingInfo::Action(StrId::STR_WEATHER_TEMP_UNIT, SettingAction::None));
|
||||||
|
menuItems.push_back(SettingInfo::Action(StrId::STR_WEATHER_WIND_UNIT, SettingAction::None));
|
||||||
|
menuItems.push_back(SettingInfo::Action(StrId::STR_WEATHER_PRECIP_UNIT, SettingAction::None));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WeatherSettingsActivity::onExit() { Activity::onExit(); }
|
void WeatherSettingsActivity::onEnter() {
|
||||||
|
MenuListActivity::onEnter();
|
||||||
|
showingSearchResults = false;
|
||||||
|
}
|
||||||
|
|
||||||
void WeatherSettingsActivity::loop() {
|
void WeatherSettingsActivity::loop() {
|
||||||
if (showingSearchResults) {
|
if (showingSearchResults) {
|
||||||
// Handle search results navigation
|
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
showingSearchResults = false;
|
showingSearchResults = false;
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
@@ -43,7 +43,7 @@ void WeatherSettingsActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
if (!searchResults.empty() && selectedIndex < searchResults.size()) {
|
if (!searchResults.empty() && selectedIndex < static_cast<int>(searchResults.size())) {
|
||||||
const auto& result = searchResults[selectedIndex];
|
const auto& result = searchResults[selectedIndex];
|
||||||
WEATHER_SETTINGS.setLocation(result.latitude, result.longitude, result.name + ", " + result.country);
|
WEATHER_SETTINGS.setLocation(result.latitude, result.longitude, result.name + ", " + result.country);
|
||||||
WEATHER_SETTINGS.saveToFile();
|
WEATHER_SETTINGS.saveToFile();
|
||||||
@@ -69,50 +69,89 @@ void WeatherSettingsActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
MenuListActivity::loop();
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
|
||||||
handleSelection();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
buttonNavigator.onNext([this] {
|
|
||||||
selectedIndex = (selectedIndex + 1) % MENU_ITEMS;
|
|
||||||
requestUpdate();
|
|
||||||
});
|
|
||||||
|
|
||||||
buttonNavigator.onPrevious([this] {
|
|
||||||
selectedIndex = (selectedIndex + MENU_ITEMS - 1) % MENU_ITEMS;
|
|
||||||
requestUpdate();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WeatherSettingsActivity::handleSelection() {
|
std::string WeatherSettingsActivity::getItemValueString(int index) const {
|
||||||
switch (selectedIndex) {
|
const auto& item = menuItems[index];
|
||||||
case 0:
|
switch (item.nameId) {
|
||||||
launchCitySearch();
|
case StrId::STR_WEATHER_LOCATION: {
|
||||||
break;
|
auto name = WEATHER_SETTINGS.getLocationName();
|
||||||
case 1:
|
return name.empty() ? std::string(tr(STR_NOT_SET)) : name;
|
||||||
launchLatitudeEntry();
|
}
|
||||||
break;
|
case StrId::STR_WEATHER_LATITUDE: {
|
||||||
case 2:
|
char buf[16];
|
||||||
launchLongitudeEntry();
|
snprintf(buf, sizeof(buf), "%.4f", WEATHER_SETTINGS.getLatitude());
|
||||||
break;
|
return std::string(buf);
|
||||||
case 3:
|
}
|
||||||
toggleTempUnit();
|
case StrId::STR_WEATHER_LONGITUDE: {
|
||||||
break;
|
char buf[16];
|
||||||
case 4:
|
snprintf(buf, sizeof(buf), "%.4f", WEATHER_SETTINGS.getLongitude());
|
||||||
toggleWindUnit();
|
return std::string(buf);
|
||||||
break;
|
}
|
||||||
case 5:
|
case StrId::STR_WEATHER_TEMP_UNIT:
|
||||||
togglePrecipUnit();
|
return WEATHER_SETTINGS.getTempUnit() == WeatherTempUnit::CELSIUS ? "C" : "F";
|
||||||
break;
|
case StrId::STR_WEATHER_WIND_UNIT:
|
||||||
|
switch (WEATHER_SETTINGS.getWindUnit()) {
|
||||||
|
case WeatherWindUnit::KMH:
|
||||||
|
return "km/h";
|
||||||
|
case WeatherWindUnit::MS:
|
||||||
|
return "m/s";
|
||||||
|
case WeatherWindUnit::MPH:
|
||||||
|
return "mph";
|
||||||
|
case WeatherWindUnit::KNOTS:
|
||||||
|
return "kn";
|
||||||
|
}
|
||||||
|
return "km/h";
|
||||||
|
case StrId::STR_WEATHER_PRECIP_UNIT:
|
||||||
|
return WEATHER_SETTINGS.getPrecipUnit() == WeatherPrecipUnit::MM ? "mm" : "in";
|
||||||
|
default:
|
||||||
|
return MenuListActivity::getItemValueString(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WeatherSettingsActivity::onActionSelected(int index) {
|
||||||
|
const auto& item = menuItems[index];
|
||||||
|
if (item.nameId == StrId::STR_WEATHER_LOCATION) {
|
||||||
|
launchCitySearch();
|
||||||
|
} else if (item.nameId == StrId::STR_WEATHER_LATITUDE) {
|
||||||
|
launchLatitudeEntry();
|
||||||
|
} else if (item.nameId == StrId::STR_WEATHER_LONGITUDE) {
|
||||||
|
launchLongitudeEntry();
|
||||||
|
} else if (item.nameId == StrId::STR_WEATHER_TEMP_UNIT) {
|
||||||
|
auto current = WEATHER_SETTINGS.getTempUnit();
|
||||||
|
WEATHER_SETTINGS.setTempUnit(current == WeatherTempUnit::CELSIUS ? WeatherTempUnit::FAHRENHEIT
|
||||||
|
: WeatherTempUnit::CELSIUS);
|
||||||
|
WEATHER_SETTINGS.saveToFile();
|
||||||
|
requestUpdate();
|
||||||
|
} else if (item.nameId == StrId::STR_WEATHER_WIND_UNIT) {
|
||||||
|
auto current = static_cast<uint8_t>(WEATHER_SETTINGS.getWindUnit());
|
||||||
|
WEATHER_SETTINGS.setWindUnit(static_cast<WeatherWindUnit>((current + 1) % 4));
|
||||||
|
WEATHER_SETTINGS.saveToFile();
|
||||||
|
requestUpdate();
|
||||||
|
} else if (item.nameId == StrId::STR_WEATHER_PRECIP_UNIT) {
|
||||||
|
auto current = WEATHER_SETTINGS.getPrecipUnit();
|
||||||
|
WEATHER_SETTINGS.setPrecipUnit(current == WeatherPrecipUnit::MM ? WeatherPrecipUnit::INCH : WeatherPrecipUnit::MM);
|
||||||
|
WEATHER_SETTINGS.saveToFile();
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WeatherSettingsActivity::onSettingToggled(int index) {
|
||||||
|
if (menuItems[index].nameId == StrId::STR_USE_WEATHER) {
|
||||||
|
SETTINGS.saveToFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WeatherSettingsActivity::onBackPressed() {
|
||||||
|
if (showingSearchResults) {
|
||||||
|
showingSearchResults = false;
|
||||||
|
requestUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
void WeatherSettingsActivity::launchCitySearch() {
|
void WeatherSettingsActivity::launchCitySearch() {
|
||||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_WEATHER_SEARCH_CITY), "",
|
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_WEATHER_SEARCH_CITY), "",
|
||||||
64, InputType::Text),
|
64, InputType::Text),
|
||||||
@@ -122,7 +161,6 @@ void WeatherSettingsActivity::launchCitySearch() {
|
|||||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||||
if (kb.text.empty()) return;
|
if (kb.text.empty()) return;
|
||||||
|
|
||||||
// Need WiFi for geocoding
|
|
||||||
if (WiFi.status() != WL_CONNECTED || WiFi.localIP() == IPAddress(0, 0, 0, 0)) {
|
if (WiFi.status() != WL_CONNECTED || WiFi.localIP() == IPAddress(0, 0, 0, 0)) {
|
||||||
startActivityForResult(std::make_unique<WifiSelectionActivity>(renderer, mappedInput),
|
startActivityForResult(std::make_unique<WifiSelectionActivity>(renderer, mappedInput),
|
||||||
[this, query = kb.text](const ActivityResult& wifiResult) {
|
[this, query = kb.text](const ActivityResult& wifiResult) {
|
||||||
@@ -181,28 +219,6 @@ void WeatherSettingsActivity::launchLongitudeEntry() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void WeatherSettingsActivity::toggleTempUnit() {
|
|
||||||
auto current = WEATHER_SETTINGS.getTempUnit();
|
|
||||||
WEATHER_SETTINGS.setTempUnit(current == WeatherTempUnit::CELSIUS ? WeatherTempUnit::FAHRENHEIT
|
|
||||||
: WeatherTempUnit::CELSIUS);
|
|
||||||
WEATHER_SETTINGS.saveToFile();
|
|
||||||
requestUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WeatherSettingsActivity::toggleWindUnit() {
|
|
||||||
auto current = static_cast<uint8_t>(WEATHER_SETTINGS.getWindUnit());
|
|
||||||
WEATHER_SETTINGS.setWindUnit(static_cast<WeatherWindUnit>((current + 1) % 4));
|
|
||||||
WEATHER_SETTINGS.saveToFile();
|
|
||||||
requestUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WeatherSettingsActivity::togglePrecipUnit() {
|
|
||||||
auto current = WEATHER_SETTINGS.getPrecipUnit();
|
|
||||||
WEATHER_SETTINGS.setPrecipUnit(current == WeatherPrecipUnit::MM ? WeatherPrecipUnit::INCH : WeatherPrecipUnit::MM);
|
|
||||||
WEATHER_SETTINGS.saveToFile();
|
|
||||||
requestUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WeatherSettingsActivity::render(RenderLock&&) {
|
void WeatherSettingsActivity::render(RenderLock&&) {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
@@ -210,7 +226,6 @@ void WeatherSettingsActivity::render(RenderLock&&) {
|
|||||||
const Rect contentRect = UITheme::getContentRect(renderer, true, false);
|
const Rect contentRect = UITheme::getContentRect(renderer, true, false);
|
||||||
|
|
||||||
if (showingSearchResults) {
|
if (showingSearchResults) {
|
||||||
// Display search results
|
|
||||||
GUI.drawHeader(renderer,
|
GUI.drawHeader(renderer,
|
||||||
Rect(contentRect.x, contentRect.y + metrics.topPadding, contentRect.width, metrics.headerHeight),
|
Rect(contentRect.x, contentRect.y + metrics.topPadding, contentRect.width, metrics.headerHeight),
|
||||||
tr(STR_WEATHER_SEARCH_RESULTS));
|
tr(STR_WEATHER_SEARCH_RESULTS));
|
||||||
@@ -241,7 +256,6 @@ void WeatherSettingsActivity::render(RenderLock&&) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main settings menu
|
|
||||||
GUI.drawHeader(renderer,
|
GUI.drawHeader(renderer,
|
||||||
Rect(contentRect.x, contentRect.y + metrics.topPadding, contentRect.width, metrics.headerHeight),
|
Rect(contentRect.x, contentRect.y + metrics.topPadding, contentRect.width, metrics.headerHeight),
|
||||||
tr(STR_WEATHER_SETTINGS));
|
tr(STR_WEATHER_SETTINGS));
|
||||||
@@ -250,46 +264,7 @@ void WeatherSettingsActivity::render(RenderLock&&) {
|
|||||||
const int contentHeight =
|
const int contentHeight =
|
||||||
contentRect.height - (metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing * 2);
|
contentRect.height - (metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing * 2);
|
||||||
|
|
||||||
GUI.drawList(
|
drawMenuList(Rect{contentRect.x, contentTop, contentRect.width, contentHeight});
|
||||||
renderer, Rect(contentRect.x, contentTop, contentRect.width, contentHeight), MENU_ITEMS,
|
|
||||||
static_cast<int>(selectedIndex), [](int index) { return std::string(I18N.get(menuNames[index])); }, nullptr,
|
|
||||||
nullptr,
|
|
||||||
[this](int index) -> std::string {
|
|
||||||
switch (index) {
|
|
||||||
case 0: {
|
|
||||||
auto name = WEATHER_SETTINGS.getLocationName();
|
|
||||||
return name.empty() ? std::string(tr(STR_NOT_SET)) : name;
|
|
||||||
}
|
|
||||||
case 1: {
|
|
||||||
char buf[16];
|
|
||||||
snprintf(buf, sizeof(buf), "%.4f", WEATHER_SETTINGS.getLatitude());
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
||||||
case 2: {
|
|
||||||
char buf[16];
|
|
||||||
snprintf(buf, sizeof(buf), "%.4f", WEATHER_SETTINGS.getLongitude());
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
||||||
case 3:
|
|
||||||
return WEATHER_SETTINGS.getTempUnit() == WeatherTempUnit::CELSIUS ? "C" : "F";
|
|
||||||
case 4:
|
|
||||||
switch (WEATHER_SETTINGS.getWindUnit()) {
|
|
||||||
case WeatherWindUnit::KMH:
|
|
||||||
return "km/h";
|
|
||||||
case WeatherWindUnit::MS:
|
|
||||||
return "m/s";
|
|
||||||
case WeatherWindUnit::MPH:
|
|
||||||
return "mph";
|
|
||||||
case WeatherWindUnit::KNOTS:
|
|
||||||
return "kn";
|
|
||||||
}
|
|
||||||
return "km/h";
|
|
||||||
case 5:
|
|
||||||
return WEATHER_SETTINGS.getPrecipUnit() == WeatherPrecipUnit::MM ? "mm" : "in";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
|
|
||||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
||||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|||||||
@@ -5,42 +5,35 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "../Activity.h"
|
#include "../MenuListActivity.h"
|
||||||
#include "util/ButtonNavigator.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings submenu for weather configuration.
|
* Settings submenu for weather configuration.
|
||||||
* Supports city search via geocoding, manual lat/lon entry, and unit selection.
|
* Supports city search via geocoding, manual lat/lon entry, and unit selection.
|
||||||
*/
|
*/
|
||||||
class WeatherSettingsActivity final : public Activity {
|
class WeatherSettingsActivity final : public MenuListActivity {
|
||||||
public:
|
public:
|
||||||
explicit WeatherSettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
explicit WeatherSettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||||
: Activity("WeatherSettings", renderer, mappedInput) {}
|
: MenuListActivity("WeatherSettings", renderer, mappedInput) {
|
||||||
|
buildMenuItems();
|
||||||
|
}
|
||||||
|
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
|
||||||
void loop() override;
|
void loop() override;
|
||||||
void render(RenderLock&&) override;
|
void render(RenderLock&&) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ButtonNavigator buttonNavigator;
|
|
||||||
int selectedIndex = 0;
|
|
||||||
|
|
||||||
// City search results (populated when user searches)
|
|
||||||
std::vector<GeocodingResult> searchResults;
|
std::vector<GeocodingResult> searchResults;
|
||||||
bool showingSearchResults = false;
|
bool showingSearchResults = false;
|
||||||
bool searchInProgress = false;
|
|
||||||
std::string searchQuery;
|
std::string searchQuery;
|
||||||
|
|
||||||
static constexpr int MENU_ITEMS = 6; // Location, Lat, Lon, TempUnit, WindUnit, PrecipUnit
|
void buildMenuItems();
|
||||||
|
void onActionSelected(int index) override;
|
||||||
|
std::string getItemValueString(int index) const override;
|
||||||
|
void onBackPressed() override;
|
||||||
|
void onSettingToggled(int index) override;
|
||||||
|
|
||||||
void handleSelection();
|
|
||||||
void launchCitySearch();
|
void launchCitySearch();
|
||||||
void launchLatitudeEntry();
|
void launchLatitudeEntry();
|
||||||
void launchLongitudeEntry();
|
void launchLongitudeEntry();
|
||||||
void toggleTempUnit();
|
|
||||||
void toggleWindUnit();
|
|
||||||
void togglePrecipUnit();
|
|
||||||
|
|
||||||
bool preventAutoSleep() override { return true; }
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user