mod: add clock settings tab, timezone support, and clock size option

Fix clock persistence bug caused by stale legacy read in settings
deserialization. Add clock size setting (Small/Medium/Large) and
timezone selection with North American presets plus custom UTC offset.
Move all clock-related settings into a dedicated Clock tab, rename
"Home Screen Clock" to "Clock", and move minute-change detection to
main loop so the header clock updates on every screen.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
cottongin
2026-02-17 03:46:06 -05:00
parent 38a87298f3
commit 966fbef3d1
22 changed files with 435 additions and 42 deletions

View File

@@ -11,7 +11,6 @@
#include <cstdio>
#include <cstring>
#include <ctime>
#include <vector>
#include "Battery.h"
@@ -192,19 +191,6 @@ void HomeActivity::freeCoverBuffer() {
}
void HomeActivity::loop() {
// Refresh the screen when the displayed minute changes (clock update)
if (SETTINGS.homeScreenClock != CrossPointSettings::CLOCK_OFF) {
time_t now = time(nullptr);
struct tm* t = localtime(&now);
if (t != nullptr && t->tm_year > 100) {
const int currentMinute = t->tm_hour * 60 + t->tm_min;
if (lastRenderedMinute >= 0 && currentMinute != lastRenderedMinute) {
requestUpdate();
}
lastRenderedMinute = currentMinute;
}
}
const int menuCount = getMenuItemCount();
buttonNavigator.onNext([this, menuCount] {

View File

@@ -16,7 +16,6 @@ class HomeActivity final : public Activity {
bool recentsLoaded = false;
bool firstRenderDone = false;
bool hasOpdsUrl = false;
int lastRenderedMinute = -1; // Track displayed minute for clock auto-update
bool coverRendered = false; // Track if cover has been rendered once
bool coverBufferStored = false; // Track if cover buffer is stored
uint8_t* coverBuffer = nullptr; // HomeActivity's own buffer for cover image

View File

@@ -0,0 +1,101 @@
#include "SetTimezoneOffsetActivity.h"
#include <GfxRenderer.h>
#include <I18n.h>
#include <cstdio>
#include <cstdlib>
#include "CrossPointSettings.h"
#include "MappedInputManager.h"
#include "components/UITheme.h"
#include "fontIds.h"
void SetTimezoneOffsetActivity::onEnter() {
Activity::onEnter();
offsetHours = SETTINGS.timezoneOffsetHours;
requestUpdate();
}
void SetTimezoneOffsetActivity::onExit() { Activity::onExit(); }
void SetTimezoneOffsetActivity::loop() {
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
onBack();
return;
}
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
SETTINGS.timezoneOffsetHours = offsetHours;
SETTINGS.saveToFile();
// Apply timezone immediately
setenv("TZ", SETTINGS.getTimezonePosixStr(), 1);
tzset();
onBack();
return;
}
if (mappedInput.wasPressed(MappedInputManager::Button::Up)) {
if (offsetHours < 14) {
offsetHours++;
requestUpdate();
}
return;
}
if (mappedInput.wasPressed(MappedInputManager::Button::Down)) {
if (offsetHours > -12) {
offsetHours--;
requestUpdate();
}
return;
}
}
void SetTimezoneOffsetActivity::render(Activity::RenderLock&&) {
renderer.clearScreen();
const auto pageWidth = renderer.getScreenWidth();
const int lineHeight12 = renderer.getLineHeight(UI_12_FONT_ID);
// Title
renderer.drawCenteredText(UI_12_FONT_ID, 20, tr(STR_SET_UTC_OFFSET), true, EpdFontFamily::BOLD);
// Format the offset string
char offsetStr[16];
if (offsetHours >= 0) {
snprintf(offsetStr, sizeof(offsetStr), "UTC+%d", offsetHours);
} else {
snprintf(offsetStr, sizeof(offsetStr), "UTC%d", offsetHours);
}
const int textWidth = renderer.getTextWidth(UI_12_FONT_ID, offsetStr);
const int startX = (pageWidth - textWidth) / 2;
const int valueY = 80;
// Draw selection highlight
constexpr int highlightPad = 10;
renderer.fillRoundedRect(startX - highlightPad, valueY - 4, textWidth + highlightPad * 2, lineHeight12 + 8, 6,
Color::LightGray);
// Draw the offset text
renderer.drawText(UI_12_FONT_ID, startX, valueY, offsetStr, true);
// Draw up/down arrows
const int arrowX = pageWidth / 2;
const int arrowUpY = valueY - 20;
const int arrowDownY = valueY + lineHeight12 + 12;
constexpr int arrowSize = 6;
for (int row = 0; row < arrowSize; row++) {
renderer.drawLine(arrowX - row, arrowUpY + row, arrowX + row, arrowUpY + row);
}
for (int row = 0; row < arrowSize; row++) {
renderer.drawLine(arrowX - row, arrowDownY + arrowSize - 1 - row, arrowX + row, arrowDownY + arrowSize - 1 - row);
}
// Button hints
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SAVE), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include <functional>
#include "activities/Activity.h"
class SetTimezoneOffsetActivity final : public Activity {
public:
explicit SetTimezoneOffsetActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const std::function<void()>& onBack)
: Activity("SetTZOffset", renderer, mappedInput), onBack(onBack) {}
void onEnter() override;
void onExit() override;
void loop() override;
void render(Activity::RenderLock&&) override;
private:
const std::function<void()> onBack;
int8_t offsetHours = 0;
};

View File

@@ -3,6 +3,9 @@
#include <GfxRenderer.h>
#include <Logging.h>
#include <algorithm>
#include <cstdlib>
#include "ButtonRemapActivity.h"
#include "CalibreSettingsActivity.h"
#include "ClearCacheActivity.h"
@@ -12,19 +15,22 @@
#include "MappedInputManager.h"
#include "OtaUpdateActivity.h"
#include "SetTimeActivity.h"
#include "SetTimezoneOffsetActivity.h"
#include "SettingsList.h"
#include "activities/network/WifiSelectionActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
const StrId SettingsActivity::categoryNames[categoryCount] = {StrId::STR_CAT_DISPLAY, StrId::STR_CAT_READER,
StrId::STR_CAT_CONTROLS, StrId::STR_CAT_SYSTEM};
StrId::STR_CAT_CONTROLS, StrId::STR_CAT_SYSTEM,
StrId::STR_CAT_CLOCK};
void SettingsActivity::onEnter() {
Activity::onEnter();
// Build per-category vectors from the shared settings list
displaySettings.clear();
clockSettings.clear();
readerSettings.clear();
controlsSettings.clear();
systemSettings.clear();
@@ -33,6 +39,8 @@ void SettingsActivity::onEnter() {
if (setting.category == StrId::STR_NONE_OPT) continue;
if (setting.category == StrId::STR_CAT_DISPLAY) {
displaySettings.push_back(std::move(setting));
} else if (setting.category == StrId::STR_CAT_CLOCK) {
clockSettings.push_back(std::move(setting));
} else if (setting.category == StrId::STR_CAT_READER) {
readerSettings.push_back(std::move(setting));
} else if (setting.category == StrId::STR_CAT_CONTROLS) {
@@ -44,7 +52,7 @@ void SettingsActivity::onEnter() {
}
// Append device-only ACTION items
displaySettings.push_back(SettingInfo::Action(StrId::STR_SET_TIME, SettingAction::SetTime));
rebuildClockActions();
controlsSettings.insert(controlsSettings.begin(),
SettingInfo::Action(StrId::STR_REMAP_FRONT_BUTTONS, SettingAction::RemapFrontButtons));
systemSettings.push_back(SettingInfo::Action(StrId::STR_WIFI_NETWORKS, SettingAction::Network));
@@ -136,6 +144,9 @@ void SettingsActivity::loop() {
case 3:
currentSettings = &systemSettings;
break;
case 4:
currentSettings = &clockSettings;
break;
}
settingsCount = static_cast<int>(currentSettings->size());
}
@@ -207,6 +218,9 @@ void SettingsActivity::toggleCurrentSetting() {
case SettingAction::SetTime:
enterSubActivity(new SetTimeActivity(renderer, mappedInput, onComplete));
break;
case SettingAction::SetTimezoneOffset:
enterSubActivity(new SetTimezoneOffsetActivity(renderer, mappedInput, onComplete));
break;
case SettingAction::None:
// Do nothing
break;
@@ -216,6 +230,37 @@ void SettingsActivity::toggleCurrentSetting() {
}
SETTINGS.saveToFile();
// Apply timezone whenever settings change (idempotent, cheap)
setenv("TZ", SETTINGS.getTimezonePosixStr(), 1);
tzset();
// Rebuild clock actions (show/hide "Set UTC Offset" based on timezone selection)
rebuildClockActions();
}
void SettingsActivity::rebuildClockActions() {
// Remove any existing ACTION items from clockSettings (keep enum settings from getSettingsList)
clockSettings.erase(std::remove_if(clockSettings.begin(), clockSettings.end(),
[](const SettingInfo& s) { return s.type == SettingType::ACTION; }),
clockSettings.end());
// Always add Set Time
clockSettings.push_back(SettingInfo::Action(StrId::STR_SET_TIME, SettingAction::SetTime));
// Only add Set UTC Offset when timezone is set to Custom
if (SETTINGS.timezone == CrossPointSettings::TZ_CUSTOM) {
clockSettings.push_back(SettingInfo::Action(StrId::STR_SET_UTC_OFFSET, SettingAction::SetTimezoneOffset));
}
// Update settingsCount if we're currently viewing the clock category
if (currentSettings == &clockSettings) {
settingsCount = static_cast<int>(clockSettings.size());
// Clamp selection to avoid pointing past the end of the list
if (selectedSettingIndex > settingsCount) {
selectedSettingIndex = settingsCount;
}
}
}
void SettingsActivity::render(Activity::RenderLock&&) {

View File

@@ -22,6 +22,7 @@ enum class SettingAction {
CheckForUpdates,
Language,
SetTime,
SetTimezoneOffset,
};
struct SettingInfo {
@@ -143,6 +144,7 @@ class SettingsActivity final : public ActivityWithSubactivity {
// Per-category settings derived from shared list + device-only actions
std::vector<SettingInfo> displaySettings;
std::vector<SettingInfo> clockSettings;
std::vector<SettingInfo> readerSettings;
std::vector<SettingInfo> controlsSettings;
std::vector<SettingInfo> systemSettings;
@@ -150,11 +152,12 @@ class SettingsActivity final : public ActivityWithSubactivity {
const std::function<void()> onGoHome;
static constexpr int categoryCount = 4;
static constexpr int categoryCount = 5;
static const StrId categoryNames[categoryCount];
void enterCategory(int categoryIndex);
void toggleCurrentSetting();
void rebuildClockActions();
public:
explicit SettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,