mirror of
https://github.com/pelgraine/Meck.git
synced 2026-10-30 19:52:01 +01:00
Max: Timezones world clock page
This commit is contained in:
1 parent
fe8bffd737
commit
3cc6d2c2c0
5 files changed
+228
-5
No files matched your search
@@ -285,6 +285,12 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
if (file.read((uint8_t *)_prefs.canned_msgs, sizeof(_prefs.canned_msgs)) != sizeof(_prefs.canned_msgs)) {
|
||||
memset(_prefs.canned_msgs, 0, sizeof(_prefs.canned_msgs)); // default: all slots empty
|
||||
}
|
||||
if (file.read((uint8_t *)&_prefs.clock_slot_a, sizeof(_prefs.clock_slot_a)) != sizeof(_prefs.clock_slot_a)) {
|
||||
_prefs.clock_slot_a = 0; // default: Zone 1 at UTC+0
|
||||
}
|
||||
if (file.read((uint8_t *)&_prefs.clock_slot_b, sizeof(_prefs.clock_slot_b)) != sizeof(_prefs.clock_slot_b)) {
|
||||
_prefs.clock_slot_b = 0; // default: Zone 2 at UTC+0
|
||||
}
|
||||
|
||||
// Clamp to valid ranges
|
||||
if (_prefs.dark_mode > 1) _prefs.dark_mode = 0;
|
||||
@@ -300,6 +306,8 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
if (_prefs.lora_antenna > 1) _prefs.lora_antenna = 0;
|
||||
if (_prefs.backlight_brightness_pct < 5 || _prefs.backlight_brightness_pct > 100) _prefs.backlight_brightness_pct = 100;
|
||||
if (_prefs.kb_backlight_pct < 5 || _prefs.kb_backlight_pct > 100) _prefs.kb_backlight_pct = 50;
|
||||
if (_prefs.clock_slot_a < -12 || _prefs.clock_slot_a > 14) _prefs.clock_slot_a = 0;
|
||||
if (_prefs.clock_slot_b < -12 || _prefs.clock_slot_b > 14) _prefs.clock_slot_b = 0;
|
||||
// Force NUL termination on each canned message slot in case of garbage
|
||||
for (int i = 0; i < (int)(sizeof(_prefs.canned_msgs) / sizeof(_prefs.canned_msgs[0])); i++) {
|
||||
_prefs.canned_msgs[i][sizeof(_prefs.canned_msgs[0]) - 1] = '\0';
|
||||
@@ -368,6 +376,8 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
||||
file.write((uint8_t *)&_prefs.backlight_brightness_pct, sizeof(_prefs.backlight_brightness_pct)); // 175
|
||||
file.write((uint8_t *)&_prefs.kb_backlight_pct, sizeof(_prefs.kb_backlight_pct)); // 176
|
||||
file.write((uint8_t *)_prefs.canned_msgs, sizeof(_prefs.canned_msgs)); // 177
|
||||
file.write((uint8_t *)&_prefs.clock_slot_a, sizeof(_prefs.clock_slot_a)); // 1520
|
||||
file.write((uint8_t *)&_prefs.clock_slot_b, sizeof(_prefs.clock_slot_b)); // 1521
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
@@ -87,9 +87,16 @@ struct NodePrefs { // persisted to file
|
||||
// Ten pre-written messages, edited in Settings -> Canned Messages and sent
|
||||
// from the channel screen (Max: speech-bubble capacitive button). Each slot
|
||||
// holds up to 133 chars + NUL; an empty string marks an unused slot. Older
|
||||
// prefs files short-read this final field and keep the all-empty default.
|
||||
// prefs files short-read this field and keep the all-empty default.
|
||||
char canned_msgs[CANNED_MSG_SLOTS][CANNED_MSG_LEN];
|
||||
|
||||
// --- Timezones home page (world clock) ---
|
||||
// UTC offsets (-12..+14) of the page's two extra zones; its Home row is
|
||||
// utc_offset_hours. Older prefs files short-read these final fields and
|
||||
// keep the default 0 (UTC+0).
|
||||
int8_t clock_slot_a; // Zone 1 UTC offset, default 0
|
||||
int8_t clock_slot_b; // Zone 2 UTC offset, default 0
|
||||
|
||||
// --- Font helpers (inline, no overhead) ---
|
||||
// Returns the DisplayDriver text-size index for "small/body" text.
|
||||
// T-Deck Pro: 0 = built-in 6×8 (or 7pt with custom fonts), 1 = 9pt.
|
||||
|
||||
@@ -1569,6 +1569,14 @@ static void openMapScreen() {
|
||||
// Home screen: long press = activate current page action
|
||||
// (BLE toggle, send advert, hibernate, GPS toggle, etc.)
|
||||
if (ui_task.isOnHomeScreen()) {
|
||||
// Timezones page: select the zone row under the finger first, so the
|
||||
// Enter opens that row's editor. While the editor is open the row is
|
||||
// left alone and the Enter saves.
|
||||
if (ui_task.isHomeOnTimezonesPage()) {
|
||||
int vx, vy;
|
||||
touchToVirtual(x, y, vx, vy);
|
||||
ui_task.selectHomeTimezoneRowAt(vy);
|
||||
}
|
||||
return (char)KEY_ENTER;
|
||||
}
|
||||
|
||||
@@ -4647,6 +4655,7 @@ void handleKeyboardInput() {
|
||||
|| ui_task.isOnAlarmScreen()
|
||||
#endif
|
||||
|| ui_task.isHomeOnShutdownPage()
|
||||
|| ui_task.isHomeOnTimezonesPage()
|
||||
|| ui_task.isOnRxLogScreen()
|
||||
) {
|
||||
ui_task.injectKey('s'); // Pass directly for scrolling
|
||||
@@ -4696,6 +4705,7 @@ void handleKeyboardInput() {
|
||||
#ifdef MECK_AUDIO_VARIANT
|
||||
|| ui_task.isOnAlarmScreen()
|
||||
#endif
|
||||
|| ui_task.isHomeOnTimezonesPage()
|
||||
|| ui_task.isOnRxLogScreen()
|
||||
) {
|
||||
ui_task.injectKey('w'); // Pass directly for scrolling
|
||||
@@ -5427,4 +5437,4 @@ void audio_eof_mp3(const char *info) {
|
||||
}
|
||||
#endif // !HAS_4G_MODEM
|
||||
|
||||
#endif // LilyGo_TDeck_Pro
|
||||
#endif // LilyGo_TDeck_Pro
|
||||
@@ -161,6 +161,7 @@ class HomeScreen : public UIScreen {
|
||||
BATTERY,
|
||||
#endif
|
||||
SHUTDOWN,
|
||||
TIMEZONES, // world clock; last page, so A or a right swipe from the tiles reaches it
|
||||
Count // keep as last
|
||||
};
|
||||
|
||||
@@ -176,6 +177,11 @@ class HomeScreen : public UIScreen {
|
||||
bool _poweroff_msg_shown; // true = "powering off..." already displayed once
|
||||
bool _editing_utc;
|
||||
int8_t _saved_utc_offset; // for cancel/undo
|
||||
// Timezones page: selected row (0 = Home, 1 = Zone 1, 2 = Zone 2) and
|
||||
// the zone offset editor, which holds the edited value until saved.
|
||||
uint8_t _tz_sel;
|
||||
bool _tz_editing;
|
||||
int8_t _tz_edit_val;
|
||||
|
||||
AdvertPath recent[UI_RECENT_LIST_SIZE];
|
||||
|
||||
@@ -284,6 +290,156 @@ void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts,
|
||||
}
|
||||
#endif
|
||||
|
||||
// ==========================================================================
|
||||
// Timezones home page (world clock) -- ported from the watch and Meck-P4
|
||||
//
|
||||
// Row 0 = Home (the device utc_offset_hours), rows 1/2 = the two saved
|
||||
// zones (prefs clock_slot_a/b). Each row shows the zone label and offset,
|
||||
// the local HH:MM, a +/-1D day marker when the zone sits on a different
|
||||
// calendar day to Home, and city codes for the offset (table copied from
|
||||
// Meck-P4, which copied it from the watch). W/S or a long-press picks a
|
||||
// row and Enter opens its offset editor; in the editor W/S or taps adjust
|
||||
// the offset, Enter or a long-press saves, Q cancels. Editing Home
|
||||
// changes the device UTC offset, as on the watch and P4.
|
||||
// ==========================================================================
|
||||
|
||||
// Row 0 label y and row pitch, in virtual coordinates. Also used by
|
||||
// selectTimezoneRowAt() for the touch long-press hit test.
|
||||
enum { TZ_ROW_TOP = 16, TZ_ROW_H = 34 };
|
||||
|
||||
static const char* tzRowLabel(int r) {
|
||||
return (r == 1) ? "Zone 1" : (r == 2) ? "Zone 2" : "Home";
|
||||
}
|
||||
|
||||
static const char* tzCities(int8_t off) {
|
||||
static const char* const CITIES[27] = {
|
||||
"BIT/HWL", /* -12 */ "PPG/NIU/MDY", /* -11 */ "HNL/PPT/RAR", /* -10 */
|
||||
"ANC/JNU", /* -9 */ "LAX/VAN/SEA", /* -8 */ "PHX/HMO/MZT", /* -7 */
|
||||
"MEX/REG/GUA", /* -6 */ "BOG/LIM/PTY", /* -5 */ "CCS/LPB/SDQ", /* -4 */
|
||||
"BUE/SAO/MVD", /* -3 */ "FEN/SGS", /* -2 */ "RAI/PDL", /* -1 */
|
||||
"REY/ACC/DKR", /* 0 */ "LOS/ALG/TUN", /* +1 */ "JNB/KRT/HRE", /* +2 */
|
||||
"NBO/MOW/ADD", /* +3 */ "DXB/BAK/TBS", /* +4 */ "KHI/TAS/SVX", /* +5 */
|
||||
"DAC/ALA/OMS", /* +6 */ "BKK/JKT/HAN", /* +7 */ "PER/BEI/HKG", /* +8 */
|
||||
"TYO/SEL/YKS", /* +9 */ "BNE/POM/VLA", /* +10 */ "NOU/HIR/VLI", /* +11 */
|
||||
"SUV/TRW/MAJ", /* +12 */ "TBU/APW", /* +13 */ "CXI" /* +14 */
|
||||
};
|
||||
int i = (int)off + 12;
|
||||
if (i < 0 || i > 26) return "";
|
||||
return CITIES[i];
|
||||
}
|
||||
|
||||
int8_t tzRowOffset(int r) const {
|
||||
if (r == 1) return _node_prefs->clock_slot_a;
|
||||
if (r == 2) return _node_prefs->clock_slot_b;
|
||||
return _node_prefs->utc_offset_hours;
|
||||
}
|
||||
|
||||
// Same clock-validity test as the header clock: before the clock is set
|
||||
// the page shows "Clock not set" and its keys do nothing.
|
||||
bool tzClockValid() const { return _rtc->getCurrentTime() > 1700000000; }
|
||||
|
||||
void renderTimezonesPage(DisplayDriver& display) {
|
||||
char buf[24];
|
||||
|
||||
if (_tz_editing) {
|
||||
// Offset editor, laid out like the watch's edit page: row name, the
|
||||
// offset being edited, its city codes, then the key and touch hints.
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.drawTextCentered(display.width() / 2, 18, tzRowLabel(_tz_sel));
|
||||
display.setTextSize(5);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
snprintf(buf, sizeof(buf), "UTC%+d", (int)_tz_edit_val);
|
||||
display.drawTextCentered(display.width() / 2, 48, buf);
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.drawTextCentered(display.width() / 2, 64, tzCities(_tz_edit_val));
|
||||
display.setTextSize(_node_prefs->smallTextSize());
|
||||
display.drawTextCentered(display.width() / 2, 84, "W/S:adj Enter:ok Q:cancel");
|
||||
display.drawTextCentered(display.width() / 2, 96, "Tap:-/+ Hold:save");
|
||||
display.setTextSize(1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tzClockValid()) {
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.drawTextCentered(display.width() / 2, 50, "Clock not set");
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t now = _rtc->getCurrentTime();
|
||||
const int32_t home_local = (int32_t)now + ((int32_t)tzRowOffset(0) * 3600);
|
||||
for (int r = 0; r < 3; r++) {
|
||||
const int ry = TZ_ROW_TOP + r * TZ_ROW_H;
|
||||
const int8_t off = tzRowOffset(r);
|
||||
const int32_t local = (int32_t)now + ((int32_t)off * 3600);
|
||||
int hrs = (local / 3600) % 24;
|
||||
if (hrs < 0) hrs += 24;
|
||||
int mins = (local / 60) % 60;
|
||||
if (mins < 0) mins += 60;
|
||||
|
||||
// Label line: selection marker, zone name and offset
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
if (r == _tz_sel) display.drawTextLeftAlign(0, ry, ">");
|
||||
snprintf(buf, sizeof(buf), "%s UTC%+d", tzRowLabel(r), (int)off);
|
||||
display.drawTextLeftAlign(7, ry, buf);
|
||||
|
||||
// Local time, large
|
||||
display.setTextSize(5);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
snprintf(buf, sizeof(buf), "%02d:%02d", hrs, mins);
|
||||
display.drawTextCentered(display.width() / 2, ry + 17, buf);
|
||||
|
||||
// Day marker (left) and city codes (right) under the time
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
const int dayDiff = (int)((local / 86400) - (home_local / 86400));
|
||||
if (dayDiff != 0) {
|
||||
snprintf(buf, sizeof(buf), "%+dD", dayDiff);
|
||||
display.drawTextLeftAlign(7, ry + 27, buf);
|
||||
}
|
||||
display.drawTextRightAlign(display.width() - 1 - EINK_X_OFFSET, ry + 27, tzCities(off));
|
||||
}
|
||||
}
|
||||
|
||||
// Keys on the Timezones page. Returns true when the key is used here;
|
||||
// anything else falls through to the page cycling in handleInput().
|
||||
// Keyboard W/S arrive as 'w'/'s' because main.cpp passes them through on
|
||||
// this page (as it passes S through on the Hibernate page). Touch taps
|
||||
// arrive as KEY_PREV (left half) / KEY_NEXT (right half) and a long-press
|
||||
// as KEY_ENTER, after selectTimezoneRowAt() has picked the row.
|
||||
bool handleTimezonesKey(char c) {
|
||||
if (_tz_editing) {
|
||||
// Zone offset editor: intercept all keys. Q and Shift+Del never get
|
||||
// here: main.cpp sends them to UITask::gotoHomeScreen(), whose
|
||||
// cancelEditing() closes the editor without saving.
|
||||
if (c == 'w' || c == KEY_NEXT) {
|
||||
if (_tz_edit_val < 14) _tz_edit_val++;
|
||||
} else if (c == 's' || c == KEY_PREV) {
|
||||
if (_tz_edit_val > -12) _tz_edit_val--;
|
||||
} else if (c == KEY_ENTER) {
|
||||
if (_tz_sel == 1) _node_prefs->clock_slot_a = _tz_edit_val;
|
||||
else if (_tz_sel == 2) _node_prefs->clock_slot_b = _tz_edit_val;
|
||||
else _node_prefs->utc_offset_hours = _tz_edit_val;
|
||||
the_mesh.savePrefs();
|
||||
Serial.printf("Timezones: %s = UTC%+d\n", tzRowLabel(_tz_sel), (int)_tz_edit_val);
|
||||
_tz_editing = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!tzClockValid()) return false;
|
||||
if (c == 'w') { _tz_sel = (_tz_sel + 2) % 3; return true; }
|
||||
if (c == 's') { _tz_sel = (_tz_sel + 1) % 3; return true; }
|
||||
if (c == KEY_ENTER) {
|
||||
_tz_edit_val = tzRowOffset(_tz_sel);
|
||||
_tz_editing = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CayenneLPP sensors_lpp;
|
||||
int sensors_nb = 0;
|
||||
bool sensors_scroll = false;
|
||||
@@ -315,17 +471,34 @@ public:
|
||||
HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs)
|
||||
: _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0),
|
||||
_shutdown_init(false), _shutdown_at(0), _poweroff_selected(false), _poweroff_confirm(false),
|
||||
_poweroff_msg_shown(false), _editing_utc(false), _saved_utc_offset(0), sensors_lpp(200) { }
|
||||
_poweroff_msg_shown(false), _editing_utc(false), _saved_utc_offset(0),
|
||||
_tz_sel(0), _tz_editing(false), _tz_edit_val(0), sensors_lpp(200) { }
|
||||
|
||||
bool isEditingUTC() const { return _editing_utc; }
|
||||
bool isFirstPage() const { return _page == HomePage::FIRST; }
|
||||
bool isOnRecentPage() const { return _page == HomePage::RECENT; }
|
||||
bool isOnShutdownPage() const { return _page == HomePage::SHUTDOWN; }
|
||||
bool isOnTimezonesPage() const { return _page == HomePage::TIMEZONES; }
|
||||
bool isEditingTimezone() const { return _tz_editing; }
|
||||
// Touch long-press on the Timezones page (main.cpp mapTouchLongPress):
|
||||
// select the zone row under the finger before the Enter that opens its
|
||||
// editor. vy comes from touchToVirtual(), which lands 5 units below the
|
||||
// render y of the same point (setCursor adds 5), so row r's label, time
|
||||
// and city codes sit in the band from TZ_ROW_TOP + r * TZ_ROW_H to the
|
||||
// next row's.
|
||||
void selectTimezoneRowAt(int vy) {
|
||||
if (_tz_editing) return; // in the editor a long-press saves instead
|
||||
int r = (vy - TZ_ROW_TOP) / TZ_ROW_H;
|
||||
if (vy < TZ_ROW_TOP) r = 0;
|
||||
if (r > 2) r = 2;
|
||||
_tz_sel = (uint8_t)r;
|
||||
}
|
||||
void cancelEditing() {
|
||||
if (_editing_utc) {
|
||||
_node_prefs->utc_offset_hours = _saved_utc_offset;
|
||||
_editing_utc = false;
|
||||
}
|
||||
_tz_editing = false; // Timezones zone editor: discard the unsaved value
|
||||
}
|
||||
|
||||
void poll() override {
|
||||
@@ -1089,7 +1262,10 @@ public:
|
||||
display.drawTextCentered(display.width() / 2, y2, line2);
|
||||
}
|
||||
}
|
||||
return _editing_utc ? 700 : 5000;
|
||||
else if (_page == HomePage::TIMEZONES) {
|
||||
renderTimezonesPage(display);
|
||||
}
|
||||
return (_editing_utc || _tz_editing) ? 700 : 5000;
|
||||
}
|
||||
|
||||
bool handleInput(char c) override {
|
||||
@@ -1127,6 +1303,13 @@ public:
|
||||
return true; // Consume all other keys while editing
|
||||
}
|
||||
|
||||
// Timezones page: W/S row select, Enter to edit, and the zone editor's
|
||||
// keys (see handleTimezonesKey). Keys it does not use fall through to
|
||||
// the page cycling below.
|
||||
if (_page == HomePage::TIMEZONES && handleTimezonesKey(c)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// SHUTDOWN page -- intercept up/down and Enter before page cycling
|
||||
if (_page == HomePage::SHUTDOWN) {
|
||||
if (_poweroff_confirm) {
|
||||
@@ -2408,7 +2591,8 @@ void UITask::gotoHomeScreen() {
|
||||
}
|
||||
|
||||
bool UITask::isEditingHomeScreen() const {
|
||||
return curr == home && ((HomeScreen *) home)->isEditingUTC();
|
||||
return curr == home && (((HomeScreen *) home)->isEditingUTC() ||
|
||||
((HomeScreen *) home)->isEditingTimezone());
|
||||
}
|
||||
|
||||
bool UITask::isHomeOnRecentPage() const {
|
||||
@@ -2419,6 +2603,14 @@ bool UITask::isHomeOnShutdownPage() const {
|
||||
return curr == home && ((HomeScreen *) home)->isOnShutdownPage();
|
||||
}
|
||||
|
||||
bool UITask::isHomeOnTimezonesPage() const {
|
||||
return curr == home && ((HomeScreen *) home)->isOnTimezonesPage();
|
||||
}
|
||||
|
||||
void UITask::selectHomeTimezoneRowAt(int vy) {
|
||||
if (curr == home) ((HomeScreen *) home)->selectTimezoneRowAt(vy);
|
||||
}
|
||||
|
||||
void UITask::gotoChannelScreen(bool resetDmView) {
|
||||
ChannelScreen* cs = (ChannelScreen*)channel_screen;
|
||||
// If currently showing DM view, reset to channel 0 (unless caller opts out)
|
||||
|
||||
@@ -291,6 +291,10 @@ public:
|
||||
// Check if home screen is showing the Recent Adverts page
|
||||
bool isHomeOnRecentPage() const;
|
||||
bool isHomeOnShutdownPage() const;
|
||||
// Check if home screen is showing the Timezones (world clock) page
|
||||
bool isHomeOnTimezonesPage() const;
|
||||
// Touch long-press on the Timezones page: select the zone row at virtual y
|
||||
void selectHomeTimezoneRowAt(int vy);
|
||||
|
||||
// Inject a key press from external source (e.g., keyboard)
|
||||
void injectKey(char c);
|
||||
|
||||
Reference in new issue
Block a user