2025-03-04 23:09:43 +11:00
|
|
|
|
#include "UITask.h"
|
|
|
|
|
|
#include <helpers/TxtDataHelpers.h>
|
2026-05-10 15:18:35 +02:00
|
|
|
|
#include <time.h>
|
2025-08-16 20:04:54 +10:00
|
|
|
|
#include "../MyMesh.h"
|
2025-08-08 20:01:31 +10:00
|
|
|
|
#include "target.h"
|
2025-10-27 17:58:29 +01:00
|
|
|
|
#ifdef WIFI_SSID
|
|
|
|
|
|
#include <WiFi.h>
|
|
|
|
|
|
#endif
|
2025-03-04 23:09:43 +11:00
|
|
|
|
|
2025-09-03 18:17:37 +02:00
|
|
|
|
#ifndef AUTO_OFF_MILLIS
|
|
|
|
|
|
#define AUTO_OFF_MILLIS 15000 // 15 seconds
|
|
|
|
|
|
#endif
|
2025-06-07 15:57:22 -07:00
|
|
|
|
#define BOOT_SCREEN_MILLIS 3000 // 3 seconds
|
2025-03-04 23:09:43 +11:00
|
|
|
|
|
2025-04-24 21:54:37 -04:00
|
|
|
|
#ifdef PIN_STATUS_LED
|
|
|
|
|
|
#define LED_ON_MILLIS 20
|
|
|
|
|
|
#define LED_ON_MSG_MILLIS 200
|
|
|
|
|
|
#define LED_CYCLE_MILLIS 4000
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
|
#define LONG_PRESS_MILLIS 1200
|
|
|
|
|
|
|
2025-08-16 18:13:50 +02:00
|
|
|
|
#ifndef UI_RECENT_LIST_SIZE
|
|
|
|
|
|
#define UI_RECENT_LIST_SIZE 4
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
2025-10-16 17:33:22 +11:00
|
|
|
|
#if UI_HAS_JOYSTICK
|
|
|
|
|
|
#define PRESS_LABEL "press Enter"
|
|
|
|
|
|
#else
|
|
|
|
|
|
#define PRESS_LABEL "long press"
|
|
|
|
|
|
#endif
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
|
|
|
|
|
#include "icons.h"
|
|
|
|
|
|
|
|
|
|
|
|
class SplashScreen : public UIScreen {
|
|
|
|
|
|
UITask* _task;
|
|
|
|
|
|
unsigned long dismiss_after;
|
|
|
|
|
|
char _version_info[12];
|
|
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
SplashScreen(UITask* task) : _task(task) {
|
|
|
|
|
|
// strip off dash and commit hash by changing dash to null terminator
|
|
|
|
|
|
// e.g: v1.2.3-abcdef -> v1.2.3
|
|
|
|
|
|
const char *ver = FIRMWARE_VERSION;
|
|
|
|
|
|
const char *dash = strchr(ver, '-');
|
|
|
|
|
|
|
|
|
|
|
|
int len = dash ? dash - ver : strlen(ver);
|
|
|
|
|
|
if (len >= sizeof(_version_info)) len = sizeof(_version_info) - 1;
|
|
|
|
|
|
memcpy(_version_info, ver, len);
|
|
|
|
|
|
_version_info[len] = 0;
|
|
|
|
|
|
|
|
|
|
|
|
dismiss_after = millis() + BOOT_SCREEN_MILLIS;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int render(DisplayDriver& display) override {
|
|
|
|
|
|
// meshcore logo
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
int logoWidth = 128;
|
|
|
|
|
|
display.drawXbm((display.width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
|
|
|
|
|
|
|
|
|
|
|
|
// version info
|
|
|
|
|
|
display.setTextSize(2);
|
|
|
|
|
|
display.drawTextCentered(display.width()/2, 22, _version_info);
|
|
|
|
|
|
|
|
|
|
|
|
display.setTextSize(1);
|
|
|
|
|
|
display.drawTextCentered(display.width()/2, 42, FIRMWARE_BUILD_DATE);
|
|
|
|
|
|
|
2026-05-10 22:58:46 +02:00
|
|
|
|
#ifdef FIRMWARE_PLUS_BUILD
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.fillRect(0, 53, display.width(), 10);
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
display.drawTextCentered(display.width()/2, 54, "Plus for Wio");
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-10 22:58:46 +02:00
|
|
|
|
#endif
|
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
|
return 1000;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void poll() override {
|
|
|
|
|
|
if (millis() >= dismiss_after) {
|
|
|
|
|
|
_task->gotoHomeScreen();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-05-11 16:52:24 +02:00
|
|
|
|
static const int QUICK_MSGS_MAX = 10;
|
|
|
|
|
|
|
|
|
|
|
|
// On-screen keyboard layout (4 rows × 10 cols)
|
|
|
|
|
|
static const char KB_CHARS[4][10] = {
|
|
|
|
|
|
{'a','b','c','d','e','f','g','h','i','j'},
|
|
|
|
|
|
{'k','l','m','n','o','p','q','r','s','t'},
|
|
|
|
|
|
{'u','v','w','x','y','z','.',' ','!','?'},
|
|
|
|
|
|
{'1','2','3','4','5','6','7','8','9','0'},
|
|
|
|
|
|
};
|
|
|
|
|
|
static const int KB_ROWS_CHAR = 4;
|
|
|
|
|
|
static const int KB_COLS_CHAR = 10;
|
|
|
|
|
|
static const int KB_SPECIAL = 5; // [^] [Sp] [Del] [{}] [OK]
|
|
|
|
|
|
static const char* KB_PH_LIST[] = { "{loc}", "{time}" };
|
|
|
|
|
|
static const int KB_PH_COUNT = 2;
|
|
|
|
|
|
static const int KB_MAX_LEN = 139;
|
|
|
|
|
|
static const int KB_CELL_W = 12; // 10 cols × 12px = 120px of 128px display
|
|
|
|
|
|
static const int KB_CELL_H = 9;
|
|
|
|
|
|
static const int KB_TEXT_Y = 0;
|
|
|
|
|
|
static const int KB_SEP_Y = 9;
|
|
|
|
|
|
static const int KB_CHARS_Y = 11; // first char row
|
|
|
|
|
|
static const int KB_SPECIAL_Y = 48;
|
|
|
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
|
class SettingsScreen : public UIScreen {
|
|
|
|
|
|
UITask* _task;
|
|
|
|
|
|
|
|
|
|
|
|
enum SettingItem {
|
2026-05-11 16:52:24 +02:00
|
|
|
|
// Display section
|
|
|
|
|
|
SECTION_DISPLAY,
|
2026-05-10 15:18:35 +02:00
|
|
|
|
BRIGHTNESS,
|
2026-05-11 16:52:24 +02:00
|
|
|
|
AUTO_OFF,
|
|
|
|
|
|
BATT_DISPLAY,
|
|
|
|
|
|
// Sound section
|
|
|
|
|
|
SECTION_SOUND,
|
2026-05-10 15:18:35 +02:00
|
|
|
|
BUZZER,
|
2026-05-11 16:52:24 +02:00
|
|
|
|
// Radio section
|
|
|
|
|
|
SECTION_RADIO,
|
2026-05-10 15:18:35 +02:00
|
|
|
|
TX_POWER,
|
2026-05-11 16:52:24 +02:00
|
|
|
|
// System section
|
|
|
|
|
|
SECTION_SYSTEM,
|
|
|
|
|
|
TIMEZONE,
|
|
|
|
|
|
LOW_BAT,
|
2026-05-10 15:18:35 +02:00
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
2026-05-11 16:52:24 +02:00
|
|
|
|
// GPS section
|
|
|
|
|
|
SECTION_GPS,
|
2026-05-10 15:18:35 +02:00
|
|
|
|
GPS_INTERVAL,
|
|
|
|
|
|
#endif
|
2026-05-11 19:19:44 +02:00
|
|
|
|
// Contacts section
|
|
|
|
|
|
SECTION_CONTACTS, DM_FILTER, ROOM_FILTER,
|
2026-05-11 16:52:24 +02:00
|
|
|
|
// Messages section
|
|
|
|
|
|
SECTION_MESSAGES,
|
|
|
|
|
|
MSG_SLOT_0, MSG_SLOT_1, MSG_SLOT_2, MSG_SLOT_3, MSG_SLOT_4,
|
|
|
|
|
|
MSG_SLOT_5, MSG_SLOT_6, MSG_SLOT_7, MSG_SLOT_8, MSG_SLOT_9,
|
2026-05-10 15:18:35 +02:00
|
|
|
|
Count
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static const int VISIBLE_ITEMS = 4;
|
|
|
|
|
|
static const int ITEM_H = 11;
|
|
|
|
|
|
static const int START_Y = 12;
|
|
|
|
|
|
static const int VAL_X = 80;
|
|
|
|
|
|
|
|
|
|
|
|
int _selected;
|
|
|
|
|
|
int _scroll;
|
2026-05-10 22:12:29 +02:00
|
|
|
|
bool _dirty;
|
2026-05-10 15:18:35 +02:00
|
|
|
|
|
|
|
|
|
|
static const uint16_t AUTO_OFF_OPTS[5];
|
|
|
|
|
|
static const char* AUTO_OFF_LABELS[5];
|
|
|
|
|
|
static const int AUTO_OFF_COUNT = 5;
|
|
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
|
|
|
|
|
static const uint32_t GPS_INTERVAL_OPTS[6];
|
|
|
|
|
|
static const char* GPS_INTERVAL_LABELS[6];
|
|
|
|
|
|
static const int GPS_INTERVAL_COUNT = 6;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
static const uint16_t LOW_BAT_OPTS[7];
|
|
|
|
|
|
static const char* LOW_BAT_LABELS[7];
|
|
|
|
|
|
static const int LOW_BAT_COUNT = 7;
|
|
|
|
|
|
static const char* BATT_DISPLAY_LABELS[3];
|
|
|
|
|
|
static const int BATT_DISPLAY_COUNT = 3;
|
|
|
|
|
|
|
|
|
|
|
|
int lowBatIndex() {
|
2026-05-10 20:26:05 +02:00
|
|
|
|
NodePrefs* p = _task->getNodePrefs();
|
|
|
|
|
|
if (!p) return 0;
|
2026-05-10 15:18:35 +02:00
|
|
|
|
for (int i = 0; i < LOW_BAT_COUNT; i++)
|
2026-05-10 20:26:05 +02:00
|
|
|
|
if (LOW_BAT_OPTS[i] == p->low_batt_mv) return i;
|
|
|
|
|
|
return 0;
|
2026-05-10 15:18:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void renderBar(DisplayDriver& display, int x, int y, int value, int max_val) {
|
|
|
|
|
|
const int box_w = 7, box_h = 7, gap = 2;
|
|
|
|
|
|
for (int i = 0; i < max_val; i++) {
|
|
|
|
|
|
int bx = x + i * (box_w + gap);
|
|
|
|
|
|
if (i < value) display.fillRect(bx, y, box_w, box_h);
|
|
|
|
|
|
else display.drawRect(bx, y, box_w, box_h);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int autoOffIndex() {
|
2026-05-10 20:26:05 +02:00
|
|
|
|
NodePrefs* p = _task->getNodePrefs();
|
|
|
|
|
|
if (!p) return 1;
|
2026-05-10 15:18:35 +02:00
|
|
|
|
for (int i = 0; i < AUTO_OFF_COUNT; i++)
|
2026-05-10 20:26:05 +02:00
|
|
|
|
if (AUTO_OFF_OPTS[i] == p->auto_off_secs) return i;
|
|
|
|
|
|
return 1;
|
2026-05-10 15:18:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
|
|
|
|
|
int gpsIntervalIndex() {
|
2026-05-10 20:26:05 +02:00
|
|
|
|
NodePrefs* p = _task->getNodePrefs();
|
|
|
|
|
|
if (!p) return 0;
|
2026-05-10 15:18:35 +02:00
|
|
|
|
for (int i = 0; i < GPS_INTERVAL_COUNT; i++)
|
2026-05-10 20:26:05 +02:00
|
|
|
|
if (GPS_INTERVAL_OPTS[i] == p->gps_interval) return i;
|
2026-05-10 15:18:35 +02:00
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
2026-05-11 16:52:24 +02:00
|
|
|
|
bool isSection(int item) const {
|
|
|
|
|
|
return item == SECTION_DISPLAY || item == SECTION_SOUND ||
|
|
|
|
|
|
item == SECTION_RADIO || item == SECTION_SYSTEM ||
|
2026-05-11 19:19:44 +02:00
|
|
|
|
item == SECTION_CONTACTS || item == SECTION_MESSAGES
|
2026-05-11 16:52:24 +02:00
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
|
|
|
|
|
|| item == SECTION_GPS
|
|
|
|
|
|
#endif
|
|
|
|
|
|
;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char* sectionName(int item) const {
|
|
|
|
|
|
if (item == SECTION_DISPLAY) return "Display";
|
|
|
|
|
|
if (item == SECTION_SOUND) return "Sound";
|
|
|
|
|
|
if (item == SECTION_RADIO) return "Radio";
|
|
|
|
|
|
if (item == SECTION_SYSTEM) return "System";
|
2026-05-11 19:19:44 +02:00
|
|
|
|
if (item == SECTION_CONTACTS) return "Contacts";
|
2026-05-11 16:52:24 +02:00
|
|
|
|
if (item == SECTION_MESSAGES) return "Messages";
|
|
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
|
|
|
|
|
if (item == SECTION_GPS) return "GPS";
|
|
|
|
|
|
#endif
|
|
|
|
|
|
return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool isMsgSlot(int item) const {
|
|
|
|
|
|
return item >= MSG_SLOT_0 && item <= MSG_SLOT_9;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int msgSlotIndex(int item) const {
|
|
|
|
|
|
return item - MSG_SLOT_0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int nextSelectable(int from) const {
|
|
|
|
|
|
for (int i = from + 1; i < (int)Count; i++)
|
|
|
|
|
|
if (!isSection(i)) return i;
|
|
|
|
|
|
return from;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int prevSelectable(int from) const {
|
|
|
|
|
|
for (int i = from - 1; i >= 0; i--)
|
|
|
|
|
|
if (!isSection(i)) return i;
|
|
|
|
|
|
return from;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
|
void renderItem(DisplayDriver& display, int item, int y) {
|
|
|
|
|
|
NodePrefs* p = _task->getNodePrefs();
|
2026-05-11 16:52:24 +02:00
|
|
|
|
|
|
|
|
|
|
if (isSection(item)) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.fillRect(0, y, display.width(), 1);
|
|
|
|
|
|
display.setCursor(2, y + 2);
|
|
|
|
|
|
display.print(sectionName(item));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
|
bool sel = (item == _selected);
|
|
|
|
|
|
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
if (sel) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.fillRect(0, y - 1, display.width(), ITEM_H);
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
|
display.setCursor(0, y);
|
|
|
|
|
|
display.print(sel ? ">" : " ");
|
|
|
|
|
|
display.setCursor(8, y);
|
|
|
|
|
|
|
|
|
|
|
|
if (item == BRIGHTNESS) {
|
|
|
|
|
|
display.print("Bright");
|
|
|
|
|
|
renderBar(display, VAL_X, y, (p ? p->display_brightness : 2) + 1, 5);
|
|
|
|
|
|
} else if (item == BUZZER) {
|
|
|
|
|
|
display.print("Buzzer");
|
|
|
|
|
|
display.setCursor(VAL_X, y);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
#ifdef PIN_BUZZER
|
|
|
|
|
|
display.print(_task->isBuzzerQuiet() ? "OFF" : "ON");
|
2026-05-10 15:18:35 +02:00
|
|
|
|
#else
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.print("N/A");
|
2026-05-10 15:18:35 +02:00
|
|
|
|
#endif
|
|
|
|
|
|
} else if (item == TX_POWER) {
|
|
|
|
|
|
display.print("TX Pwr");
|
|
|
|
|
|
char buf[8];
|
|
|
|
|
|
sprintf(buf, "%ddBm", p ? p->tx_power_dbm : 0);
|
|
|
|
|
|
display.setCursor(VAL_X, y);
|
|
|
|
|
|
display.print(buf);
|
|
|
|
|
|
} else if (item == AUTO_OFF) {
|
|
|
|
|
|
display.print("AutoOff");
|
|
|
|
|
|
display.setCursor(VAL_X, y);
|
|
|
|
|
|
display.print(AUTO_OFF_LABELS[autoOffIndex()]);
|
|
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
|
|
|
|
|
} else if (item == GPS_INTERVAL) {
|
|
|
|
|
|
display.print("GPS upd");
|
|
|
|
|
|
display.setCursor(VAL_X, y);
|
|
|
|
|
|
display.print(GPS_INTERVAL_LABELS[gpsIntervalIndex()]);
|
|
|
|
|
|
#endif
|
|
|
|
|
|
} else if (item == TIMEZONE) {
|
|
|
|
|
|
display.print("TimeZone");
|
|
|
|
|
|
char buf[8];
|
|
|
|
|
|
int8_t tz = p ? p->tz_offset_hours : 0;
|
|
|
|
|
|
if (tz >= 0) sprintf(buf, "UTC+%d", (int)tz);
|
|
|
|
|
|
else sprintf(buf, "UTC%d", (int)tz);
|
|
|
|
|
|
display.setCursor(VAL_X, y);
|
|
|
|
|
|
display.print(buf);
|
|
|
|
|
|
} else if (item == LOW_BAT) {
|
|
|
|
|
|
display.print("LowBat");
|
|
|
|
|
|
display.setCursor(VAL_X, y);
|
|
|
|
|
|
display.print(LOW_BAT_LABELS[lowBatIndex()]);
|
|
|
|
|
|
} else if (item == BATT_DISPLAY) {
|
|
|
|
|
|
display.print("BattDisp");
|
|
|
|
|
|
display.setCursor(VAL_X, y);
|
|
|
|
|
|
uint8_t mode = p ? p->batt_display_mode : 0;
|
|
|
|
|
|
display.print(BATT_DISPLAY_LABELS[mode < BATT_DISPLAY_COUNT ? mode : 0]);
|
2026-05-11 19:19:44 +02:00
|
|
|
|
} else if (item == DM_FILTER) {
|
|
|
|
|
|
display.print("DM");
|
|
|
|
|
|
display.setCursor(VAL_X, y);
|
|
|
|
|
|
display.print((p && p->dm_show_all) ? "all" : "fav");
|
|
|
|
|
|
} else if (item == ROOM_FILTER) {
|
|
|
|
|
|
display.print("Rooms");
|
|
|
|
|
|
display.setCursor(VAL_X, y);
|
|
|
|
|
|
display.print((p && p->room_fav_only) ? "fav" : "all");
|
2026-05-11 16:52:24 +02:00
|
|
|
|
} else if (isMsgSlot(item)) {
|
|
|
|
|
|
int slot = msgSlotIndex(item);
|
|
|
|
|
|
char label[5];
|
|
|
|
|
|
snprintf(label, sizeof(label), "M%d:", slot + 1);
|
|
|
|
|
|
display.print(label);
|
|
|
|
|
|
const char* tmpl = (p && p->custom_msgs[slot][0]) ? p->custom_msgs[slot] : "(empty)";
|
|
|
|
|
|
display.drawTextEllipsized(VAL_X - 20, y, display.width() - VAL_X + 18, tmpl);
|
2026-05-10 15:18:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 16:52:24 +02:00
|
|
|
|
// Keyboard state for editing message slots
|
|
|
|
|
|
int _edit_slot; // -1 = not editing, 0..9 = slot being edited
|
|
|
|
|
|
char _edit_buf[KB_MAX_LEN + 1];
|
|
|
|
|
|
int _edit_len;
|
|
|
|
|
|
int _edit_kb_row, _edit_kb_col;
|
|
|
|
|
|
bool _edit_caps;
|
|
|
|
|
|
bool _edit_ph_mode;
|
|
|
|
|
|
int _edit_ph_sel;
|
|
|
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
|
public:
|
2026-05-11 16:52:24 +02:00
|
|
|
|
SettingsScreen(UITask* task)
|
|
|
|
|
|
: _task(task), _selected(BRIGHTNESS), _scroll(0), _dirty(false),
|
|
|
|
|
|
_edit_slot(-1), _edit_len(0), _edit_kb_row(0), _edit_kb_col(0),
|
|
|
|
|
|
_edit_caps(false), _edit_ph_mode(false), _edit_ph_sel(0) {
|
|
|
|
|
|
_edit_buf[0] = '\0';
|
|
|
|
|
|
}
|
2026-05-10 22:12:29 +02:00
|
|
|
|
|
|
|
|
|
|
void markClean() { _dirty = false; }
|
2026-05-10 15:18:35 +02:00
|
|
|
|
|
|
|
|
|
|
int render(DisplayDriver& display) override {
|
|
|
|
|
|
display.setTextSize(1);
|
2026-05-11 16:52:24 +02:00
|
|
|
|
|
|
|
|
|
|
if (_edit_slot >= 0) {
|
|
|
|
|
|
// Keyboard mode for editing a message slot
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
// preview line with cursor
|
|
|
|
|
|
const char* disp_start = _edit_buf;
|
|
|
|
|
|
int disp_len = _edit_len;
|
|
|
|
|
|
if (disp_len > 20) { disp_start = _edit_buf + (disp_len - 20); disp_len = 20; }
|
|
|
|
|
|
char preview[26];
|
|
|
|
|
|
snprintf(preview, sizeof(preview), "M%d:%.*s_", _edit_slot + 1, disp_len, disp_start);
|
|
|
|
|
|
display.setCursor(0, KB_TEXT_Y);
|
|
|
|
|
|
display.print(preview);
|
|
|
|
|
|
display.fillRect(0, KB_SEP_Y, display.width(), 1);
|
|
|
|
|
|
|
|
|
|
|
|
// char rows
|
|
|
|
|
|
for (int row = 0; row < KB_ROWS_CHAR; row++) {
|
|
|
|
|
|
int y = KB_CHARS_Y + row * KB_CELL_H;
|
|
|
|
|
|
for (int col = 0; col < KB_COLS_CHAR; col++) {
|
|
|
|
|
|
bool sel = (_edit_kb_row == row && _edit_kb_col == col);
|
|
|
|
|
|
char ch = KB_CHARS[row][col];
|
|
|
|
|
|
if (_edit_caps && ch >= 'a' && ch <= 'z') ch = ch - 'a' + 'A';
|
|
|
|
|
|
char ch_buf[2] = { ch, '\0' };
|
|
|
|
|
|
if (ch_buf[0] == ' ') ch_buf[0] = '_';
|
|
|
|
|
|
int cx = col * KB_CELL_W;
|
|
|
|
|
|
if (sel) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.fillRect(cx, y - 1, KB_CELL_W - 1, KB_CELL_H);
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
}
|
|
|
|
|
|
display.setCursor(cx + 3, y);
|
|
|
|
|
|
display.print(ch_buf);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// special row: [^] [Sp] [Del] [{}] [OK] — 5 buttons at 25px each
|
|
|
|
|
|
const char* spec[] = { "[^]", "[Sp]", "[Del]", "[{}]", "[OK]" };
|
|
|
|
|
|
for (int i = 0; i < KB_SPECIAL; i++) {
|
|
|
|
|
|
bool sel = (_edit_kb_row == KB_ROWS_CHAR && _edit_kb_col == i);
|
|
|
|
|
|
bool active = (i == 0 && _edit_caps);
|
|
|
|
|
|
int sx = i * 25;
|
|
|
|
|
|
if (sel || active) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.fillRect(sx, KB_SPECIAL_Y - 1, 24, KB_CELL_H);
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
}
|
|
|
|
|
|
display.setCursor(sx + 1, KB_SPECIAL_Y);
|
|
|
|
|
|
display.print(spec[i]);
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// placeholder picker overlay
|
|
|
|
|
|
if (_edit_ph_mode) {
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
2026-05-11 17:19:03 +02:00
|
|
|
|
display.fillRect(20, 20, 88, 12 + KB_PH_COUNT * 10);
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.drawRect(20, 20, 88, 12 + KB_PH_COUNT * 10);
|
2026-05-11 16:52:24 +02:00
|
|
|
|
display.setCursor(24, 21);
|
|
|
|
|
|
display.print("Placeholder:");
|
2026-05-11 17:19:03 +02:00
|
|
|
|
display.fillRect(20, 30, 88, 1);
|
2026-05-11 16:52:24 +02:00
|
|
|
|
for (int i = 0; i < KB_PH_COUNT; i++) {
|
2026-05-11 17:19:03 +02:00
|
|
|
|
int py = 33 + i * 10;
|
2026-05-11 16:52:24 +02:00
|
|
|
|
if (i == _edit_ph_sel) {
|
2026-05-11 17:19:03 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.fillRect(21, py - 1, 86, 10);
|
2026-05-11 16:52:24 +02:00
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
}
|
|
|
|
|
|
display.setCursor(24, py);
|
|
|
|
|
|
display.print(KB_PH_LIST[i]);
|
|
|
|
|
|
}
|
2026-05-11 17:19:03 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-11 16:52:24 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return 50;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-10 15:18:35 +02:00
|
|
|
|
display.drawTextCentered(display.width() / 2, 0, "SETTINGS");
|
|
|
|
|
|
display.fillRect(0, 10, display.width(), 1);
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < VISIBLE_ITEMS && (_scroll + i) < SettingItem::Count; i++) {
|
|
|
|
|
|
renderItem(display, _scroll + i, START_Y + i * ITEM_H);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// scroll indicators
|
|
|
|
|
|
if (_scroll > 0) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.setCursor(display.width() - 6, START_Y);
|
|
|
|
|
|
display.print("^");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_scroll + VISIBLE_ITEMS < SettingItem::Count) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.setCursor(display.width() - 6, START_Y + (VISIBLE_ITEMS - 1) * ITEM_H);
|
|
|
|
|
|
display.print("v");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return 300;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool handleInput(char c) override {
|
|
|
|
|
|
NodePrefs* p = _task->getNodePrefs();
|
|
|
|
|
|
|
2026-05-11 16:52:24 +02:00
|
|
|
|
// Keyboard editing mode for message slots
|
|
|
|
|
|
if (_edit_slot >= 0) {
|
|
|
|
|
|
if (c == KEY_UP) {
|
|
|
|
|
|
if (_edit_kb_row > 0) {
|
|
|
|
|
|
_edit_kb_row--;
|
2026-05-11 17:19:03 +02:00
|
|
|
|
if (_edit_kb_row == KB_ROWS_CHAR - 1) // leaving special row upward
|
|
|
|
|
|
_edit_kb_col = _edit_kb_col * KB_COLS_CHAR / KB_SPECIAL;
|
2026-05-11 16:52:24 +02:00
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_DOWN) {
|
|
|
|
|
|
if (_edit_kb_row < KB_ROWS_CHAR) {
|
|
|
|
|
|
_edit_kb_row++;
|
2026-05-11 17:19:03 +02:00
|
|
|
|
if (_edit_kb_row == KB_ROWS_CHAR) // entering special row
|
2026-05-11 16:52:24 +02:00
|
|
|
|
_edit_kb_col = _edit_kb_col * KB_SPECIAL / KB_COLS_CHAR;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_LEFT) {
|
|
|
|
|
|
if (_edit_kb_col > 0) _edit_kb_col--;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_RIGHT) {
|
|
|
|
|
|
int max_col = (_edit_kb_row == KB_ROWS_CHAR) ? KB_SPECIAL - 1 : KB_COLS_CHAR - 1;
|
|
|
|
|
|
if (_edit_kb_col < max_col) _edit_kb_col++;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_edit_ph_mode) {
|
|
|
|
|
|
if (c == KEY_UP && _edit_ph_sel > 0) { _edit_ph_sel--; return true; }
|
|
|
|
|
|
if (c == KEY_DOWN && _edit_ph_sel < KB_PH_COUNT - 1) { _edit_ph_sel++; return true; }
|
|
|
|
|
|
if (c == KEY_ENTER) {
|
|
|
|
|
|
const char* ph = KB_PH_LIST[_edit_ph_sel];
|
|
|
|
|
|
int ph_len = strlen(ph);
|
|
|
|
|
|
if (_edit_len + ph_len <= KB_MAX_LEN) {
|
|
|
|
|
|
memcpy(_edit_buf + _edit_len, ph, ph_len);
|
|
|
|
|
|
_edit_len += ph_len;
|
|
|
|
|
|
_edit_buf[_edit_len] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
_edit_ph_mode = false;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_CANCEL) { _edit_ph_mode = false; return true; }
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_ENTER) {
|
|
|
|
|
|
if (_edit_kb_row < KB_ROWS_CHAR) {
|
|
|
|
|
|
if (_edit_len < KB_MAX_LEN) {
|
|
|
|
|
|
char ch = KB_CHARS[_edit_kb_row][_edit_kb_col];
|
|
|
|
|
|
if (_edit_caps && ch >= 'a' && ch <= 'z') ch = ch - 'a' + 'A';
|
|
|
|
|
|
_edit_buf[_edit_len++] = ch;
|
|
|
|
|
|
_edit_buf[_edit_len] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (_edit_kb_col == 0) {
|
|
|
|
|
|
// Caps Lock toggle
|
|
|
|
|
|
_edit_caps = !_edit_caps;
|
|
|
|
|
|
} else if (_edit_kb_col == 1) {
|
|
|
|
|
|
// Space
|
|
|
|
|
|
if (_edit_len < KB_MAX_LEN) {
|
|
|
|
|
|
_edit_buf[_edit_len++] = ' ';
|
|
|
|
|
|
_edit_buf[_edit_len] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (_edit_kb_col == 2) {
|
|
|
|
|
|
// Del
|
|
|
|
|
|
if (_edit_len > 0) _edit_buf[--_edit_len] = '\0';
|
|
|
|
|
|
} else if (_edit_kb_col == 3) {
|
|
|
|
|
|
// Placeholder picker
|
|
|
|
|
|
_edit_ph_mode = true;
|
|
|
|
|
|
_edit_ph_sel = 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// OK — save
|
|
|
|
|
|
if (p) {
|
|
|
|
|
|
strncpy(p->custom_msgs[_edit_slot], _edit_buf, sizeof(p->custom_msgs[0]) - 1);
|
|
|
|
|
|
p->custom_msgs[_edit_slot][sizeof(p->custom_msgs[0]) - 1] = '\0';
|
|
|
|
|
|
the_mesh.savePrefs();
|
|
|
|
|
|
}
|
|
|
|
|
|
_edit_slot = -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_CANCEL) {
|
|
|
|
|
|
_edit_slot = -1;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
|
if (c == KEY_UP) {
|
2026-05-11 16:52:24 +02:00
|
|
|
|
int prev = prevSelectable(_selected);
|
|
|
|
|
|
if (prev != _selected) {
|
|
|
|
|
|
_selected = prev;
|
2026-05-10 15:18:35 +02:00
|
|
|
|
if (_selected < _scroll) _scroll = _selected;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_DOWN) {
|
2026-05-11 16:52:24 +02:00
|
|
|
|
int next = nextSelectable(_selected);
|
|
|
|
|
|
if (next != _selected) {
|
|
|
|
|
|
_selected = next;
|
2026-05-10 15:18:35 +02:00
|
|
|
|
if (_selected >= _scroll + VISIBLE_ITEMS) _scroll = _selected - VISIBLE_ITEMS + 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_CANCEL) {
|
2026-05-10 22:12:29 +02:00
|
|
|
|
if (_dirty) the_mesh.savePrefs();
|
2026-05-10 15:18:35 +02:00
|
|
|
|
_task->gotoHomeScreen();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool right = (c == KEY_RIGHT || c == KEY_NEXT);
|
|
|
|
|
|
bool left = (c == KEY_LEFT || c == KEY_PREV);
|
|
|
|
|
|
bool enter = (c == KEY_ENTER);
|
|
|
|
|
|
|
|
|
|
|
|
if (_selected == BRIGHTNESS) {
|
|
|
|
|
|
uint8_t lvl = _task->getBrightnessLevel();
|
2026-05-10 22:12:29 +02:00
|
|
|
|
if (right && lvl < 4) { _task->setBrightnessLevel(lvl + 1); _dirty = true; return true; }
|
|
|
|
|
|
if (left && lvl > 0) { _task->setBrightnessLevel(lvl - 1); _dirty = true; return true; }
|
2026-05-10 15:18:35 +02:00
|
|
|
|
return right || left;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_selected == BUZZER && (left || right || enter)) {
|
2026-05-10 22:12:29 +02:00
|
|
|
|
_task->toggleBuzzer(); // saves immediately internally
|
2026-05-10 15:18:35 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_selected == TX_POWER && p) {
|
2026-05-10 22:12:29 +02:00
|
|
|
|
if (right && p->tx_power_dbm < 22) { p->tx_power_dbm++; _task->applyTxPower(); _dirty = true; return true; }
|
|
|
|
|
|
if (left && p->tx_power_dbm > 2) { p->tx_power_dbm--; _task->applyTxPower(); _dirty = true; return true; }
|
2026-05-10 15:18:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
if (_selected == AUTO_OFF && p) {
|
|
|
|
|
|
int idx = autoOffIndex();
|
|
|
|
|
|
if (right) idx = (idx + 1) % AUTO_OFF_COUNT;
|
|
|
|
|
|
if (left) idx = (idx + AUTO_OFF_COUNT - 1) % AUTO_OFF_COUNT;
|
2026-05-10 22:12:29 +02:00
|
|
|
|
if (left || right) { p->auto_off_secs = AUTO_OFF_OPTS[idx]; _dirty = true; return true; }
|
2026-05-10 15:18:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
|
|
|
|
|
if (_selected == GPS_INTERVAL && p) {
|
|
|
|
|
|
int idx = gpsIntervalIndex();
|
|
|
|
|
|
if (right) idx = (idx + 1) % GPS_INTERVAL_COUNT;
|
|
|
|
|
|
if (left) idx = (idx + GPS_INTERVAL_COUNT - 1) % GPS_INTERVAL_COUNT;
|
|
|
|
|
|
if (left || right) {
|
|
|
|
|
|
p->gps_interval = GPS_INTERVAL_OPTS[idx];
|
|
|
|
|
|
_task->applyGPSInterval();
|
2026-05-10 22:12:29 +02:00
|
|
|
|
_dirty = true;
|
2026-05-10 15:18:35 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
if (_selected == TIMEZONE && p) {
|
2026-05-10 22:12:29 +02:00
|
|
|
|
if (right && p->tz_offset_hours < 14) { p->tz_offset_hours++; _dirty = true; return true; }
|
|
|
|
|
|
if (left && p->tz_offset_hours > -12) { p->tz_offset_hours--; _dirty = true; return true; }
|
2026-05-10 15:18:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
if (_selected == LOW_BAT && p) {
|
|
|
|
|
|
int idx = lowBatIndex();
|
|
|
|
|
|
if (right) idx = (idx + 1) % LOW_BAT_COUNT;
|
|
|
|
|
|
if (left) idx = (idx + LOW_BAT_COUNT - 1) % LOW_BAT_COUNT;
|
2026-05-10 22:12:29 +02:00
|
|
|
|
if (left || right) { p->low_batt_mv = LOW_BAT_OPTS[idx]; _dirty = true; return true; }
|
2026-05-10 15:18:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
if (_selected == BATT_DISPLAY && p) {
|
|
|
|
|
|
int idx = p->batt_display_mode < BATT_DISPLAY_COUNT ? p->batt_display_mode : 0;
|
|
|
|
|
|
if (right) idx = (idx + 1) % BATT_DISPLAY_COUNT;
|
|
|
|
|
|
if (left) idx = (idx + BATT_DISPLAY_COUNT - 1) % BATT_DISPLAY_COUNT;
|
2026-05-10 22:12:29 +02:00
|
|
|
|
if (left || right) { p->batt_display_mode = idx; _dirty = true; return true; }
|
2026-05-10 15:18:35 +02:00
|
|
|
|
}
|
2026-05-11 19:19:44 +02:00
|
|
|
|
if (_selected == DM_FILTER && p && (left || right || enter)) {
|
|
|
|
|
|
p->dm_show_all = p->dm_show_all ? 0 : 1;
|
|
|
|
|
|
_dirty = true;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_selected == ROOM_FILTER && p && (left || right || enter)) {
|
|
|
|
|
|
p->room_fav_only = p->room_fav_only ? 0 : 1;
|
|
|
|
|
|
_dirty = true;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-05-11 16:52:24 +02:00
|
|
|
|
if (isMsgSlot(_selected) && enter) {
|
|
|
|
|
|
int slot = msgSlotIndex(_selected);
|
|
|
|
|
|
_edit_slot = slot;
|
|
|
|
|
|
_edit_len = 0;
|
|
|
|
|
|
_edit_buf[0] = '\0';
|
|
|
|
|
|
// Pre-fill with existing text
|
|
|
|
|
|
if (p && p->custom_msgs[slot][0]) {
|
|
|
|
|
|
int existing = strlen(p->custom_msgs[slot]);
|
|
|
|
|
|
if (existing > KB_MAX_LEN) existing = KB_MAX_LEN;
|
|
|
|
|
|
memcpy(_edit_buf, p->custom_msgs[slot], existing);
|
|
|
|
|
|
_edit_buf[existing] = '\0';
|
|
|
|
|
|
_edit_len = existing;
|
|
|
|
|
|
}
|
|
|
|
|
|
_edit_kb_row = 0;
|
|
|
|
|
|
_edit_kb_col = 0;
|
|
|
|
|
|
_edit_caps = false;
|
|
|
|
|
|
_edit_ph_mode = false;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-05-10 15:18:35 +02:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const uint16_t SettingsScreen::AUTO_OFF_OPTS[5] = { 5, 15, 30, 60, 0 };
|
|
|
|
|
|
const char* SettingsScreen::AUTO_OFF_LABELS[5] = { "5s", "15s", "30s", "60s", "never" };
|
|
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
|
|
|
|
|
const uint32_t SettingsScreen::GPS_INTERVAL_OPTS[6] = { 0, 30, 60, 300, 900, 1800 };
|
|
|
|
|
|
const char* SettingsScreen::GPS_INTERVAL_LABELS[6] = { "off", "30s", "1min", "5min", "15min", "30min" };
|
|
|
|
|
|
#endif
|
|
|
|
|
|
const uint16_t SettingsScreen::LOW_BAT_OPTS[7] = { 0, 3000, 3100, 3200, 3300, 3400, 3500 };
|
|
|
|
|
|
const char* SettingsScreen::LOW_BAT_LABELS[7] = { "off", "3.0V", "3.1V", "3.2V", "3.3V", "3.4V", "3.5V" };
|
|
|
|
|
|
const char* SettingsScreen::BATT_DISPLAY_LABELS[3] = { "icon", "%", "V" };
|
|
|
|
|
|
|
2026-05-10 18:45:35 +02:00
|
|
|
|
class QuickMsgScreen : public UIScreen {
|
|
|
|
|
|
UITask* _task;
|
|
|
|
|
|
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
enum Phase { MODE_SELECT, CONTACT_PICK, MSG_PICK, CHANNEL_PICK, CHANNEL_HIST, KEYBOARD };
|
2026-05-10 18:45:35 +02:00
|
|
|
|
Phase _phase;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
|
|
|
|
|
|
// MODE_SELECT
|
2026-05-11 19:16:26 +02:00
|
|
|
|
int _mode_sel; // 0=Direct, 1=Channel, 2=Room Servers
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
|
|
|
|
|
|
// CONTACT_PICK
|
2026-05-10 18:45:35 +02:00
|
|
|
|
int _contact_sel, _contact_scroll;
|
|
|
|
|
|
int _num_contacts;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
uint8_t _sorted[MAX_CONTACTS];
|
2026-05-10 18:45:35 +02:00
|
|
|
|
ContactInfo _sel_contact;
|
2026-05-11 19:16:26 +02:00
|
|
|
|
bool _room_mode; // true = picking a room server, false = picking a DM contact
|
2026-05-10 18:45:35 +02:00
|
|
|
|
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
// CHANNEL_PICK
|
|
|
|
|
|
int _channel_sel, _channel_scroll;
|
|
|
|
|
|
int _num_channels;
|
|
|
|
|
|
uint8_t _channel_indices[MAX_GROUP_CHANNELS];
|
2026-05-11 16:52:24 +02:00
|
|
|
|
uint8_t _ch_unread[MAX_GROUP_CHANNELS];
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
int _sel_channel_idx;
|
|
|
|
|
|
bool _sending_to_channel;
|
|
|
|
|
|
|
|
|
|
|
|
// MSG_PICK (shared)
|
|
|
|
|
|
int _msg_sel, _msg_scroll;
|
2026-05-11 16:52:24 +02:00
|
|
|
|
int _active_msgs[QUICK_MSGS_MAX];
|
|
|
|
|
|
int _active_msg_count;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
|
|
|
|
|
|
// CHANNEL_HIST
|
|
|
|
|
|
int _hist_sel, _hist_scroll;
|
|
|
|
|
|
bool _hist_fullscreen;
|
|
|
|
|
|
int _hist_fs_scroll;
|
2026-05-11 21:10:36 +02:00
|
|
|
|
int _viewing_unread_start; // index of first "unread" message when entering CHANNEL_HIST
|
|
|
|
|
|
int _viewing_max_seen; // highest _hist_sel reached in current session
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
static const int CH_HIST_MAX = 32;
|
|
|
|
|
|
|
|
|
|
|
|
static const int FS_CHARS = 21;
|
|
|
|
|
|
static const int FS_LINE_H = 9;
|
|
|
|
|
|
static const int FS_START_Y = 12;
|
|
|
|
|
|
static const int FS_VISIBLE = (64 - FS_START_Y) / FS_LINE_H;
|
|
|
|
|
|
|
|
|
|
|
|
int wrapLines(const char* text, char out[][FS_CHARS + 1], int max_lines) const {
|
|
|
|
|
|
int count = 0;
|
|
|
|
|
|
const char* p = text;
|
|
|
|
|
|
while (*p && count < max_lines) {
|
|
|
|
|
|
int len = strlen(p);
|
|
|
|
|
|
if (len <= FS_CHARS) {
|
|
|
|
|
|
strncpy(out[count++], p, FS_CHARS);
|
|
|
|
|
|
out[count-1][len] = '\0';
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
int brk = FS_CHARS;
|
|
|
|
|
|
for (int i = FS_CHARS - 1; i > 0; i--) {
|
|
|
|
|
|
if (p[i] == ' ') { brk = i; break; }
|
|
|
|
|
|
}
|
|
|
|
|
|
strncpy(out[count], p, brk);
|
|
|
|
|
|
out[count++][brk] = '\0';
|
|
|
|
|
|
p += brk + (p[brk] == ' ' ? 1 : 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
return count;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// KEYBOARD
|
|
|
|
|
|
int _kb_row, _kb_col;
|
|
|
|
|
|
char _kb_text[KB_MAX_LEN + 1];
|
|
|
|
|
|
int _kb_len;
|
2026-05-11 16:52:24 +02:00
|
|
|
|
bool _kb_caps;
|
|
|
|
|
|
bool _kb_ph_mode;
|
|
|
|
|
|
int _kb_ph_sel;
|
2026-05-11 18:56:26 +02:00
|
|
|
|
|
|
|
|
|
|
// Context menu (opened by long-press ENTER in CHANNEL_PICK)
|
|
|
|
|
|
bool _ctx_open;
|
2026-05-11 19:28:37 +02:00
|
|
|
|
bool _ctx_dirty;
|
2026-05-11 18:56:26 +02:00
|
|
|
|
int _ctx_sel;
|
|
|
|
|
|
|
2026-05-11 16:52:24 +02:00
|
|
|
|
struct ChHistEntry { uint8_t ch_idx; char text[140]; };
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
ChHistEntry _hist[CH_HIST_MAX];
|
|
|
|
|
|
int _hist_head, _hist_count;
|
|
|
|
|
|
|
|
|
|
|
|
static const int VISIBLE = 4;
|
|
|
|
|
|
static const int ITEM_H = 12;
|
|
|
|
|
|
static const int START_Y = 12;
|
|
|
|
|
|
static const int HIST_VISIBLE = 2; // 2-line boxed history entries
|
|
|
|
|
|
static const int HIST_ITEM_H = 21; // box(19) + gap(2)
|
|
|
|
|
|
static const int HIST_BOX_H = 19;
|
|
|
|
|
|
static const int HIST_START_Y = 11;
|
2026-05-10 18:45:35 +02:00
|
|
|
|
|
2026-05-11 16:52:24 +02:00
|
|
|
|
void expandMsg(const char* tmpl, char* out, int out_len) const {
|
|
|
|
|
|
int oi = 0;
|
|
|
|
|
|
const char* p = tmpl;
|
|
|
|
|
|
while (*p && oi < out_len - 1) {
|
|
|
|
|
|
if (strncmp(p, "{loc}", 5) == 0) {
|
2026-05-10 18:45:35 +02:00
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
2026-05-11 16:52:24 +02:00
|
|
|
|
LocationProvider* loc = sensors.getLocationProvider();
|
|
|
|
|
|
if (loc && loc->isValid()) {
|
|
|
|
|
|
char lb[32];
|
|
|
|
|
|
snprintf(lb, sizeof(lb), "%.5f,%.5f",
|
|
|
|
|
|
loc->getLatitude() / 1000000.0, loc->getLongitude() / 1000000.0);
|
|
|
|
|
|
int ll = strlen(lb);
|
|
|
|
|
|
if (oi + ll < out_len - 1) { memcpy(out + oi, lb, ll); oi += ll; }
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const char* s = "no GPS"; int sl = 6;
|
|
|
|
|
|
if (oi + sl < out_len - 1) { memcpy(out + oi, s, sl); oi += sl; }
|
|
|
|
|
|
}
|
|
|
|
|
|
#else
|
|
|
|
|
|
const char* s = "no GPS"; int sl = 6;
|
|
|
|
|
|
if (oi + sl < out_len - 1) { memcpy(out + oi, s, sl); oi += sl; }
|
2026-05-10 18:45:35 +02:00
|
|
|
|
#endif
|
2026-05-11 16:52:24 +02:00
|
|
|
|
p += 5;
|
|
|
|
|
|
} else if (strncmp(p, "{time}", 6) == 0) {
|
|
|
|
|
|
uint32_t ts = rtc_clock.getCurrentTime();
|
|
|
|
|
|
if (ts > 1000000000UL) {
|
|
|
|
|
|
NodePrefs* np = _task->getNodePrefs();
|
|
|
|
|
|
ts += (int32_t)(np ? np->tz_offset_hours : 0) * 3600;
|
|
|
|
|
|
time_t t = (time_t)ts;
|
|
|
|
|
|
struct tm* ti = gmtime(&t);
|
|
|
|
|
|
char tb[8];
|
|
|
|
|
|
snprintf(tb, sizeof(tb), "%02d:%02d", ti->tm_hour, ti->tm_min);
|
|
|
|
|
|
int tl = strlen(tb);
|
|
|
|
|
|
if (oi + tl < out_len - 1) { memcpy(out + oi, tb, tl); oi += tl; }
|
|
|
|
|
|
}
|
|
|
|
|
|
p += 6;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
out[oi++] = *p++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
out[oi] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void setupMsgPick() {
|
|
|
|
|
|
_msg_sel = _msg_scroll = 0;
|
|
|
|
|
|
_active_msg_count = 0;
|
|
|
|
|
|
NodePrefs* p = _task->getNodePrefs();
|
|
|
|
|
|
if (p) {
|
|
|
|
|
|
for (int i = 0; i < QUICK_MSGS_MAX; i++) {
|
|
|
|
|
|
if (p->custom_msgs[i][0] != '\0')
|
|
|
|
|
|
_active_msgs[_active_msg_count++] = i;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-10 18:45:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void renderScrollHints(DisplayDriver& display, int scroll, int count) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
if (scroll > 0) {
|
|
|
|
|
|
display.setCursor(display.width() - 6, START_Y);
|
|
|
|
|
|
display.print("^");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (scroll + VISIBLE < count) {
|
|
|
|
|
|
display.setCursor(display.width() - 6, START_Y + (VISIBLE-1)*ITEM_H);
|
|
|
|
|
|
display.print("v");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
// count history entries for a specific channel
|
|
|
|
|
|
int histCountForChannel(int ch_idx) const {
|
|
|
|
|
|
int n = 0;
|
|
|
|
|
|
for (int i = 0; i < _hist_count; i++) {
|
|
|
|
|
|
if (_hist[(_hist_head + i) % CH_HIST_MAX].ch_idx == (uint8_t)ch_idx) n++;
|
|
|
|
|
|
}
|
|
|
|
|
|
return n;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// get ring-buffer position of j-th history entry for channel (newest first)
|
|
|
|
|
|
int histEntryForChannel(int ch_idx, int j) const {
|
|
|
|
|
|
int n = 0;
|
|
|
|
|
|
for (int i = _hist_count - 1; i >= 0; i--) {
|
|
|
|
|
|
int pos = (_hist_head + i) % CH_HIST_MAX;
|
|
|
|
|
|
if (_hist[pos].ch_idx == (uint8_t)ch_idx) {
|
|
|
|
|
|
if (n == j) return pos;
|
|
|
|
|
|
n++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void afterSend(bool ok, const char* msg) {
|
|
|
|
|
|
if (ok && _sending_to_channel) {
|
2026-05-11 17:19:03 +02:00
|
|
|
|
_hist_sel = 0;
|
|
|
|
|
|
_hist_scroll = 0;
|
|
|
|
|
|
_phase = CHANNEL_HIST; // set before addChannelMsg so viewing=true, no unread bump
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
char entry[sizeof(ChHistEntry::text)];
|
|
|
|
|
|
snprintf(entry, sizeof(entry), "Me: %s", msg);
|
|
|
|
|
|
addChannelMsg(_sel_channel_idx, entry);
|
|
|
|
|
|
_task->showAlert("Sent!", 600);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
_task->showAlert(ok ? "Sent!" : "Send failed", 1500);
|
|
|
|
|
|
_task->gotoHomeScreen();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool sendText(const char* msg) {
|
|
|
|
|
|
if (_sending_to_channel) {
|
|
|
|
|
|
ChannelDetails ch;
|
|
|
|
|
|
if (!the_mesh.getChannel(_sel_channel_idx, ch)) return false;
|
|
|
|
|
|
return the_mesh.sendGroupMessage(rtc_clock.getCurrentTime(), ch.channel,
|
|
|
|
|
|
the_mesh.getNodeName(), msg, strlen(msg));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
uint32_t expected_ack = 0, est_timeout = 0;
|
|
|
|
|
|
return the_mesh.sendMessage(_sel_contact, rtc_clock.getCurrentTime(), 0,
|
|
|
|
|
|
msg, expected_ack, est_timeout) > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 19:16:26 +02:00
|
|
|
|
void buildContactList() {
|
2026-05-11 19:19:44 +02:00
|
|
|
|
NodePrefs* p = _task->getNodePrefs();
|
2026-05-11 19:16:26 +02:00
|
|
|
|
ContactInfo c;
|
|
|
|
|
|
int total = the_mesh.getNumContacts();
|
|
|
|
|
|
_num_contacts = 0;
|
|
|
|
|
|
if (_room_mode) {
|
2026-05-11 19:19:44 +02:00
|
|
|
|
bool fav_only = (p && p->room_fav_only);
|
2026-05-11 19:16:26 +02:00
|
|
|
|
for (int i = 0; i < total; i++) {
|
|
|
|
|
|
if (!the_mesh.getContactByIdx(i, c) || c.type != ADV_TYPE_ROOM) continue;
|
2026-05-11 19:19:44 +02:00
|
|
|
|
if (fav_only && !(c.flags & 0x01)) continue;
|
2026-05-11 19:16:26 +02:00
|
|
|
|
_sorted[_num_contacts++] = i;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2026-05-11 19:19:44 +02:00
|
|
|
|
bool show_all = (p && p->dm_show_all);
|
2026-05-11 19:16:26 +02:00
|
|
|
|
for (int i = 0; i < total; i++) {
|
|
|
|
|
|
if (!the_mesh.getContactByIdx(i, c) || c.type != ADV_TYPE_CHAT) continue;
|
2026-05-11 19:19:44 +02:00
|
|
|
|
if (!show_all && !(c.flags & 0x01)) continue;
|
|
|
|
|
|
_sorted[_num_contacts++] = i;
|
2026-05-11 19:16:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
void buildChannelList() {
|
|
|
|
|
|
_num_channels = 0;
|
|
|
|
|
|
for (int i = 0; i < MAX_GROUP_CHANNELS; i++) {
|
|
|
|
|
|
ChannelDetails ch;
|
|
|
|
|
|
if (the_mesh.getChannel(i, ch) && ch.name[0] != '\0') {
|
|
|
|
|
|
_channel_indices[_num_channels++] = (uint8_t)i;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 18:56:26 +02:00
|
|
|
|
// Returns per-channel notification state: 0=follow global, 1=muted, 2=force-on
|
|
|
|
|
|
uint8_t chNotifState(uint8_t ch_idx) const {
|
|
|
|
|
|
NodePrefs* p = _task->getNodePrefs();
|
|
|
|
|
|
if (!p) return 0;
|
|
|
|
|
|
uint64_t mask = 1ULL << ch_idx;
|
|
|
|
|
|
if (!(p->ch_notif_override & mask)) return 0;
|
|
|
|
|
|
return (p->ch_notif_muted & mask) ? 1 : 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void setChNotifState(uint8_t ch_idx, uint8_t state) {
|
|
|
|
|
|
NodePrefs* p = _task->getNodePrefs();
|
|
|
|
|
|
if (!p) return;
|
|
|
|
|
|
uint64_t mask = 1ULL << ch_idx;
|
|
|
|
|
|
if (state == 0) {
|
|
|
|
|
|
p->ch_notif_override &= ~mask;
|
|
|
|
|
|
p->ch_notif_muted &= ~mask;
|
|
|
|
|
|
} else if (state == 1) {
|
|
|
|
|
|
p->ch_notif_override |= mask;
|
|
|
|
|
|
p->ch_notif_muted |= mask;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
p->ch_notif_override |= mask;
|
|
|
|
|
|
p->ch_notif_muted &= ~mask;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 18:45:35 +02:00
|
|
|
|
public:
|
|
|
|
|
|
QuickMsgScreen(UITask* task)
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
: _task(task), _phase(MODE_SELECT), _mode_sel(0),
|
2026-05-11 19:16:26 +02:00
|
|
|
|
_contact_sel(0), _contact_scroll(0), _num_contacts(0), _room_mode(false),
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
_channel_sel(0), _channel_scroll(0), _num_channels(0),
|
|
|
|
|
|
_sel_channel_idx(0), _sending_to_channel(false),
|
2026-05-11 16:52:24 +02:00
|
|
|
|
_msg_sel(0), _msg_scroll(0), _active_msg_count(0),
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
_hist_sel(0), _hist_scroll(0),
|
|
|
|
|
|
_hist_fullscreen(false), _hist_fs_scroll(0),
|
2026-05-11 21:10:36 +02:00
|
|
|
|
_viewing_unread_start(0), _viewing_max_seen(0),
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
_hist_head(0), _hist_count(0),
|
2026-05-11 16:52:24 +02:00
|
|
|
|
_kb_row(0), _kb_col(0), _kb_len(0),
|
2026-05-11 18:56:26 +02:00
|
|
|
|
_kb_caps(false), _kb_ph_mode(false), _kb_ph_sel(0),
|
2026-05-11 19:34:07 +02:00
|
|
|
|
_ctx_open(false), _ctx_dirty(false), _ctx_sel(0) {
|
2026-05-11 16:52:24 +02:00
|
|
|
|
_kb_text[0] = '\0';
|
|
|
|
|
|
memset(_ch_unread, 0, sizeof(_ch_unread));
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
|
|
|
|
|
|
void addChannelMsg(uint8_t ch_idx, const char* text) {
|
|
|
|
|
|
int pos;
|
|
|
|
|
|
if (_hist_count < CH_HIST_MAX) {
|
|
|
|
|
|
pos = (_hist_head + _hist_count) % CH_HIST_MAX;
|
|
|
|
|
|
_hist_count++;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
pos = _hist_head;
|
|
|
|
|
|
_hist_head = (_hist_head + 1) % CH_HIST_MAX;
|
|
|
|
|
|
}
|
|
|
|
|
|
_hist[pos].ch_idx = ch_idx;
|
|
|
|
|
|
strncpy(_hist[pos].text, text, sizeof(_hist[pos].text) - 1);
|
|
|
|
|
|
_hist[pos].text[sizeof(_hist[pos].text) - 1] = '\0';
|
2026-05-11 16:52:24 +02:00
|
|
|
|
|
|
|
|
|
|
if (ch_idx < MAX_GROUP_CHANNELS) {
|
|
|
|
|
|
bool viewing = (_phase == CHANNEL_HIST && _sel_channel_idx == (int)ch_idx);
|
|
|
|
|
|
if (!viewing && _ch_unread[ch_idx] < 99) _ch_unread[ch_idx]++;
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
}
|
2026-05-10 18:45:35 +02:00
|
|
|
|
|
2026-05-11 19:11:19 +02:00
|
|
|
|
int getTotalChannelUnread() const {
|
|
|
|
|
|
int total = 0;
|
|
|
|
|
|
for (int i = 0; i < MAX_GROUP_CHANNELS; i++) total += _ch_unread[i];
|
|
|
|
|
|
return total;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 21:10:36 +02:00
|
|
|
|
void updateChannelUnread() {
|
|
|
|
|
|
if (_hist_sel < 0 || _sel_channel_idx < 0 || _sel_channel_idx >= MAX_GROUP_CHANNELS) return;
|
|
|
|
|
|
if (_hist_sel > _viewing_max_seen) _viewing_max_seen = _hist_sel;
|
|
|
|
|
|
int hist_count = histCountForChannel(_sel_channel_idx);
|
|
|
|
|
|
int watermark = _viewing_max_seen > (_viewing_unread_start - 1)
|
|
|
|
|
|
? _viewing_max_seen : (_viewing_unread_start - 1);
|
|
|
|
|
|
int remaining = (hist_count - 1) - watermark;
|
|
|
|
|
|
_ch_unread[_sel_channel_idx] = (uint8_t)(remaining > 0 ? remaining : 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 18:45:35 +02:00
|
|
|
|
void reset() {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
_phase = MODE_SELECT;
|
|
|
|
|
|
_mode_sel = 0;
|
2026-05-10 18:45:35 +02:00
|
|
|
|
_contact_sel = _contact_scroll = 0;
|
|
|
|
|
|
_msg_sel = _msg_scroll = 0;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
_channel_sel = _channel_scroll = 0;
|
|
|
|
|
|
_sending_to_channel = false;
|
|
|
|
|
|
_kb_row = _kb_col = _kb_len = 0;
|
2026-05-11 16:52:24 +02:00
|
|
|
|
_kb_caps = false; _kb_ph_mode = false; _kb_ph_sel = 0;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
_kb_text[0] = '\0';
|
|
|
|
|
|
|
2026-05-11 19:16:26 +02:00
|
|
|
|
_room_mode = false;
|
|
|
|
|
|
buildContactList();
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
buildChannelList();
|
2026-05-11 19:34:07 +02:00
|
|
|
|
|
|
|
|
|
|
_ctx_open = false;
|
|
|
|
|
|
_ctx_dirty = false;
|
|
|
|
|
|
_ctx_sel = 0;
|
2026-05-11 21:10:36 +02:00
|
|
|
|
_viewing_unread_start = 0;
|
|
|
|
|
|
_viewing_max_seen = 0;
|
2026-05-10 18:45:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int render(DisplayDriver& display) override {
|
|
|
|
|
|
display.setTextSize(1);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
|
|
|
|
|
|
if (_phase == MODE_SELECT) {
|
|
|
|
|
|
display.drawTextCentered(display.width()/2, 0, "MESSAGE");
|
|
|
|
|
|
display.fillRect(0, 10, display.width(), 1);
|
2026-05-11 19:16:26 +02:00
|
|
|
|
const char* opts[] = { "Direct message", "Channels", "Room Servers" };
|
2026-05-11 19:27:16 +02:00
|
|
|
|
int badges[3] = {
|
|
|
|
|
|
_task->getMsgCount() - _task->getRoomUnreadCount(), // DM only
|
|
|
|
|
|
_task->getChannelUnreadCount(),
|
|
|
|
|
|
_task->getRoomUnreadCount()
|
|
|
|
|
|
};
|
|
|
|
|
|
if (badges[0] < 0) badges[0] = 0;
|
2026-05-11 19:16:26 +02:00
|
|
|
|
for (int i = 0; i < 3; i++) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
int y = START_Y + i * ITEM_H;
|
|
|
|
|
|
bool sel = (i == _mode_sel);
|
|
|
|
|
|
if (sel) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.fillRect(0, y - 1, display.width(), ITEM_H - 1);
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
}
|
|
|
|
|
|
display.setCursor(0, y);
|
|
|
|
|
|
display.print(sel ? ">" : " ");
|
|
|
|
|
|
display.setCursor(8, y);
|
|
|
|
|
|
display.print(opts[i]);
|
2026-05-11 19:27:16 +02:00
|
|
|
|
if (badges[i] > 0) {
|
2026-05-11 17:19:03 +02:00
|
|
|
|
char badge[5];
|
2026-05-11 19:27:16 +02:00
|
|
|
|
snprintf(badge, sizeof(badge), "%d", badges[i]);
|
2026-05-11 17:19:03 +02:00
|
|
|
|
int bw = display.getTextWidth(badge) + 2;
|
|
|
|
|
|
display.setCursor(display.width() - bw, y);
|
|
|
|
|
|
display.print(badge);
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-10 18:45:35 +02:00
|
|
|
|
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
} else if (_phase == CONTACT_PICK) {
|
2026-05-11 19:16:26 +02:00
|
|
|
|
display.drawTextCentered(display.width()/2, 0, _room_mode ? "SELECT ROOM" : "SELECT CONTACT");
|
2026-05-10 18:45:35 +02:00
|
|
|
|
display.fillRect(0, 10, display.width(), 1);
|
|
|
|
|
|
|
|
|
|
|
|
if (_num_contacts == 0) {
|
2026-05-11 19:16:26 +02:00
|
|
|
|
display.drawTextCentered(display.width()/2, 32, _room_mode ? "No room servers" : "No favourites");
|
2026-05-10 18:45:35 +02:00
|
|
|
|
return 5000;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < VISIBLE && (_contact_scroll+i) < _num_contacts; i++) {
|
2026-05-10 18:52:31 +02:00
|
|
|
|
int list_idx = _contact_scroll + i;
|
|
|
|
|
|
int mesh_idx = _sorted[list_idx];
|
|
|
|
|
|
bool sel = (list_idx == _contact_sel);
|
2026-05-10 18:45:35 +02:00
|
|
|
|
int y = START_Y + i * ITEM_H;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
|
|
|
|
|
|
if (sel) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.fillRect(0, y - 1, display.width(), ITEM_H - 1);
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
}
|
2026-05-10 18:45:35 +02:00
|
|
|
|
|
|
|
|
|
|
ContactInfo c;
|
2026-05-10 18:52:31 +02:00
|
|
|
|
if (the_mesh.getContactByIdx(mesh_idx, c)) {
|
|
|
|
|
|
display.setCursor(0, y);
|
2026-05-11 18:58:39 +02:00
|
|
|
|
display.print(sel ? ">" : " ");
|
2026-05-10 18:45:35 +02:00
|
|
|
|
char filtered[sizeof(c.name)];
|
|
|
|
|
|
display.translateUTF8ToBlocks(filtered, c.name, sizeof(filtered));
|
|
|
|
|
|
display.drawTextEllipsized(8, y, display.width() - 24, filtered);
|
|
|
|
|
|
char hop[5];
|
2026-05-10 18:53:52 +02:00
|
|
|
|
snprintf(hop, sizeof(hop), c.out_path_len == 0xFF ? "D" : "%dh", (int)c.out_path_len);
|
2026-05-10 18:45:35 +02:00
|
|
|
|
display.setCursor(display.width() - display.getTextWidth(hop) - 1, y);
|
|
|
|
|
|
display.print(hop);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-10 18:45:35 +02:00
|
|
|
|
renderScrollHints(display, _contact_scroll, _num_contacts);
|
|
|
|
|
|
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
} else if (_phase == CHANNEL_PICK) {
|
|
|
|
|
|
display.drawTextCentered(display.width()/2, 0, "SELECT CHANNEL");
|
|
|
|
|
|
display.fillRect(0, 10, display.width(), 1);
|
|
|
|
|
|
|
|
|
|
|
|
if (_num_channels == 0) {
|
|
|
|
|
|
display.drawTextCentered(display.width()/2, 32, "No channels");
|
|
|
|
|
|
return 5000;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < VISIBLE && (_channel_scroll+i) < _num_channels; i++) {
|
|
|
|
|
|
int list_idx = _channel_scroll + i;
|
|
|
|
|
|
bool sel = (list_idx == _channel_sel);
|
|
|
|
|
|
int y = START_Y + i * ITEM_H;
|
|
|
|
|
|
|
|
|
|
|
|
if (sel) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.fillRect(0, y - 1, display.width(), ITEM_H - 1);
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
}
|
|
|
|
|
|
display.setCursor(0, y);
|
|
|
|
|
|
display.print(sel ? ">" : " ");
|
|
|
|
|
|
ChannelDetails ch;
|
|
|
|
|
|
if (the_mesh.getChannel(_channel_indices[list_idx], ch)) {
|
2026-05-11 16:52:24 +02:00
|
|
|
|
uint8_t unread = _ch_unread[_channel_indices[list_idx]];
|
|
|
|
|
|
char badge[5] = "";
|
|
|
|
|
|
int bw = 0;
|
|
|
|
|
|
if (unread > 0) {
|
|
|
|
|
|
snprintf(badge, sizeof(badge), "%d", (int)unread);
|
|
|
|
|
|
bw = display.getTextWidth(badge) + 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
display.drawTextEllipsized(8, y, display.width() - 10 - bw, ch.name);
|
|
|
|
|
|
if (unread > 0) {
|
|
|
|
|
|
display.setCursor(display.width() - bw, y);
|
|
|
|
|
|
display.print(badge);
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
renderScrollHints(display, _channel_scroll, _num_channels);
|
|
|
|
|
|
|
2026-05-11 18:56:26 +02:00
|
|
|
|
// Context menu overlay (long-press ENTER)
|
|
|
|
|
|
if (_ctx_open && _num_channels > 0) {
|
|
|
|
|
|
static const char* NOTIF_LABELS[] = { "default", "OFF", "ON" };
|
|
|
|
|
|
uint8_t ch_idx = _channel_indices[_channel_sel];
|
|
|
|
|
|
uint8_t nstate = chNotifState(ch_idx);
|
|
|
|
|
|
char notif_item[22];
|
|
|
|
|
|
snprintf(notif_item, sizeof(notif_item), "Notif: %s", NOTIF_LABELS[nstate]);
|
|
|
|
|
|
const char* items[] = { "Mark all read", notif_item };
|
|
|
|
|
|
const int CTX_COUNT = 2;
|
|
|
|
|
|
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
display.fillRect(15, 14, 98, 34);
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.drawRect(15, 14, 98, 34);
|
|
|
|
|
|
display.setCursor(19, 15);
|
|
|
|
|
|
display.print("Channel options");
|
|
|
|
|
|
display.fillRect(15, 24, 98, 1);
|
|
|
|
|
|
for (int i = 0; i < CTX_COUNT; i++) {
|
|
|
|
|
|
int py = 27 + i * 10;
|
|
|
|
|
|
if (i == _ctx_sel) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.fillRect(16, py - 1, 96, 10);
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
}
|
|
|
|
|
|
display.setCursor(19, py);
|
|
|
|
|
|
display.print(items[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
} else if (_phase == CHANNEL_HIST) {
|
|
|
|
|
|
if (_hist_fullscreen && _hist_sel >= 0) {
|
2026-05-11 21:10:36 +02:00
|
|
|
|
int fs_hist_count = histCountForChannel(_sel_channel_idx);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
int ring_pos = histEntryForChannel(_sel_channel_idx, _hist_sel);
|
|
|
|
|
|
if (ring_pos >= 0) {
|
|
|
|
|
|
const char* ftext = _hist[ring_pos].text;
|
|
|
|
|
|
const char* fsep = strstr(ftext, ": ");
|
|
|
|
|
|
char fsender[22], fmsg[79];
|
|
|
|
|
|
if (fsep) {
|
|
|
|
|
|
int nl = fsep - ftext; if (nl > 21) nl = 21;
|
|
|
|
|
|
strncpy(fsender, ftext, nl); fsender[nl] = '\0';
|
|
|
|
|
|
strncpy(fmsg, fsep + 2, sizeof(fmsg) - 1); fmsg[sizeof(fmsg)-1] = '\0';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
strncpy(fsender, "?", sizeof(fsender));
|
|
|
|
|
|
strncpy(fmsg, ftext, sizeof(fmsg) - 1); fmsg[sizeof(fmsg)-1] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.fillRect(0, 0, display.width(), 10);
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
display.drawTextEllipsized(2, 1, display.width() - 4, fsender);
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
|
|
|
|
|
|
char lines[12][FS_CHARS + 1];
|
|
|
|
|
|
int lcount = wrapLines(fmsg, lines, 12);
|
|
|
|
|
|
int max_scroll = lcount > FS_VISIBLE ? lcount - FS_VISIBLE : 0;
|
|
|
|
|
|
if (_hist_fs_scroll > max_scroll) _hist_fs_scroll = max_scroll;
|
|
|
|
|
|
for (int i = 0; i < FS_VISIBLE && (_hist_fs_scroll + i) < lcount; i++) {
|
|
|
|
|
|
display.setCursor(0, FS_START_Y + i * FS_LINE_H);
|
|
|
|
|
|
display.print(lines[_hist_fs_scroll + i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_hist_fs_scroll > 0) {
|
|
|
|
|
|
display.setCursor(display.width() - 6, FS_START_Y);
|
|
|
|
|
|
display.print("^");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_hist_fs_scroll < max_scroll) {
|
|
|
|
|
|
display.setCursor(display.width() - 6, FS_START_Y + (FS_VISIBLE - 1) * FS_LINE_H);
|
|
|
|
|
|
display.print("v");
|
|
|
|
|
|
}
|
2026-05-11 21:10:36 +02:00
|
|
|
|
if (_hist_sel > 0) {
|
|
|
|
|
|
display.setCursor(0, 56);
|
|
|
|
|
|
display.print("<");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_hist_sel < fs_hist_count - 1) {
|
|
|
|
|
|
display.setCursor(display.width() - 6, 56);
|
|
|
|
|
|
display.print(">");
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
return 300;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ChannelDetails ch;
|
|
|
|
|
|
the_mesh.getChannel(_sel_channel_idx, ch);
|
|
|
|
|
|
char title[24];
|
|
|
|
|
|
snprintf(title, sizeof(title), "#%.21s", ch.name);
|
|
|
|
|
|
display.drawTextCentered(display.width()/2, 0, title);
|
|
|
|
|
|
display.fillRect(0, 9, display.width(), 1);
|
|
|
|
|
|
|
|
|
|
|
|
int ch_hist_count = histCountForChannel(_sel_channel_idx);
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < HIST_VISIBLE && (_hist_scroll + i) < ch_hist_count; i++) {
|
|
|
|
|
|
int item = _hist_scroll + i;
|
|
|
|
|
|
bool sel = (item == _hist_sel);
|
|
|
|
|
|
int y = HIST_START_Y + i * HIST_ITEM_H;
|
|
|
|
|
|
|
|
|
|
|
|
int ring_pos = histEntryForChannel(_sel_channel_idx, item);
|
|
|
|
|
|
if (ring_pos < 0) continue;
|
|
|
|
|
|
|
|
|
|
|
|
const char* text = _hist[ring_pos].text;
|
|
|
|
|
|
const char* sep = strstr(text, ": ");
|
|
|
|
|
|
char sender[22], msg_part[79];
|
|
|
|
|
|
if (sep) {
|
|
|
|
|
|
int nl = sep - text; if (nl > 21) nl = 21;
|
|
|
|
|
|
strncpy(sender, text, nl); sender[nl] = '\0';
|
|
|
|
|
|
strncpy(msg_part, sep + 2, sizeof(msg_part) - 1);
|
|
|
|
|
|
msg_part[sizeof(msg_part) - 1] = '\0';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
strncpy(sender, "?", sizeof(sender));
|
|
|
|
|
|
strncpy(msg_part, text, sizeof(msg_part) - 1);
|
|
|
|
|
|
msg_part[sizeof(msg_part) - 1] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (sel) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.fillRect(0, y, display.width(), HIST_BOX_H);
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
display.drawTextEllipsized(3, y + 1, display.width() - 6, sender);
|
|
|
|
|
|
display.drawTextEllipsized(3, y + 10, display.width() - 6, msg_part);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.drawRect(0, y, display.width(), HIST_BOX_H);
|
|
|
|
|
|
display.fillRect(1, y + 1, display.width() - 2, 8);
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
display.drawTextEllipsized(3, y + 1, display.width() - 6, sender);
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.drawTextEllipsized(3, y + 10, display.width() - 6, msg_part);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (ch_hist_count == 0) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.drawTextCentered(display.width()/2, 32, "No messages yet");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// scroll hints
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
if (_hist_scroll > 0) {
|
|
|
|
|
|
display.setCursor(display.width() - 6, HIST_START_Y + 1);
|
|
|
|
|
|
display.print("^");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_hist_scroll + HIST_VISIBLE < ch_hist_count) {
|
|
|
|
|
|
display.setCursor(display.width() - 6, HIST_START_Y + HIST_VISIBLE * HIST_ITEM_H - 10);
|
|
|
|
|
|
display.print("v");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// small compose button (bottom-left, always bordered, inverted when selected)
|
|
|
|
|
|
bool compose_sel = (_hist_sel == -1);
|
|
|
|
|
|
const char* ctxt = "[+ send]";
|
|
|
|
|
|
int ctw = display.getTextWidth(ctxt);
|
|
|
|
|
|
int cbx = 1, cby = 55;
|
|
|
|
|
|
if (compose_sel) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.fillRect(cbx - 1, cby - 1, ctw + 4, 10);
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.drawRect(cbx - 1, cby - 1, ctw + 4, 10);
|
|
|
|
|
|
}
|
|
|
|
|
|
display.setCursor(cbx + 1, cby);
|
|
|
|
|
|
display.print(ctxt);
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
|
|
|
|
|
|
} else if (_phase == KEYBOARD) {
|
|
|
|
|
|
// text preview with cursor
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
const char* disp_start = _kb_text;
|
|
|
|
|
|
int disp_len = _kb_len;
|
|
|
|
|
|
if (disp_len > 20) { disp_start = _kb_text + (disp_len - 20); disp_len = 20; }
|
|
|
|
|
|
char preview[24];
|
|
|
|
|
|
snprintf(preview, sizeof(preview), "%.*s_", disp_len, disp_start);
|
|
|
|
|
|
display.setCursor(0, KB_TEXT_Y);
|
|
|
|
|
|
display.print(preview);
|
|
|
|
|
|
display.fillRect(0, KB_SEP_Y, display.width(), 1);
|
|
|
|
|
|
|
|
|
|
|
|
// char rows
|
|
|
|
|
|
for (int row = 0; row < KB_ROWS_CHAR; row++) {
|
|
|
|
|
|
int y = KB_CHARS_Y + row * KB_CELL_H;
|
|
|
|
|
|
for (int col = 0; col < KB_COLS_CHAR; col++) {
|
|
|
|
|
|
bool sel = (_kb_row == row && _kb_col == col);
|
2026-05-11 16:52:24 +02:00
|
|
|
|
char ch = KB_CHARS[row][col];
|
|
|
|
|
|
if (_kb_caps && ch >= 'a' && ch <= 'z') ch = ch - 'a' + 'A';
|
|
|
|
|
|
char ch_buf[2] = { ch, '\0' };
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
if (ch_buf[0] == ' ') ch_buf[0] = '_';
|
|
|
|
|
|
int cx = col * KB_CELL_W;
|
|
|
|
|
|
if (sel) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.fillRect(cx, y - 1, KB_CELL_W - 1, KB_CELL_H);
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
}
|
|
|
|
|
|
display.setCursor(cx + 3, y);
|
|
|
|
|
|
display.print(ch_buf);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 16:52:24 +02:00
|
|
|
|
// special row: [^] [Sp] [Del] [{}] [OK] — 5 buttons at 25px each
|
|
|
|
|
|
const char* spec[] = { "[^]", "[Sp]", "[Del]", "[{}]", "[OK]" };
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
for (int i = 0; i < KB_SPECIAL; i++) {
|
|
|
|
|
|
bool sel = (_kb_row == KB_ROWS_CHAR && _kb_col == i);
|
2026-05-11 16:52:24 +02:00
|
|
|
|
bool active = (i == 0 && _kb_caps); // caps indicator
|
|
|
|
|
|
int sx = i * 25;
|
|
|
|
|
|
if (sel || active) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-11 16:52:24 +02:00
|
|
|
|
display.fillRect(sx, KB_SPECIAL_Y - 1, 24, KB_CELL_H);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
}
|
|
|
|
|
|
display.setCursor(sx + 1, KB_SPECIAL_Y);
|
|
|
|
|
|
display.print(spec[i]);
|
2026-05-11 16:52:24 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// placeholder picker overlay
|
|
|
|
|
|
if (_kb_ph_mode) {
|
2026-05-11 17:19:03 +02:00
|
|
|
|
// Black box with white border
|
2026-05-11 16:52:24 +02:00
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
2026-05-11 17:19:03 +02:00
|
|
|
|
display.fillRect(20, 20, 88, 12 + KB_PH_COUNT * 10);
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.drawRect(20, 20, 88, 12 + KB_PH_COUNT * 10);
|
2026-05-11 16:52:24 +02:00
|
|
|
|
display.setCursor(24, 21);
|
|
|
|
|
|
display.print("Placeholder:");
|
2026-05-11 17:19:03 +02:00
|
|
|
|
display.fillRect(20, 30, 88, 1); // separator
|
2026-05-11 16:52:24 +02:00
|
|
|
|
for (int i = 0; i < KB_PH_COUNT; i++) {
|
2026-05-11 17:19:03 +02:00
|
|
|
|
int py = 33 + i * 10;
|
2026-05-11 16:52:24 +02:00
|
|
|
|
if (i == _kb_ph_sel) {
|
2026-05-11 17:19:03 +02:00
|
|
|
|
// Selected: white fill + black text
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.fillRect(21, py - 1, 86, 10);
|
2026-05-11 16:52:24 +02:00
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
} else {
|
2026-05-11 17:19:03 +02:00
|
|
|
|
// Unselected: white text on black
|
2026-05-11 16:52:24 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
}
|
|
|
|
|
|
display.setCursor(24, py);
|
|
|
|
|
|
display.print(KB_PH_LIST[i]);
|
|
|
|
|
|
}
|
2026-05-11 17:19:03 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} else { // MSG_PICK
|
2026-05-10 18:45:35 +02:00
|
|
|
|
char title[24];
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
if (_sending_to_channel) {
|
|
|
|
|
|
ChannelDetails ch;
|
|
|
|
|
|
the_mesh.getChannel(_sel_channel_idx, ch);
|
|
|
|
|
|
snprintf(title, sizeof(title), "#%.21s", ch.name);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
snprintf(title, sizeof(title), "TO:%.14s", _sel_contact.name);
|
|
|
|
|
|
}
|
2026-05-10 18:45:35 +02:00
|
|
|
|
display.drawTextCentered(display.width()/2, 0, title);
|
|
|
|
|
|
display.fillRect(0, 10, display.width(), 1);
|
|
|
|
|
|
|
2026-05-11 16:52:24 +02:00
|
|
|
|
int total_msg_items = 1 + _active_msg_count;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
for (int i = 0; i < VISIBLE && (_msg_scroll+i) < total_msg_items; i++) {
|
2026-05-10 18:45:35 +02:00
|
|
|
|
int idx = _msg_scroll + i;
|
|
|
|
|
|
bool sel = (idx == _msg_sel);
|
|
|
|
|
|
int y = START_Y + i * ITEM_H;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
|
|
|
|
|
|
if (sel) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.fillRect(0, y - 1, display.width(), ITEM_H - 1);
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
}
|
2026-05-10 18:45:35 +02:00
|
|
|
|
display.setCursor(0, y);
|
|
|
|
|
|
display.print(sel ? ">" : " ");
|
|
|
|
|
|
|
|
|
|
|
|
if (idx == 0) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setCursor(8, y);
|
|
|
|
|
|
display.print("Custom message...");
|
2026-05-10 18:45:35 +02:00
|
|
|
|
} else {
|
2026-05-11 16:52:24 +02:00
|
|
|
|
NodePrefs* p = _task->getNodePrefs();
|
|
|
|
|
|
int slot = _active_msgs[idx - 1];
|
|
|
|
|
|
const char* tmpl = p ? p->custom_msgs[slot] : "";
|
|
|
|
|
|
display.drawTextEllipsized(8, y, display.width() - 10, tmpl);
|
2026-05-10 18:45:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
renderScrollHints(display, _msg_scroll, total_msg_items);
|
2026-05-10 18:45:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
return 300;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool handleInput(char c) override {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
if (_phase == MODE_SELECT) {
|
2026-05-10 18:45:35 +02:00
|
|
|
|
if (c == KEY_CANCEL) { _task->gotoHomeScreen(); return true; }
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
if (c == KEY_UP && _mode_sel > 0) { _mode_sel--; return true; }
|
2026-05-11 19:16:26 +02:00
|
|
|
|
if (c == KEY_DOWN && _mode_sel < 2) { _mode_sel++; return true; }
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
if (c == KEY_ENTER) {
|
2026-05-11 19:16:26 +02:00
|
|
|
|
if (_mode_sel == 1) {
|
|
|
|
|
|
_phase = CHANNEL_PICK;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
_room_mode = (_mode_sel == 2);
|
2026-05-11 19:27:16 +02:00
|
|
|
|
if (_room_mode) _task->clearRoomUnread();
|
2026-05-11 19:16:26 +02:00
|
|
|
|
buildContactList();
|
|
|
|
|
|
_contact_sel = _contact_scroll = 0;
|
|
|
|
|
|
_phase = CONTACT_PICK;
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} else if (_phase == CONTACT_PICK) {
|
2026-05-11 19:16:26 +02:00
|
|
|
|
if (c == KEY_CANCEL) { _room_mode = false; _phase = MODE_SELECT; return true; }
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
if (c == KEY_UP && _contact_sel > 0) {
|
2026-05-10 18:45:35 +02:00
|
|
|
|
_contact_sel--;
|
|
|
|
|
|
if (_contact_sel < _contact_scroll) _contact_scroll = _contact_sel;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
if (c == KEY_DOWN && _contact_sel < _num_contacts - 1) {
|
2026-05-10 18:45:35 +02:00
|
|
|
|
_contact_sel++;
|
|
|
|
|
|
if (_contact_sel >= _contact_scroll + VISIBLE) _contact_scroll = _contact_sel - VISIBLE + 1;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_ENTER && _num_contacts > 0) {
|
2026-05-10 18:52:31 +02:00
|
|
|
|
if (the_mesh.getContactByIdx(_sorted[_contact_sel], _sel_contact)) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
_sending_to_channel = false;
|
2026-05-11 16:52:24 +02:00
|
|
|
|
setupMsgPick();
|
2026-05-10 18:45:35 +02:00
|
|
|
|
_phase = MSG_PICK;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
|
|
|
|
|
|
} else if (_phase == CHANNEL_PICK) {
|
2026-05-11 18:56:26 +02:00
|
|
|
|
// Context menu consumes all input while open
|
|
|
|
|
|
if (_ctx_open) {
|
2026-05-11 19:28:37 +02:00
|
|
|
|
if (c == KEY_CANCEL) {
|
|
|
|
|
|
if (_ctx_dirty) { the_mesh.savePrefs(); _ctx_dirty = false; }
|
|
|
|
|
|
_ctx_open = false;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-05-11 18:56:26 +02:00
|
|
|
|
if (c == KEY_UP && _ctx_sel > 0) { _ctx_sel--; return true; }
|
|
|
|
|
|
if (c == KEY_DOWN && _ctx_sel < 1) { _ctx_sel++; return true; }
|
|
|
|
|
|
if (c == KEY_ENTER && _num_channels > 0) {
|
|
|
|
|
|
uint8_t ch_idx = _channel_indices[_channel_sel];
|
|
|
|
|
|
if (_ctx_sel == 0) {
|
2026-05-11 19:28:37 +02:00
|
|
|
|
_ch_unread[ch_idx] = 0;
|
2026-05-11 18:56:26 +02:00
|
|
|
|
} else {
|
|
|
|
|
|
uint8_t nstate = chNotifState(ch_idx);
|
2026-05-11 19:28:37 +02:00
|
|
|
|
setChNotifState(ch_idx, (nstate + 1) % 3);
|
|
|
|
|
|
_ctx_dirty = true;
|
2026-05-11 18:56:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
if (c == KEY_CANCEL) { _phase = MODE_SELECT; return true; }
|
|
|
|
|
|
if (c == KEY_UP && _channel_sel > 0) {
|
|
|
|
|
|
_channel_sel--;
|
|
|
|
|
|
if (_channel_sel < _channel_scroll) _channel_scroll = _channel_sel;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_DOWN && _channel_sel < _num_channels - 1) {
|
|
|
|
|
|
_channel_sel++;
|
|
|
|
|
|
if (_channel_sel >= _channel_scroll + VISIBLE) _channel_scroll = _channel_sel - VISIBLE + 1;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_ENTER && _num_channels > 0) {
|
|
|
|
|
|
_sel_channel_idx = _channel_indices[_channel_sel];
|
2026-05-11 21:10:36 +02:00
|
|
|
|
int hc = histCountForChannel(_sel_channel_idx);
|
|
|
|
|
|
_viewing_unread_start = hc - (int)_ch_unread[_sel_channel_idx];
|
|
|
|
|
|
if (_viewing_unread_start < 0) _viewing_unread_start = 0;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
_hist_scroll = 0;
|
2026-05-11 21:10:36 +02:00
|
|
|
|
_hist_sel = hc > 0 ? 0 : -1;
|
|
|
|
|
|
_viewing_max_seen = _hist_sel >= 0 ? _hist_sel : 0;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
_phase = CHANNEL_HIST;
|
2026-05-11 21:10:36 +02:00
|
|
|
|
updateChannelUnread();
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-05-11 18:56:26 +02:00
|
|
|
|
if (c == KEY_CONTEXT_MENU && _num_channels > 0) {
|
|
|
|
|
|
_ctx_sel = 0;
|
2026-05-11 19:28:37 +02:00
|
|
|
|
_ctx_dirty = false;
|
2026-05-11 18:56:26 +02:00
|
|
|
|
_ctx_open = true;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
|
|
|
|
|
|
} else if (_phase == CHANNEL_HIST) {
|
|
|
|
|
|
int ch_hist_count = histCountForChannel(_sel_channel_idx);
|
|
|
|
|
|
if (_hist_fullscreen) {
|
|
|
|
|
|
if (c == KEY_UP) { if (_hist_fs_scroll > 0) _hist_fs_scroll--; return true; }
|
|
|
|
|
|
if (c == KEY_DOWN) { _hist_fs_scroll++; return true; }
|
2026-05-11 21:10:36 +02:00
|
|
|
|
if (c == KEY_LEFT) {
|
|
|
|
|
|
if (_hist_sel > 0) { _hist_sel--; _hist_fs_scroll = 0; updateChannelUnread(); }
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_RIGHT) {
|
|
|
|
|
|
int count = histCountForChannel(_sel_channel_idx);
|
|
|
|
|
|
if (_hist_sel < count - 1) { _hist_sel++; _hist_fs_scroll = 0; updateChannelUnread(); }
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
if (c == KEY_ENTER || c == KEY_CANCEL) { _hist_fullscreen = false; return true; }
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_CANCEL) { _phase = CHANNEL_PICK; return true; }
|
|
|
|
|
|
if (c == KEY_UP) {
|
|
|
|
|
|
if (_hist_sel > 0) { _hist_sel--; if (_hist_sel < _hist_scroll) _hist_scroll = _hist_sel; }
|
|
|
|
|
|
else if (_hist_sel == 0) _hist_sel = -1;
|
2026-05-11 21:10:36 +02:00
|
|
|
|
updateChannelUnread();
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_DOWN) {
|
|
|
|
|
|
if (_hist_sel == -1 && ch_hist_count > 0) { _hist_sel = 0; _hist_scroll = 0; }
|
|
|
|
|
|
else if (_hist_sel >= 0 && _hist_sel < ch_hist_count - 1) {
|
|
|
|
|
|
_hist_sel++;
|
|
|
|
|
|
if (_hist_sel >= _hist_scroll + HIST_VISIBLE) _hist_scroll = _hist_sel - HIST_VISIBLE + 1;
|
|
|
|
|
|
}
|
2026-05-11 21:10:36 +02:00
|
|
|
|
updateChannelUnread();
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_ENTER) {
|
|
|
|
|
|
if (_hist_sel >= 0) {
|
|
|
|
|
|
_hist_fullscreen = true;
|
|
|
|
|
|
_hist_fs_scroll = 0;
|
2026-05-11 21:10:36 +02:00
|
|
|
|
updateChannelUnread();
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
} else {
|
|
|
|
|
|
_sending_to_channel = true;
|
2026-05-11 16:52:24 +02:00
|
|
|
|
setupMsgPick();
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
_phase = MSG_PICK;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} else if (_phase == KEYBOARD) {
|
2026-05-11 16:52:24 +02:00
|
|
|
|
if (_kb_ph_mode) {
|
|
|
|
|
|
if (c == KEY_UP && _kb_ph_sel > 0) { _kb_ph_sel--; return true; }
|
|
|
|
|
|
if (c == KEY_DOWN && _kb_ph_sel < KB_PH_COUNT - 1) { _kb_ph_sel++; return true; }
|
|
|
|
|
|
if (c == KEY_ENTER) {
|
|
|
|
|
|
const char* ph = KB_PH_LIST[_kb_ph_sel];
|
|
|
|
|
|
int ph_len = strlen(ph);
|
|
|
|
|
|
if (_kb_len + ph_len <= KB_MAX_LEN) {
|
|
|
|
|
|
memcpy(_kb_text + _kb_len, ph, ph_len);
|
|
|
|
|
|
_kb_len += ph_len;
|
|
|
|
|
|
_kb_text[_kb_len] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
_kb_ph_mode = false;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_CANCEL) { _kb_ph_mode = false; return true; }
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
if (c == KEY_CANCEL) {
|
|
|
|
|
|
_phase = MSG_PICK;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_UP) {
|
|
|
|
|
|
if (_kb_row > 0) {
|
|
|
|
|
|
_kb_row--;
|
2026-05-11 17:19:03 +02:00
|
|
|
|
if (_kb_row == KB_ROWS_CHAR - 1) // leaving special row upward
|
|
|
|
|
|
_kb_col = _kb_col * KB_COLS_CHAR / KB_SPECIAL;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_DOWN) {
|
|
|
|
|
|
if (_kb_row < KB_ROWS_CHAR) {
|
|
|
|
|
|
_kb_row++;
|
2026-05-11 17:19:03 +02:00
|
|
|
|
if (_kb_row == KB_ROWS_CHAR) // entering special row
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
_kb_col = _kb_col * KB_SPECIAL / KB_COLS_CHAR;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_LEFT) {
|
|
|
|
|
|
if (_kb_col > 0) _kb_col--;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_RIGHT) {
|
|
|
|
|
|
int max_col = (_kb_row == KB_ROWS_CHAR) ? KB_SPECIAL - 1 : KB_COLS_CHAR - 1;
|
|
|
|
|
|
if (_kb_col < max_col) _kb_col++;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_ENTER) {
|
|
|
|
|
|
if (_kb_row < KB_ROWS_CHAR) {
|
|
|
|
|
|
if (_kb_len < KB_MAX_LEN) {
|
2026-05-11 16:52:24 +02:00
|
|
|
|
char ch = KB_CHARS[_kb_row][_kb_col];
|
|
|
|
|
|
if (_kb_caps && ch >= 'a' && ch <= 'z') ch = ch - 'a' + 'A';
|
|
|
|
|
|
_kb_text[_kb_len++] = ch;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
_kb_text[_kb_len] = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (_kb_col == 0) {
|
2026-05-11 16:52:24 +02:00
|
|
|
|
// Caps Lock toggle
|
|
|
|
|
|
_kb_caps = !_kb_caps;
|
|
|
|
|
|
} else if (_kb_col == 1) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
// Space
|
|
|
|
|
|
if (_kb_len < KB_MAX_LEN) {
|
|
|
|
|
|
_kb_text[_kb_len++] = ' ';
|
|
|
|
|
|
_kb_text[_kb_len] = '\0';
|
|
|
|
|
|
}
|
2026-05-11 16:52:24 +02:00
|
|
|
|
} else if (_kb_col == 2) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
// Delete
|
|
|
|
|
|
if (_kb_len > 0) _kb_text[--_kb_len] = '\0';
|
2026-05-11 16:52:24 +02:00
|
|
|
|
} else if (_kb_col == 3) {
|
|
|
|
|
|
// Placeholder picker
|
|
|
|
|
|
_kb_ph_mode = true;
|
|
|
|
|
|
_kb_ph_sel = 0;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
} else {
|
2026-05-11 17:19:03 +02:00
|
|
|
|
// OK — send (expand placeholders first)
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
if (_kb_len > 0) {
|
2026-05-11 17:19:03 +02:00
|
|
|
|
char expanded[KB_MAX_LEN + 1];
|
|
|
|
|
|
expandMsg(_kb_text, expanded, sizeof(expanded));
|
|
|
|
|
|
bool ok = sendText(expanded);
|
|
|
|
|
|
afterSend(ok, expanded);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} else { // MSG_PICK
|
2026-05-11 16:52:24 +02:00
|
|
|
|
int total_msg_items = 1 + _active_msg_count;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
if (c == KEY_CANCEL) {
|
|
|
|
|
|
_phase = _sending_to_channel ? CHANNEL_HIST : CONTACT_PICK;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_UP && _msg_sel > 0) {
|
2026-05-10 18:45:35 +02:00
|
|
|
|
_msg_sel--;
|
|
|
|
|
|
if (_msg_sel < _msg_scroll) _msg_scroll = _msg_sel;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
if (c == KEY_DOWN && _msg_sel < total_msg_items - 1) {
|
2026-05-10 18:45:35 +02:00
|
|
|
|
_msg_sel++;
|
|
|
|
|
|
if (_msg_sel >= _msg_scroll + VISIBLE) _msg_scroll = _msg_sel - VISIBLE + 1;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_ENTER) {
|
|
|
|
|
|
if (_msg_sel == 0) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
_kb_row = _kb_col = _kb_len = 0;
|
2026-05-11 16:52:24 +02:00
|
|
|
|
_kb_caps = false; _kb_ph_mode = false; _kb_ph_sel = 0;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
_kb_text[0] = '\0';
|
|
|
|
|
|
_phase = KEYBOARD;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-05-11 16:52:24 +02:00
|
|
|
|
NodePrefs* p = _task->getNodePrefs();
|
|
|
|
|
|
int slot = _active_msgs[_msg_sel - 1];
|
|
|
|
|
|
const char* tmpl = p ? p->custom_msgs[slot] : "OK";
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
char msg[80];
|
2026-05-11 16:52:24 +02:00
|
|
|
|
expandMsg(tmpl, msg, sizeof(msg));
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
bool ok = sendText(msg);
|
|
|
|
|
|
afterSend(ok, msg);
|
2026-05-10 18:45:35 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
|
class HomeScreen : public UIScreen {
|
|
|
|
|
|
enum HomePage {
|
2026-05-10 15:18:35 +02:00
|
|
|
|
CLOCK,
|
2025-08-08 20:01:31 +10:00
|
|
|
|
RECENT,
|
|
|
|
|
|
RADIO,
|
|
|
|
|
|
BLUETOOTH,
|
|
|
|
|
|
ADVERT,
|
2025-09-28 09:43:28 +02:00
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
2025-09-23 10:39:43 +02:00
|
|
|
|
GPS,
|
|
|
|
|
|
#endif
|
2025-09-09 16:32:41 +02:00
|
|
|
|
#if UI_SENSORS_PAGE == 1
|
2025-09-05 15:20:52 +02:00
|
|
|
|
SENSORS,
|
2025-09-09 16:32:41 +02:00
|
|
|
|
#endif
|
2026-05-10 15:18:35 +02:00
|
|
|
|
SETTINGS,
|
2026-05-10 18:45:35 +02:00
|
|
|
|
QUICK_MSG,
|
2025-08-08 20:01:31 +10:00
|
|
|
|
SHUTDOWN,
|
|
|
|
|
|
Count // keep as last
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
UITask* _task;
|
|
|
|
|
|
mesh::RTCClock* _rtc;
|
|
|
|
|
|
SensorManager* _sensors;
|
|
|
|
|
|
NodePrefs* _node_prefs;
|
|
|
|
|
|
uint8_t _page;
|
|
|
|
|
|
bool _shutdown_init;
|
2025-08-16 18:13:50 +02:00
|
|
|
|
AdvertPath recent[UI_RECENT_LIST_SIZE];
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
2025-09-16 17:17:15 -07:00
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
|
void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) {
|
2026-01-28 14:23:36 -07:00
|
|
|
|
#ifndef BATT_MIN_MILLIVOLTS
|
2026-05-10 15:18:35 +02:00
|
|
|
|
#define BATT_MIN_MILLIVOLTS 3200
|
2026-01-28 14:23:36 -07:00
|
|
|
|
#endif
|
2026-05-10 19:47:12 +02:00
|
|
|
|
// LiPo discharge curve: voltage (mV) → raw capacity (%)
|
|
|
|
|
|
static const struct { uint16_t mv; uint8_t pct; } CURVE[] = {
|
|
|
|
|
|
{3200, 0}, {3300, 3}, {3400, 8}, {3500, 15},
|
|
|
|
|
|
{3600, 25}, {3650, 33}, {3700, 45}, {3750, 58},
|
|
|
|
|
|
{3800, 68}, {3900, 77}, {4000, 86}, {4100, 93}, {4200, 100}
|
|
|
|
|
|
};
|
|
|
|
|
|
static const int CURVE_LEN = sizeof(CURVE) / sizeof(CURVE[0]);
|
|
|
|
|
|
|
|
|
|
|
|
auto curveAt = [&](int mv) -> int {
|
|
|
|
|
|
if (mv <= (int)CURVE[0].mv) return CURVE[0].pct;
|
|
|
|
|
|
if (mv >= (int)CURVE[CURVE_LEN-1].mv) return CURVE[CURVE_LEN-1].pct;
|
|
|
|
|
|
for (int i = 1; i < CURVE_LEN; i++) {
|
|
|
|
|
|
if (mv <= (int)CURVE[i].mv) {
|
|
|
|
|
|
int span_mv = CURVE[i].mv - CURVE[i-1].mv;
|
|
|
|
|
|
int span_pct = CURVE[i].pct - CURVE[i-1].pct;
|
|
|
|
|
|
return CURVE[i-1].pct + (mv - (int)CURVE[i-1].mv) * span_pct / span_mv;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return 100;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
int low_mv = (_node_prefs && _node_prefs->low_batt_mv > 0)
|
|
|
|
|
|
? (int)_node_prefs->low_batt_mv : BATT_MIN_MILLIVOLTS;
|
|
|
|
|
|
int raw_pct = curveAt((int)batteryMilliVolts);
|
|
|
|
|
|
int low_pct = curveAt(low_mv);
|
|
|
|
|
|
// rescale so low_mv = 0% and 4200mV = 100%
|
|
|
|
|
|
int pct = (low_pct >= 100) ? 0
|
|
|
|
|
|
: (raw_pct - low_pct) * 100 / (100 - low_pct);
|
2026-05-10 15:18:35 +02:00
|
|
|
|
if (pct < 0) pct = 0;
|
|
|
|
|
|
if (pct > 100) pct = 100;
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
|
uint8_t mode = (_node_prefs && _node_prefs->batt_display_mode < 3)
|
|
|
|
|
|
? _node_prefs->batt_display_mode : 0;
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
|
display.setTextSize(1);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
|
int battLeftX;
|
|
|
|
|
|
if (mode == 1) { // percent
|
|
|
|
|
|
char buf[6];
|
|
|
|
|
|
sprintf(buf, "%d%%", pct);
|
|
|
|
|
|
battLeftX = display.width() - display.getTextWidth(buf) - 1;
|
|
|
|
|
|
display.setCursor(battLeftX, 0);
|
|
|
|
|
|
display.print(buf);
|
|
|
|
|
|
} else if (mode == 2) { // voltage
|
|
|
|
|
|
char buf[8];
|
|
|
|
|
|
sprintf(buf, "%u.%02uV", batteryMilliVolts / 1000, (batteryMilliVolts % 1000) / 10);
|
|
|
|
|
|
battLeftX = display.width() - display.getTextWidth(buf) - 1;
|
|
|
|
|
|
display.setCursor(battLeftX, 0);
|
|
|
|
|
|
display.print(buf);
|
|
|
|
|
|
} else { // icon
|
|
|
|
|
|
const int iconWidth = 24, iconHeight = 10;
|
|
|
|
|
|
battLeftX = display.width() - iconWidth - 5;
|
|
|
|
|
|
display.drawRect(battLeftX, 0, iconWidth, iconHeight);
|
|
|
|
|
|
display.fillRect(battLeftX + iconWidth, iconHeight / 4, 3, iconHeight / 2);
|
|
|
|
|
|
int fillWidth = (pct * (iconWidth - 4)) / 100;
|
|
|
|
|
|
display.fillRect(battLeftX + 2, 2, fillWidth, iconHeight - 4);
|
|
|
|
|
|
}
|
2026-02-11 09:51:28 +01:00
|
|
|
|
|
|
|
|
|
|
#ifdef PIN_BUZZER
|
|
|
|
|
|
if (_task->isBuzzerQuiet()) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-10 15:18:35 +02:00
|
|
|
|
display.drawXbm(battLeftX - 9, 1, muted_icon, 8, 8);
|
2026-02-11 09:51:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2026-05-11 16:52:24 +02:00
|
|
|
|
|
|
|
|
|
|
// BT connection indicator (left of muted/battery icons)
|
|
|
|
|
|
if (_task->isSerialEnabled()) {
|
|
|
|
|
|
#ifdef PIN_BUZZER
|
|
|
|
|
|
int btX = battLeftX - 18;
|
|
|
|
|
|
#else
|
|
|
|
|
|
int btX = battLeftX - 9;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
if (_task->hasConnection()) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.fillRect(btX - 1, 0, 8, 9);
|
|
|
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
|
display.setCursor(btX, 1);
|
|
|
|
|
|
display.print("B");
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
display.setCursor(btX, 1);
|
|
|
|
|
|
display.print("b");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-09 16:32:41 +02:00
|
|
|
|
CayenneLPP sensors_lpp;
|
|
|
|
|
|
int sensors_nb = 0;
|
|
|
|
|
|
bool sensors_scroll = false;
|
|
|
|
|
|
int sensors_scroll_offset = 0;
|
2025-09-05 15:20:52 +02:00
|
|
|
|
int next_sensors_refresh = 0;
|
2025-10-22 20:01:15 +02:00
|
|
|
|
|
2025-09-05 15:20:52 +02:00
|
|
|
|
void refresh_sensors() {
|
|
|
|
|
|
if (millis() > next_sensors_refresh) {
|
2025-09-09 16:32:41 +02:00
|
|
|
|
sensors_lpp.reset();
|
|
|
|
|
|
sensors_nb = 0;
|
|
|
|
|
|
sensors_lpp.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
|
|
|
|
|
sensors.querySensors(0xFF, sensors_lpp);
|
|
|
|
|
|
LPPReader reader (sensors_lpp.getBuffer(), sensors_lpp.getSize());
|
|
|
|
|
|
uint8_t channel, type;
|
|
|
|
|
|
while(reader.readHeader(channel, type)) {
|
|
|
|
|
|
reader.skipData(type);
|
|
|
|
|
|
sensors_nb ++;
|
|
|
|
|
|
}
|
|
|
|
|
|
sensors_scroll = sensors_nb > UI_RECENT_LIST_SIZE;
|
2025-09-05 15:20:52 +02:00
|
|
|
|
#if AUTO_OFF_MILLIS > 0
|
|
|
|
|
|
next_sensors_refresh = millis() + 5000; // refresh sensor values every 5 sec
|
|
|
|
|
|
#else
|
|
|
|
|
|
next_sensors_refresh = millis() + 60000; // refresh sensor values every 1 min
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
|
public:
|
|
|
|
|
|
HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs)
|
2025-09-05 15:20:52 +02:00
|
|
|
|
: _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0),
|
2025-09-09 16:32:41 +02:00
|
|
|
|
_shutdown_init(false), sensors_lpp(200) { }
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
|
|
|
|
|
void poll() override {
|
|
|
|
|
|
if (_shutdown_init && !_task->isButtonPressed()) { // must wait for USR button to be released
|
|
|
|
|
|
_task->shutdown();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int render(DisplayDriver& display) override {
|
|
|
|
|
|
char tmp[80];
|
|
|
|
|
|
// node name
|
|
|
|
|
|
display.setTextSize(1);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2025-09-16 17:17:15 -07:00
|
|
|
|
char filtered_name[sizeof(_node_prefs->node_name)];
|
|
|
|
|
|
display.translateUTF8ToBlocks(filtered_name, _node_prefs->node_name, sizeof(filtered_name));
|
|
|
|
|
|
display.setCursor(0, 0);
|
|
|
|
|
|
display.print(filtered_name);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
|
|
|
|
|
// battery voltage
|
|
|
|
|
|
renderBatteryIndicator(display, _task->getBattMilliVolts());
|
|
|
|
|
|
|
|
|
|
|
|
// curr page indicator
|
|
|
|
|
|
int y = 14;
|
2025-09-28 09:43:28 +02:00
|
|
|
|
int x = display.width() / 2 - 5 * (HomePage::Count-1);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
for (uint8_t i = 0; i < HomePage::Count; i++, x += 10) {
|
|
|
|
|
|
if (i == _page) {
|
|
|
|
|
|
display.fillRect(x-1, y-1, 3, 3);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
display.fillRect(x, y, 1, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
|
if (_page == HomePage::CLOCK) {
|
|
|
|
|
|
uint32_t unix_ts = _rtc->getCurrentTime();
|
|
|
|
|
|
if (unix_ts < 1000000000UL) {
|
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setTextSize(1);
|
|
|
|
|
|
display.drawTextCentered(display.width() / 2, 25, "! No time sync");
|
2026-05-10 15:18:35 +02:00
|
|
|
|
display.drawTextCentered(display.width() / 2, 40, "Enable GPS or");
|
|
|
|
|
|
display.drawTextCentered(display.width() / 2, 51, "connect app");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
int8_t tz = _node_prefs ? _node_prefs->tz_offset_hours : 0;
|
|
|
|
|
|
unix_ts += (int32_t)tz * 3600;
|
|
|
|
|
|
time_t t = (time_t)unix_ts;
|
|
|
|
|
|
struct tm* ti = gmtime(&t);
|
|
|
|
|
|
|
|
|
|
|
|
char buf[24];
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-10 15:18:35 +02:00
|
|
|
|
display.setTextSize(2);
|
|
|
|
|
|
sprintf(buf, "%02d:%02d:%02d", ti->tm_hour, ti->tm_min, ti->tm_sec);
|
|
|
|
|
|
display.drawTextCentered(display.width() / 2, 18, buf);
|
|
|
|
|
|
|
|
|
|
|
|
display.setTextSize(1);
|
|
|
|
|
|
const char* wd[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
|
|
|
|
|
|
const char* mo[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
|
|
|
|
|
|
sprintf(buf, "%s %d %s %d", wd[ti->tm_wday], ti->tm_mday, mo[ti->tm_mon], 1900 + ti->tm_year);
|
|
|
|
|
|
display.drawTextCentered(display.width() / 2, 40, buf);
|
|
|
|
|
|
|
|
|
|
|
|
if (tz >= 0) sprintf(buf, "UTC+%d", (int)tz);
|
|
|
|
|
|
else sprintf(buf, "UTC%d", (int)tz);
|
|
|
|
|
|
display.drawTextCentered(display.width() / 2, 54, buf);
|
|
|
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
|
} else if (_page == HomePage::RECENT) {
|
2025-08-16 18:13:50 +02:00
|
|
|
|
the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
int y = 20;
|
2025-08-16 18:13:50 +02:00
|
|
|
|
for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) {
|
2025-08-08 20:01:31 +10:00
|
|
|
|
auto a = &recent[i];
|
|
|
|
|
|
if (a->name[0] == 0) continue; // empty slot
|
|
|
|
|
|
int secs = _rtc->getCurrentTime() - a->recv_timestamp;
|
|
|
|
|
|
if (secs < 60) {
|
|
|
|
|
|
sprintf(tmp, "%ds", secs);
|
|
|
|
|
|
} else if (secs < 60*60) {
|
|
|
|
|
|
sprintf(tmp, "%dm", secs / 60);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
sprintf(tmp, "%dh", secs / (60*60));
|
|
|
|
|
|
}
|
2025-09-16 17:17:15 -07:00
|
|
|
|
|
|
|
|
|
|
int timestamp_width = display.getTextWidth(tmp);
|
|
|
|
|
|
int max_name_width = display.width() - timestamp_width - 1;
|
|
|
|
|
|
|
|
|
|
|
|
char filtered_recent_name[sizeof(a->name)];
|
|
|
|
|
|
display.translateUTF8ToBlocks(filtered_recent_name, a->name, sizeof(filtered_recent_name));
|
|
|
|
|
|
display.drawTextEllipsized(0, y, max_name_width, filtered_recent_name);
|
|
|
|
|
|
display.setCursor(display.width() - timestamp_width - 1, y);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
display.print(tmp);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (_page == HomePage::RADIO) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
display.setTextSize(1);
|
|
|
|
|
|
// freq / sf
|
|
|
|
|
|
display.setCursor(0, 20);
|
|
|
|
|
|
sprintf(tmp, "FQ: %06.3f SF: %d", _node_prefs->freq, _node_prefs->sf);
|
|
|
|
|
|
display.print(tmp);
|
|
|
|
|
|
|
|
|
|
|
|
display.setCursor(0, 31);
|
|
|
|
|
|
sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr);
|
|
|
|
|
|
display.print(tmp);
|
|
|
|
|
|
|
|
|
|
|
|
// tx power, noise floor
|
|
|
|
|
|
display.setCursor(0, 42);
|
|
|
|
|
|
sprintf(tmp, "TX: %ddBm", _node_prefs->tx_power_dbm);
|
|
|
|
|
|
display.print(tmp);
|
|
|
|
|
|
display.setCursor(0, 53);
|
|
|
|
|
|
sprintf(tmp, "Noise floor: %d", radio_driver.getNoiseFloor());
|
|
|
|
|
|
display.print(tmp);
|
|
|
|
|
|
} else if (_page == HomePage::BLUETOOTH) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-11 16:52:24 +02:00
|
|
|
|
display.drawXbm((display.width() - 32) / 2, 14,
|
2025-09-04 21:45:42 +02:00
|
|
|
|
_task->isSerialEnabled() ? bluetooth_on : bluetooth_off,
|
2025-08-08 20:01:31 +10:00
|
|
|
|
32, 32);
|
|
|
|
|
|
display.setTextSize(1);
|
2026-05-11 16:52:24 +02:00
|
|
|
|
if (_task->isSerialEnabled() && !_task->hasConnection() && the_mesh.getBLEPin() != 0) {
|
|
|
|
|
|
char pin_buf[16];
|
|
|
|
|
|
snprintf(pin_buf, sizeof(pin_buf), "PIN: %d", the_mesh.getBLEPin());
|
|
|
|
|
|
display.drawTextCentered(display.width() / 2, 49, pin_buf);
|
|
|
|
|
|
}
|
|
|
|
|
|
display.drawTextCentered(display.width() / 2, 57, "toggle: " PRESS_LABEL);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
} else if (_page == HomePage::ADVERT) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
display.drawXbm((display.width() - 32) / 2, 18, advert_icon, 32, 32);
|
|
|
|
|
|
display.drawTextCentered(display.width() / 2, 64 - 11, "advert: " PRESS_LABEL);
|
2025-09-28 09:43:28 +02:00
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
2025-09-23 10:39:43 +02:00
|
|
|
|
} else if (_page == HomePage::GPS) {
|
|
|
|
|
|
LocationProvider* nmea = sensors.getLocationProvider();
|
2025-11-28 11:11:13 +01:00
|
|
|
|
char buf[50];
|
2025-09-23 10:39:43 +02:00
|
|
|
|
int y = 18;
|
2025-11-28 11:11:13 +01:00
|
|
|
|
bool gps_state = _task->getGPSState();
|
|
|
|
|
|
#ifdef PIN_GPS_SWITCH
|
|
|
|
|
|
bool hw_gps_state = digitalRead(PIN_GPS_SWITCH);
|
|
|
|
|
|
if (gps_state != hw_gps_state) {
|
|
|
|
|
|
strcpy(buf, gps_state ? "gps off(hw)" : "gps off(sw)");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
strcpy(buf, gps_state ? "gps on" : "gps off");
|
|
|
|
|
|
}
|
|
|
|
|
|
#else
|
|
|
|
|
|
strcpy(buf, gps_state ? "gps on" : "gps off");
|
|
|
|
|
|
#endif
|
|
|
|
|
|
display.drawTextLeftAlign(0, y, buf);
|
2025-09-23 10:39:43 +02:00
|
|
|
|
if (nmea == NULL) {
|
|
|
|
|
|
y = y + 12;
|
2025-09-30 09:21:12 +02:00
|
|
|
|
display.drawTextLeftAlign(0, y, "Can't access GPS");
|
2025-09-23 10:39:43 +02:00
|
|
|
|
} else {
|
|
|
|
|
|
strcpy(buf, nmea->isValid()?"fix":"no fix");
|
2025-09-30 09:21:12 +02:00
|
|
|
|
display.drawTextRightAlign(display.width()-1, y, buf);
|
2025-09-23 10:39:43 +02:00
|
|
|
|
y = y + 12;
|
2025-09-30 09:21:12 +02:00
|
|
|
|
display.drawTextLeftAlign(0, y, "sat");
|
2025-09-23 10:39:43 +02:00
|
|
|
|
sprintf(buf, "%d", nmea->satellitesCount());
|
2025-09-30 09:21:12 +02:00
|
|
|
|
display.drawTextRightAlign(display.width()-1, y, buf);
|
2025-09-23 10:39:43 +02:00
|
|
|
|
y = y + 12;
|
2025-09-30 09:21:12 +02:00
|
|
|
|
display.drawTextLeftAlign(0, y, "pos");
|
2025-09-23 10:39:43 +02:00
|
|
|
|
sprintf(buf, "%.4f %.4f",
|
|
|
|
|
|
nmea->getLatitude()/1000000., nmea->getLongitude()/1000000.);
|
2025-09-30 09:21:12 +02:00
|
|
|
|
display.drawTextRightAlign(display.width()-1, y, buf);
|
2025-09-23 10:39:43 +02:00
|
|
|
|
y = y + 12;
|
2025-09-30 09:21:12 +02:00
|
|
|
|
display.drawTextLeftAlign(0, y, "alt");
|
2025-09-23 10:39:43 +02:00
|
|
|
|
sprintf(buf, "%.2f", nmea->getAltitude()/1000.);
|
2025-09-30 09:21:12 +02:00
|
|
|
|
display.drawTextRightAlign(display.width()-1, y, buf);
|
2025-09-23 10:39:43 +02:00
|
|
|
|
y = y + 12;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2025-09-09 16:32:41 +02:00
|
|
|
|
#if UI_SENSORS_PAGE == 1
|
2025-09-05 15:20:52 +02:00
|
|
|
|
} else if (_page == HomePage::SENSORS) {
|
|
|
|
|
|
int y = 18;
|
|
|
|
|
|
refresh_sensors();
|
2025-09-09 16:32:41 +02:00
|
|
|
|
char buf[30];
|
|
|
|
|
|
char name[30];
|
|
|
|
|
|
LPPReader r(sensors_lpp.getBuffer(), sensors_lpp.getSize());
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < sensors_scroll_offset; i++) {
|
|
|
|
|
|
uint8_t channel, type;
|
|
|
|
|
|
r.readHeader(channel, type);
|
|
|
|
|
|
r.skipData(type);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < (sensors_scroll?UI_RECENT_LIST_SIZE:sensors_nb); i++) {
|
|
|
|
|
|
uint8_t channel, type;
|
|
|
|
|
|
if (!r.readHeader(channel, type)) { // reached end, reset
|
|
|
|
|
|
r.reset();
|
|
|
|
|
|
r.readHeader(channel, type);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-05 15:20:52 +02:00
|
|
|
|
display.setCursor(0, y);
|
2025-09-09 16:32:41 +02:00
|
|
|
|
float v;
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
|
case LPP_GPS: // GPS
|
|
|
|
|
|
float lat, lon, alt;
|
|
|
|
|
|
r.readGPS(lat, lon, alt);
|
|
|
|
|
|
strcpy(name, "gps"); sprintf(buf, "%.4f %.4f", lat, lon);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case LPP_VOLTAGE:
|
|
|
|
|
|
r.readVoltage(v);
|
|
|
|
|
|
strcpy(name, "voltage"); sprintf(buf, "%6.2f", v);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case LPP_CURRENT:
|
|
|
|
|
|
r.readCurrent(v);
|
|
|
|
|
|
strcpy(name, "current"); sprintf(buf, "%.3f", v);
|
2025-09-05 15:20:52 +02:00
|
|
|
|
break;
|
2025-09-09 16:32:41 +02:00
|
|
|
|
case LPP_TEMPERATURE:
|
|
|
|
|
|
r.readTemperature(v);
|
|
|
|
|
|
strcpy(name, "temperature"); sprintf(buf, "%.2f", v);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case LPP_RELATIVE_HUMIDITY:
|
|
|
|
|
|
r.readRelativeHumidity(v);
|
|
|
|
|
|
strcpy(name, "humidity"); sprintf(buf, "%.2f", v);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case LPP_BAROMETRIC_PRESSURE:
|
|
|
|
|
|
r.readPressure(v);
|
|
|
|
|
|
strcpy(name, "pressure"); sprintf(buf, "%.2f", v);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case LPP_ALTITUDE:
|
|
|
|
|
|
r.readAltitude(v);
|
|
|
|
|
|
strcpy(name, "altitude"); sprintf(buf, "%.0f", v);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case LPP_POWER:
|
|
|
|
|
|
r.readPower(v);
|
|
|
|
|
|
strcpy(name, "power"); sprintf(buf, "%6.2f", v);
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
r.skipData(type);
|
|
|
|
|
|
strcpy(name, "unk"); sprintf(buf, "");
|
2025-09-05 15:20:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
display.setCursor(0, y);
|
2025-09-09 16:32:41 +02:00
|
|
|
|
display.print(name);
|
2025-09-05 15:20:52 +02:00
|
|
|
|
display.setCursor(
|
|
|
|
|
|
display.width()-display.getTextWidth(buf)-1, y
|
|
|
|
|
|
);
|
|
|
|
|
|
display.print(buf);
|
|
|
|
|
|
y = y + 12;
|
|
|
|
|
|
}
|
2025-09-09 16:32:41 +02:00
|
|
|
|
if (sensors_scroll) sensors_scroll_offset = (sensors_scroll_offset+1)%sensors_nb;
|
|
|
|
|
|
else sensors_scroll_offset = 0;
|
|
|
|
|
|
#endif
|
2026-05-10 15:18:35 +02:00
|
|
|
|
} else if (_page == HomePage::SETTINGS) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-10 15:18:35 +02:00
|
|
|
|
display.setTextSize(1);
|
2026-05-11 16:52:24 +02:00
|
|
|
|
display.drawTextCentered(display.width() / 2, 30, "Settings");
|
|
|
|
|
|
display.drawTextCentered(display.width() / 2, 46, PRESS_LABEL " to open");
|
2026-05-10 18:45:35 +02:00
|
|
|
|
} else if (_page == HomePage::QUICK_MSG) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-10 18:45:35 +02:00
|
|
|
|
display.setTextSize(1);
|
2026-05-11 19:11:19 +02:00
|
|
|
|
display.drawTextCentered(display.width() / 2, 22, "Messages");
|
|
|
|
|
|
int total_unread = _task->getMsgCount() + _task->getChannelUnreadCount();
|
2026-05-11 19:27:16 +02:00
|
|
|
|
// getMsgCount() already includes room msgs via offline_queue_len; avoid double-count
|
2026-05-11 19:11:19 +02:00
|
|
|
|
if (total_unread > 0) {
|
|
|
|
|
|
char badge[20];
|
|
|
|
|
|
snprintf(badge, sizeof(badge), "%d unread", total_unread);
|
|
|
|
|
|
display.drawTextCentered(display.width() / 2, 35, badge);
|
|
|
|
|
|
}
|
|
|
|
|
|
display.drawTextCentered(display.width() / 2, 50, PRESS_LABEL " to open");
|
2025-08-08 20:01:31 +10:00
|
|
|
|
} else if (_page == HomePage::SHUTDOWN) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
display.setTextSize(1);
|
|
|
|
|
|
if (_shutdown_init) {
|
2025-08-23 14:45:18 +10:00
|
|
|
|
display.drawTextCentered(display.width() / 2, 34, "hibernating...");
|
2025-08-08 20:01:31 +10:00
|
|
|
|
} else {
|
|
|
|
|
|
display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32);
|
2025-10-16 17:33:22 +11:00
|
|
|
|
display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate:" PRESS_LABEL);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-10 15:18:35 +02:00
|
|
|
|
return (_page == HomePage::CLOCK) ? 1000 : 5000;
|
2025-08-08 20:01:31 +10:00
|
|
|
|
}
|
2025-03-10 22:42:52 +01:00
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
|
bool handleInput(char c) override {
|
2025-09-03 16:28:58 +10:00
|
|
|
|
if (c == KEY_LEFT || c == KEY_PREV) {
|
2025-08-08 20:01:31 +10:00
|
|
|
|
_page = (_page + HomePage::Count - 1) % HomePage::Count;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2025-09-03 16:28:58 +10:00
|
|
|
|
if (c == KEY_NEXT || c == KEY_RIGHT) {
|
2025-08-08 20:01:31 +10:00
|
|
|
|
_page = (_page + 1) % HomePage::Count;
|
|
|
|
|
|
if (_page == HomePage::RECENT) {
|
|
|
|
|
|
_task->showAlert("Recent adverts", 800);
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_ENTER && _page == HomePage::BLUETOOTH) {
|
|
|
|
|
|
if (_task->isSerialEnabled()) { // toggle Bluetooth on/off
|
|
|
|
|
|
_task->disableSerial();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
_task->enableSerial();
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_ENTER && _page == HomePage::ADVERT) {
|
2025-09-17 08:53:50 +08:00
|
|
|
|
_task->notify(UIEventType::ack);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
if (the_mesh.advert()) {
|
|
|
|
|
|
_task->showAlert("Advert sent!", 1000);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
_task->showAlert("Advert failed..", 1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2025-09-28 09:43:28 +02:00
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
2025-09-23 10:39:43 +02:00
|
|
|
|
if (c == KEY_ENTER && _page == HomePage::GPS) {
|
|
|
|
|
|
_task->toggleGPS();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2025-09-09 16:32:41 +02:00
|
|
|
|
#if UI_SENSORS_PAGE == 1
|
2025-09-05 15:32:02 +02:00
|
|
|
|
if (c == KEY_ENTER && _page == HomePage::SENSORS) {
|
|
|
|
|
|
_task->toggleGPS();
|
2025-09-05 15:35:04 +02:00
|
|
|
|
next_sensors_refresh=0;
|
2025-09-05 15:32:02 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2025-09-09 16:32:41 +02:00
|
|
|
|
#endif
|
2026-05-10 15:18:35 +02:00
|
|
|
|
if (c == KEY_ENTER && _page == HomePage::SETTINGS) {
|
|
|
|
|
|
_task->gotoSettingsScreen();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-05-10 18:45:35 +02:00
|
|
|
|
if (c == KEY_ENTER && _page == HomePage::QUICK_MSG) {
|
|
|
|
|
|
_task->gotoQuickMsgScreen();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
|
if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) {
|
|
|
|
|
|
_shutdown_init = true; // need to wait for button to be released
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class MsgPreviewScreen : public UIScreen {
|
|
|
|
|
|
UITask* _task;
|
|
|
|
|
|
mesh::RTCClock* _rtc;
|
|
|
|
|
|
|
|
|
|
|
|
struct MsgEntry {
|
|
|
|
|
|
uint32_t timestamp;
|
|
|
|
|
|
char origin[62];
|
|
|
|
|
|
char msg[78];
|
|
|
|
|
|
};
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
#define MAX_UNREAD_MSGS 32
|
2025-08-08 20:01:31 +10:00
|
|
|
|
int num_unread;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
int head;
|
|
|
|
|
|
int _view_offset; // 0=newest, num_unread-1=oldest
|
|
|
|
|
|
bool _fullscreen;
|
|
|
|
|
|
int _fs_scroll; // line scroll offset in fullscreen
|
2025-08-08 20:01:31 +10:00
|
|
|
|
MsgEntry unread[MAX_UNREAD_MSGS];
|
|
|
|
|
|
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
static const int FS_CHARS = 21; // chars per line at text size 1, 128px wide
|
|
|
|
|
|
static const int FS_LINE_H = 9;
|
|
|
|
|
|
static const int FS_START_Y = 12;
|
|
|
|
|
|
static const int FS_VISIBLE = (64 - FS_START_Y) / FS_LINE_H; // 5 lines
|
|
|
|
|
|
|
|
|
|
|
|
// word-wrap msg into lines; returns line count
|
|
|
|
|
|
int wrapLines(const char* text, char out[][FS_CHARS + 1], int max_lines) const {
|
|
|
|
|
|
int count = 0;
|
|
|
|
|
|
const char* p = text;
|
|
|
|
|
|
while (*p && count < max_lines) {
|
|
|
|
|
|
int len = strlen(p);
|
|
|
|
|
|
if (len <= FS_CHARS) {
|
|
|
|
|
|
strncpy(out[count++], p, FS_CHARS);
|
|
|
|
|
|
out[count-1][len] = '\0';
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
int brk = FS_CHARS;
|
|
|
|
|
|
for (int i = FS_CHARS - 1; i > 0; i--) {
|
|
|
|
|
|
if (p[i] == ' ') { brk = i; break; }
|
|
|
|
|
|
}
|
|
|
|
|
|
strncpy(out[count], p, brk);
|
|
|
|
|
|
out[count++][brk] = '\0';
|
|
|
|
|
|
p += brk + (p[brk] == ' ' ? 1 : 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
return count;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
|
public:
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
MsgPreviewScreen(UITask* task, mesh::RTCClock* rtc)
|
|
|
|
|
|
: _task(task), _rtc(rtc), num_unread(0), head(MAX_UNREAD_MSGS - 1),
|
|
|
|
|
|
_view_offset(0), _fullscreen(false), _fs_scroll(0) {}
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
|
|
|
|
|
void addPreview(uint8_t path_len, const char* from_name, const char* msg) {
|
2026-01-26 16:41:08 +11:00
|
|
|
|
head = (head + 1) % MAX_UNREAD_MSGS;
|
|
|
|
|
|
if (num_unread < MAX_UNREAD_MSGS) num_unread++;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
_view_offset = 0;
|
|
|
|
|
|
_fullscreen = false;
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
2026-01-26 16:41:08 +11:00
|
|
|
|
auto p = &unread[head];
|
2025-08-08 20:01:31 +10:00
|
|
|
|
p->timestamp = _rtc->getCurrentTime();
|
|
|
|
|
|
if (path_len == 0xFF) {
|
|
|
|
|
|
sprintf(p->origin, "(D) %s:", from_name);
|
|
|
|
|
|
} else {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
sprintf(p->origin, "(%d) %s:", (uint32_t)path_len, from_name);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
}
|
|
|
|
|
|
StrHelper::strncpy(p->msg, msg, sizeof(p->msg));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int render(DisplayDriver& display) override {
|
|
|
|
|
|
display.setTextSize(1);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
int msg_idx = (head - _view_offset + MAX_UNREAD_MSGS) % MAX_UNREAD_MSGS;
|
|
|
|
|
|
auto p = &unread[msg_idx];
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
2025-09-16 17:17:15 -07:00
|
|
|
|
char filtered_origin[sizeof(p->origin)];
|
|
|
|
|
|
display.translateUTF8ToBlocks(filtered_origin, p->origin, sizeof(filtered_origin));
|
|
|
|
|
|
char filtered_msg[sizeof(p->msg)];
|
|
|
|
|
|
display.translateUTF8ToBlocks(filtered_msg, p->msg, sizeof(filtered_msg));
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
2026-05-11 16:52:24 +02:00
|
|
|
|
// header: origin + counter, tekst + separator
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
char counter[8];
|
|
|
|
|
|
snprintf(counter, sizeof(counter), "%d/%d", _view_offset + 1, num_unread);
|
|
|
|
|
|
int counter_w = display.getTextWidth(counter);
|
2026-05-11 16:52:24 +02:00
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
display.drawTextEllipsized(2, 1, display.width() - counter_w - 6, filtered_origin);
|
|
|
|
|
|
display.setCursor(display.width() - counter_w - 2, 1);
|
|
|
|
|
|
display.print(counter);
|
2026-05-11 16:52:24 +02:00
|
|
|
|
display.fillRect(0, 10, display.width(), 1);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
|
|
|
|
|
|
if (_fullscreen) {
|
|
|
|
|
|
char lines[10][FS_CHARS + 1];
|
|
|
|
|
|
int line_count = wrapLines(filtered_msg, lines, 10);
|
|
|
|
|
|
int max_scroll = (line_count > FS_VISIBLE) ? line_count - FS_VISIBLE : 0;
|
|
|
|
|
|
if (_fs_scroll > max_scroll) _fs_scroll = max_scroll;
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < FS_VISIBLE && (_fs_scroll + i) < line_count; i++) {
|
|
|
|
|
|
display.setCursor(0, FS_START_Y + i * FS_LINE_H);
|
|
|
|
|
|
display.print(lines[_fs_scroll + i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_fs_scroll > 0) {
|
|
|
|
|
|
display.setCursor(display.width() - 6, FS_START_Y);
|
|
|
|
|
|
display.print("^");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_fs_scroll < max_scroll) {
|
|
|
|
|
|
display.setCursor(display.width() - 6, FS_START_Y + (FS_VISIBLE - 1) * FS_LINE_H);
|
|
|
|
|
|
display.print("v");
|
|
|
|
|
|
}
|
2026-05-11 20:05:44 +02:00
|
|
|
|
if (_view_offset < num_unread - 1) {
|
|
|
|
|
|
display.setCursor(0, 56);
|
|
|
|
|
|
display.print("<");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_view_offset > 0) {
|
|
|
|
|
|
display.setCursor(display.width() - 6, 56);
|
|
|
|
|
|
display.print(">");
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
} else {
|
|
|
|
|
|
display.setCursor(0, 13);
|
|
|
|
|
|
display.printWordWrap(filtered_msg, display.width());
|
|
|
|
|
|
|
|
|
|
|
|
// nav hints
|
|
|
|
|
|
if (_view_offset < num_unread - 1) {
|
|
|
|
|
|
display.setCursor(display.width() - 6, 13);
|
|
|
|
|
|
display.print("^");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_view_offset > 0) {
|
|
|
|
|
|
display.setCursor(display.width() - 6, 55);
|
|
|
|
|
|
display.print("v");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// timestamp
|
|
|
|
|
|
char tmp[10];
|
|
|
|
|
|
int secs = _rtc->getCurrentTime() - p->timestamp;
|
|
|
|
|
|
if (secs < 60) snprintf(tmp, sizeof(tmp), "%ds", secs);
|
|
|
|
|
|
else if (secs < 3600) snprintf(tmp, sizeof(tmp), "%dm", secs / 60);
|
|
|
|
|
|
else snprintf(tmp, sizeof(tmp), "%dh", secs / 3600);
|
|
|
|
|
|
display.setCursor(0, 56);
|
|
|
|
|
|
display.print(tmp);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#if AUTO_OFF_MILLIS==0
|
|
|
|
|
|
return 10000;
|
2025-09-03 18:17:37 +02:00
|
|
|
|
#else
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
return 1000;
|
2025-09-03 18:17:37 +02:00
|
|
|
|
#endif
|
2025-08-08 20:01:31 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool handleInput(char c) override {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
if (_fullscreen) {
|
|
|
|
|
|
if (c == KEY_UP) { if (_fs_scroll > 0) _fs_scroll--; return true; }
|
|
|
|
|
|
if (c == KEY_DOWN) { _fs_scroll++; return true; }
|
2026-05-11 20:05:44 +02:00
|
|
|
|
if (c == KEY_LEFT) {
|
|
|
|
|
|
if (_view_offset < num_unread - 1) { _view_offset++; _fs_scroll = 0; }
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_RIGHT) {
|
|
|
|
|
|
if (_view_offset > 0) { _view_offset--; _fs_scroll = 0; }
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
if (c == KEY_ENTER || c == KEY_CANCEL) { _fullscreen = false; return true; }
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_UP || c == KEY_LEFT) {
|
|
|
|
|
|
if (_view_offset < num_unread - 1) _view_offset++;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_DOWN || c == KEY_RIGHT) {
|
|
|
|
|
|
if (_view_offset > 0) _view_offset--;
|
2025-08-08 20:01:31 +10:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_ENTER) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
_fullscreen = true;
|
|
|
|
|
|
_fs_scroll = 0;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (c == KEY_CANCEL) {
|
|
|
|
|
|
num_unread = 0;
|
2025-08-08 20:01:31 +10:00
|
|
|
|
_task->gotoHomeScreen();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-03-04 23:09:43 +11:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-06-19 17:26:58 +02:00
|
|
|
|
void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs) {
|
2025-03-16 13:42:36 +11:00
|
|
|
|
_display = display;
|
2025-06-19 17:26:58 +02:00
|
|
|
|
_sensors = sensors;
|
2026-05-10 15:18:35 +02:00
|
|
|
|
_node_prefs = node_prefs;
|
|
|
|
|
|
uint32_t aoff = autoOffMillis();
|
|
|
|
|
|
_auto_off = millis() + (aoff > 0 ? aoff : AUTO_OFF_MILLIS);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
|
|
|
|
|
#if defined(PIN_USER_BTN)
|
|
|
|
|
|
user_btn.begin();
|
|
|
|
|
|
#endif
|
2025-09-01 17:11:55 +10:00
|
|
|
|
#if defined(PIN_USER_BTN_ANA)
|
|
|
|
|
|
analog_btn.begin();
|
|
|
|
|
|
#endif
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
2025-03-10 17:11:55 +01:00
|
|
|
|
if (_display != NULL) {
|
|
|
|
|
|
_display->turnOn();
|
|
|
|
|
|
}
|
2025-04-08 22:58:17 +12:00
|
|
|
|
|
2025-05-20 11:52:55 +10:00
|
|
|
|
#ifdef PIN_BUZZER
|
2025-11-20 18:55:39 -08:00
|
|
|
|
buzzer.quiet(_node_prefs->buzzer_quiet);
|
2026-05-10 15:18:35 +02:00
|
|
|
|
buzzer.begin();
|
2025-05-20 11:52:55 +10:00
|
|
|
|
#endif
|
2025-05-27 19:10:56 -07:00
|
|
|
|
|
2025-09-07 15:16:15 +08:00
|
|
|
|
#ifdef PIN_VIBRATION
|
|
|
|
|
|
vibration.begin();
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
2026-05-11 16:52:24 +02:00
|
|
|
|
// Set default quick message if slot 0 is empty (first boot)
|
|
|
|
|
|
if (_node_prefs && _node_prefs->custom_msgs[0][0] == '\0') {
|
|
|
|
|
|
strncpy(_node_prefs->custom_msgs[0], "OK", sizeof(_node_prefs->custom_msgs[0]) - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-04 21:33:48 +10:00
|
|
|
|
ui_started_at = millis();
|
2025-08-08 20:01:31 +10:00
|
|
|
|
_alert_expiry = 0;
|
2026-05-10 15:18:35 +02:00
|
|
|
|
_batt_mv = AbstractUITask::getBattMilliVolts(); // seed EMA with first reading
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
|
|
|
|
|
splash = new SplashScreen(this);
|
|
|
|
|
|
home = new HomeScreen(this, &rtc_clock, sensors, node_prefs);
|
|
|
|
|
|
msg_preview = new MsgPreviewScreen(this, &rtc_clock);
|
2026-05-10 15:18:35 +02:00
|
|
|
|
settings = new SettingsScreen(this);
|
2026-05-10 18:45:35 +02:00
|
|
|
|
quick_msg = new QuickMsgScreen(this);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
setCurrScreen(splash);
|
2026-05-10 15:18:35 +02:00
|
|
|
|
|
|
|
|
|
|
applyBrightness();
|
2025-08-08 20:01:31 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 22:12:29 +02:00
|
|
|
|
void UITask::gotoSettingsScreen() {
|
|
|
|
|
|
((SettingsScreen*)settings)->markClean();
|
|
|
|
|
|
setCurrScreen(settings);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 18:45:35 +02:00
|
|
|
|
void UITask::gotoQuickMsgScreen() {
|
|
|
|
|
|
((QuickMsgScreen*)quick_msg)->reset();
|
|
|
|
|
|
setCurrScreen(quick_msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
void UITask::addChannelMsg(uint8_t channel_idx, const char* text) {
|
2026-05-11 18:56:26 +02:00
|
|
|
|
_last_notif_ch_idx = (int)channel_idx;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
|
((QuickMsgScreen*)quick_msg)->addChannelMsg(channel_idx, text);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 19:11:19 +02:00
|
|
|
|
int UITask::getChannelUnreadCount() const {
|
|
|
|
|
|
return ((QuickMsgScreen*)quick_msg)->getTotalChannelUnread();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
|
void UITask::showAlert(const char* text, int duration_millis) {
|
|
|
|
|
|
strcpy(_alert, text);
|
|
|
|
|
|
_alert_expiry = millis() + duration_millis;
|
2025-05-20 11:52:55 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-17 08:53:50 +08:00
|
|
|
|
void UITask::notify(UIEventType t) {
|
2025-05-20 11:52:55 +10:00
|
|
|
|
#if defined(PIN_BUZZER)
|
2025-09-17 08:53:50 +08:00
|
|
|
|
switch(t){
|
2025-05-20 19:33:21 +12:00
|
|
|
|
case UIEventType::contactMessage:
|
2025-05-20 19:09:49 +12:00
|
|
|
|
// gemini's pick
|
|
|
|
|
|
buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7");
|
|
|
|
|
|
break;
|
2026-05-11 18:56:26 +02:00
|
|
|
|
case UIEventType::channelMessage: {
|
2026-05-11 21:17:31 +02:00
|
|
|
|
bool play = false;
|
|
|
|
|
|
bool force = false;
|
2026-05-11 18:56:26 +02:00
|
|
|
|
if (_last_notif_ch_idx >= 0 && _node_prefs) {
|
|
|
|
|
|
uint64_t mask = 1ULL << _last_notif_ch_idx;
|
|
|
|
|
|
if (_node_prefs->ch_notif_override & mask) {
|
2026-05-11 21:17:31 +02:00
|
|
|
|
if (!(_node_prefs->ch_notif_muted & mask)) { play = true; force = true; } // state 2: force-on
|
|
|
|
|
|
// state 1: muted — play stays false
|
2026-05-11 18:56:26 +02:00
|
|
|
|
} else {
|
2026-05-11 21:17:31 +02:00
|
|
|
|
play = !buzzer.isQuiet(); // state 0: follow global
|
2026-05-11 18:56:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
play = !buzzer.isQuiet();
|
|
|
|
|
|
}
|
|
|
|
|
|
_last_notif_ch_idx = -1;
|
2026-05-11 21:17:31 +02:00
|
|
|
|
if (play) {
|
|
|
|
|
|
if (force) buzzer.playForced("kerplop:d=16,o=6,b=120:32g#,32c#");
|
|
|
|
|
|
else buzzer.play("kerplop:d=16,o=6,b=120:32g#,32c#");
|
|
|
|
|
|
}
|
2025-05-23 17:58:13 +12:00
|
|
|
|
break;
|
2026-05-11 18:56:26 +02:00
|
|
|
|
}
|
2025-05-30 22:55:53 -07:00
|
|
|
|
case UIEventType::ack:
|
2025-05-30 22:58:30 -07:00
|
|
|
|
buzzer.play("ack:d=32,o=8,b=120:c");
|
2025-05-30 22:55:53 -07:00
|
|
|
|
break;
|
2025-05-20 19:33:21 +12:00
|
|
|
|
case UIEventType::roomMessage:
|
|
|
|
|
|
case UIEventType::newContactMessage:
|
|
|
|
|
|
case UIEventType::none:
|
2025-05-20 19:09:49 +12:00
|
|
|
|
default:
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2025-05-20 11:52:55 +10:00
|
|
|
|
#endif
|
2025-03-10 17:11:55 +01:00
|
|
|
|
|
2025-09-07 15:16:15 +08:00
|
|
|
|
#ifdef PIN_VIBRATION
|
2025-09-17 08:53:50 +08:00
|
|
|
|
// Trigger vibration for all UI events except none
|
|
|
|
|
|
if (t != UIEventType::none) {
|
|
|
|
|
|
vibration.trigger();
|
|
|
|
|
|
}
|
2025-09-07 15:16:15 +08:00
|
|
|
|
#endif
|
2025-03-10 17:11:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-07 15:16:15 +08:00
|
|
|
|
|
2025-03-10 17:11:55 +01:00
|
|
|
|
void UITask::msgRead(int msgcount) {
|
|
|
|
|
|
_msgcount = msgcount;
|
|
|
|
|
|
if (msgcount == 0) {
|
2026-05-11 19:27:16 +02:00
|
|
|
|
_room_unread = 0;
|
2026-05-11 20:05:44 +02:00
|
|
|
|
if (curr == msg_preview) gotoHomeScreen();
|
2025-03-10 17:11:55 +01:00
|
|
|
|
}
|
2025-03-04 23:09:43 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 19:27:16 +02:00
|
|
|
|
void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount, uint8_t contact_type) {
|
2025-03-10 17:11:55 +01:00
|
|
|
|
_msgcount = msgcount;
|
2026-05-11 19:34:07 +02:00
|
|
|
|
if (contact_type == ADV_TYPE_ROOM && _room_unread < _msgcount) _room_unread++;
|
2025-03-10 17:11:55 +01:00
|
|
|
|
|
2026-05-11 17:19:03 +02:00
|
|
|
|
char alert_buf[80];
|
|
|
|
|
|
snprintf(alert_buf, sizeof(alert_buf), "Msg: %.20s", from_name);
|
|
|
|
|
|
showAlert(alert_buf, 3000);
|
2025-03-04 23:09:43 +11:00
|
|
|
|
|
2025-03-10 17:11:55 +01:00
|
|
|
|
if (_display != NULL) {
|
2025-11-03 21:25:31 +01:00
|
|
|
|
if (!_display->isOn() && !hasConnection()) {
|
|
|
|
|
|
_display->turnOn();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_display->isOn()) {
|
2026-05-11 17:19:03 +02:00
|
|
|
|
uint32_t aoff = autoOffMillis();
|
|
|
|
|
|
if (aoff > 0) _auto_off = millis() + aoff;
|
|
|
|
|
|
_next_refresh = 100;
|
2025-11-03 21:25:31 +01:00
|
|
|
|
}
|
2025-03-10 17:11:55 +01:00
|
|
|
|
}
|
2025-03-04 23:09:43 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-10 17:11:55 +01:00
|
|
|
|
void UITask::userLedHandler() {
|
|
|
|
|
|
#ifdef PIN_STATUS_LED
|
|
|
|
|
|
int cur_time = millis();
|
2025-09-03 17:22:11 +02:00
|
|
|
|
if (cur_time > next_led_change) {
|
|
|
|
|
|
if (led_state == 0) {
|
|
|
|
|
|
led_state = 1;
|
2025-03-10 17:11:55 +01:00
|
|
|
|
if (_msgcount > 0) {
|
2025-09-03 17:22:11 +02:00
|
|
|
|
last_led_increment = LED_ON_MSG_MILLIS;
|
2025-03-10 17:11:55 +01:00
|
|
|
|
} else {
|
2025-09-03 17:22:11 +02:00
|
|
|
|
last_led_increment = LED_ON_MILLIS;
|
2025-03-10 17:11:55 +01:00
|
|
|
|
}
|
2025-09-03 17:22:11 +02:00
|
|
|
|
next_led_change = cur_time + last_led_increment;
|
2025-03-10 17:11:55 +01:00
|
|
|
|
} else {
|
2025-09-03 17:22:11 +02:00
|
|
|
|
led_state = 0;
|
|
|
|
|
|
next_led_change = cur_time + LED_CYCLE_MILLIS - last_led_increment;
|
2025-03-10 17:11:55 +01:00
|
|
|
|
}
|
2025-11-20 10:58:14 +01:00
|
|
|
|
digitalWrite(PIN_STATUS_LED, led_state == LED_STATE_ON);
|
2025-03-10 17:11:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
|
void UITask::setCurrScreen(UIScreen* c) {
|
|
|
|
|
|
curr = c;
|
2025-09-03 17:22:11 +02:00
|
|
|
|
_next_refresh = 100;
|
2025-08-08 20:01:31 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-04 21:45:42 +02:00
|
|
|
|
/*
|
|
|
|
|
|
hardware-agnostic pre-shutdown activity should be done here
|
2025-05-27 11:07:51 +12:00
|
|
|
|
*/
|
|
|
|
|
|
void UITask::shutdown(bool restart){
|
2026-05-10 18:23:25 +02:00
|
|
|
|
the_mesh.saveRTCTime();
|
2025-05-27 11:07:51 +12:00
|
|
|
|
|
|
|
|
|
|
#ifdef PIN_BUZZER
|
|
|
|
|
|
/* note: we have a choice here -
|
|
|
|
|
|
we can do a blocking buzzer.loop() with non-deterministic consequences
|
|
|
|
|
|
or we can set a flag and delay the shutdown for a couple of seconds
|
|
|
|
|
|
while a non-blocking buzzer.loop() plays out in UITask::loop()
|
|
|
|
|
|
*/
|
|
|
|
|
|
buzzer.shutdown();
|
|
|
|
|
|
uint32_t buzzer_timer = millis(); // fail-safe shutdown
|
|
|
|
|
|
while (buzzer.isPlaying() && (millis() - 2500) < buzzer_timer)
|
|
|
|
|
|
buzzer.loop();
|
|
|
|
|
|
|
|
|
|
|
|
#endif // PIN_BUZZER
|
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
|
if (restart) {
|
2025-05-27 11:07:51 +12:00
|
|
|
|
_board->reboot();
|
2025-08-08 20:01:31 +10:00
|
|
|
|
} else {
|
|
|
|
|
|
_display->turnOff();
|
2025-11-22 02:06:44 +01:00
|
|
|
|
radio_driver.powerOff();
|
2025-05-27 11:07:51 +12:00
|
|
|
|
_board->powerOff();
|
2025-08-08 20:01:31 +10:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool UITask::isButtonPressed() const {
|
|
|
|
|
|
#ifdef PIN_USER_BTN
|
|
|
|
|
|
return user_btn.isPressed();
|
|
|
|
|
|
#else
|
|
|
|
|
|
return false;
|
|
|
|
|
|
#endif
|
2025-05-27 11:07:51 +12:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-10 17:11:55 +01:00
|
|
|
|
void UITask::loop() {
|
2025-08-08 20:01:31 +10:00
|
|
|
|
char c = 0;
|
2025-10-16 17:33:22 +11:00
|
|
|
|
#if UI_HAS_JOYSTICK
|
2025-08-08 20:01:31 +10:00
|
|
|
|
int ev = user_btn.check();
|
|
|
|
|
|
if (ev == BUTTON_EVENT_CLICK) {
|
2025-10-16 17:33:22 +11:00
|
|
|
|
c = checkDisplayOn(KEY_ENTER);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
2025-10-16 17:33:22 +11:00
|
|
|
|
c = handleLongPress(KEY_ENTER); // REVISIT: could be mapped to different key code
|
2025-08-08 20:01:31 +10:00
|
|
|
|
}
|
2026-05-10 15:18:35 +02:00
|
|
|
|
#if UI_HAS_JOYSTICK_UPDOWN
|
|
|
|
|
|
ev = joystick_up.check();
|
|
|
|
|
|
if (ev == BUTTON_EVENT_CLICK) {
|
|
|
|
|
|
c = checkDisplayOn(KEY_UP);
|
|
|
|
|
|
}
|
|
|
|
|
|
ev = joystick_down.check();
|
|
|
|
|
|
if (ev == BUTTON_EVENT_CLICK) {
|
|
|
|
|
|
c = checkDisplayOn(KEY_DOWN);
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2025-08-17 19:01:28 +10:00
|
|
|
|
ev = joystick_left.check();
|
|
|
|
|
|
if (ev == BUTTON_EVENT_CLICK) {
|
|
|
|
|
|
c = checkDisplayOn(KEY_LEFT);
|
|
|
|
|
|
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
|
|
|
|
|
c = handleLongPress(KEY_LEFT);
|
|
|
|
|
|
}
|
|
|
|
|
|
ev = joystick_right.check();
|
|
|
|
|
|
if (ev == BUTTON_EVENT_CLICK) {
|
|
|
|
|
|
c = checkDisplayOn(KEY_RIGHT);
|
|
|
|
|
|
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
|
|
|
|
|
c = handleLongPress(KEY_RIGHT);
|
|
|
|
|
|
}
|
2025-10-18 13:37:18 +02:00
|
|
|
|
ev = back_btn.check();
|
2026-05-10 15:18:35 +02:00
|
|
|
|
if (ev == BUTTON_EVENT_CLICK) {
|
|
|
|
|
|
c = checkDisplayOn(KEY_CANCEL);
|
|
|
|
|
|
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
2025-10-18 13:37:18 +02:00
|
|
|
|
c = handleTripleClick(KEY_SELECT);
|
|
|
|
|
|
}
|
2025-10-16 17:33:22 +11:00
|
|
|
|
#elif defined(PIN_USER_BTN)
|
|
|
|
|
|
int ev = user_btn.check();
|
|
|
|
|
|
if (ev == BUTTON_EVENT_CLICK) {
|
|
|
|
|
|
c = checkDisplayOn(KEY_NEXT);
|
|
|
|
|
|
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
|
|
|
|
|
c = handleLongPress(KEY_ENTER);
|
|
|
|
|
|
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
|
|
|
|
|
c = handleDoubleClick(KEY_PREV);
|
|
|
|
|
|
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
|
|
|
|
|
c = handleTripleClick(KEY_SELECT);
|
|
|
|
|
|
}
|
2025-08-17 19:01:28 +10:00
|
|
|
|
#endif
|
2025-09-01 17:11:55 +10:00
|
|
|
|
#if defined(PIN_USER_BTN_ANA)
|
2025-10-31 13:04:59 +00:00
|
|
|
|
if (abs(millis() - _analogue_pin_read_millis) > 10) {
|
|
|
|
|
|
ev = analog_btn.check();
|
|
|
|
|
|
if (ev == BUTTON_EVENT_CLICK) {
|
|
|
|
|
|
c = checkDisplayOn(KEY_NEXT);
|
|
|
|
|
|
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
|
|
|
|
|
c = handleLongPress(KEY_ENTER);
|
|
|
|
|
|
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
|
|
|
|
|
c = handleDoubleClick(KEY_PREV);
|
|
|
|
|
|
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
|
|
|
|
|
c = handleTripleClick(KEY_SELECT);
|
|
|
|
|
|
}
|
|
|
|
|
|
_analogue_pin_read_millis = millis();
|
2025-09-01 17:11:55 +10:00
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2025-11-28 10:33:19 +01:00
|
|
|
|
#if defined(BACKLIGHT_BTN)
|
2025-09-03 17:22:11 +02:00
|
|
|
|
if (millis() > next_backlight_btn_check) {
|
2025-09-02 11:43:48 +02:00
|
|
|
|
bool touch_state = digitalRead(PIN_BUTTON2);
|
2025-11-28 10:33:19 +01:00
|
|
|
|
#if defined(DISP_BACKLIGHT)
|
2025-09-02 11:43:48 +02:00
|
|
|
|
digitalWrite(DISP_BACKLIGHT, !touch_state);
|
2025-11-28 10:33:19 +01:00
|
|
|
|
#elif defined(EXP_PIN_BACKLIGHT)
|
|
|
|
|
|
expander.digitalWrite(EXP_PIN_BACKLIGHT, !touch_state);
|
|
|
|
|
|
#endif
|
2025-09-03 17:22:11 +02:00
|
|
|
|
next_backlight_btn_check = millis() + 300;
|
2025-09-02 11:43:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
|
|
|
|
|
if (c != 0 && curr) {
|
|
|
|
|
|
curr->handleInput(c);
|
2026-05-10 15:18:35 +02:00
|
|
|
|
{ uint32_t aoff = autoOffMillis(); if (aoff > 0) _auto_off = millis() + aoff; } // extend auto-off timer
|
2025-09-03 17:22:11 +02:00
|
|
|
|
_next_refresh = 100; // trigger refresh
|
2025-08-08 20:01:31 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-10 17:11:55 +01:00
|
|
|
|
userLedHandler();
|
2025-03-04 23:09:43 +11:00
|
|
|
|
|
2025-05-20 11:52:55 +10:00
|
|
|
|
#ifdef PIN_BUZZER
|
|
|
|
|
|
if (buzzer.isPlaying()) buzzer.loop();
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
|
if (curr) curr->poll();
|
|
|
|
|
|
|
2025-03-10 17:11:55 +01:00
|
|
|
|
if (_display != NULL && _display->isOn()) {
|
2025-08-08 20:01:31 +10:00
|
|
|
|
if (millis() >= _next_refresh && curr) {
|
2025-03-04 23:09:43 +11:00
|
|
|
|
_display->startFrame();
|
2025-08-08 20:01:31 +10:00
|
|
|
|
int delay_millis = curr->render(*_display);
|
2026-05-11 17:19:03 +02:00
|
|
|
|
if (millis() < _alert_expiry && curr == home) { // render alert only on home screen
|
2025-08-08 20:01:31 +10:00
|
|
|
|
_display->setTextSize(1);
|
|
|
|
|
|
int y = _display->height() / 3;
|
|
|
|
|
|
int p = _display->height() / 32;
|
|
|
|
|
|
_display->setColor(DisplayDriver::DARK);
|
|
|
|
|
|
_display->fillRect(p, y, _display->width() - p*2, y);
|
|
|
|
|
|
_display->setColor(DisplayDriver::LIGHT); // draw box border
|
|
|
|
|
|
_display->drawRect(p, y, _display->width() - p*2, y);
|
|
|
|
|
|
_display->drawTextCentered(_display->width() / 2, y + p*3, _alert);
|
|
|
|
|
|
_next_refresh = _alert_expiry; // will need refresh when alert is dismissed
|
|
|
|
|
|
} else {
|
|
|
|
|
|
_next_refresh = millis() + delay_millis;
|
|
|
|
|
|
}
|
2025-03-04 23:09:43 +11:00
|
|
|
|
_display->endFrame();
|
|
|
|
|
|
}
|
2025-09-03 18:17:37 +02:00
|
|
|
|
#if AUTO_OFF_MILLIS > 0
|
2026-05-10 15:18:35 +02:00
|
|
|
|
if (autoOffMillis() > 0 && millis() > _auto_off) {
|
2025-03-04 23:09:43 +11:00
|
|
|
|
_display->turnOff();
|
2026-05-10 18:01:59 +02:00
|
|
|
|
#ifdef PIN_LED
|
|
|
|
|
|
digitalWrite(PIN_LED, LOW); // turn off status LED with display to save power
|
|
|
|
|
|
#endif
|
2025-03-04 23:09:43 +11:00
|
|
|
|
}
|
2025-09-03 18:17:37 +02:00
|
|
|
|
#endif
|
2025-03-04 23:09:43 +11:00
|
|
|
|
}
|
2025-05-27 19:10:56 -07:00
|
|
|
|
|
2025-09-07 15:16:15 +08:00
|
|
|
|
#ifdef PIN_VIBRATION
|
|
|
|
|
|
vibration.loop();
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
|
if (millis() > next_batt_chck) {
|
2026-05-10 15:18:35 +02:00
|
|
|
|
uint16_t raw = AbstractUITask::getBattMilliVolts();
|
|
|
|
|
|
if (raw > 0) {
|
|
|
|
|
|
// EMA filter: alpha=0.2 (80% old, 20% new) — smooths ADC noise from uneven load
|
|
|
|
|
|
_batt_mv = (_batt_mv == 0) ? raw : (uint16_t)((_batt_mv * 4u + raw) / 5u);
|
|
|
|
|
|
}
|
|
|
|
|
|
uint16_t low_mv = _node_prefs ? _node_prefs->low_batt_mv : 0;
|
|
|
|
|
|
if (low_mv > 0 && _batt_mv > 0 && _batt_mv < low_mv) {
|
2025-08-30 23:09:01 +12:00
|
|
|
|
if (_display != NULL) {
|
|
|
|
|
|
_display->startFrame();
|
|
|
|
|
|
_display->setTextSize(2);
|
2026-05-10 15:18:35 +02:00
|
|
|
|
_display->setColor(DisplayDriver::LIGHT);
|
|
|
|
|
|
_display->drawTextCentered(_display->width() / 2, 16, "Low Battery");
|
|
|
|
|
|
_display->drawTextCentered(_display->width() / 2, 36, "Shutting down");
|
2025-08-30 23:09:01 +12:00
|
|
|
|
_display->endFrame();
|
2026-05-10 15:18:35 +02:00
|
|
|
|
delay(2000);
|
2025-08-30 23:09:01 +12:00
|
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
|
shutdown();
|
2025-05-27 19:10:56 -07:00
|
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
|
next_batt_chck = millis() + 8000;
|
2025-05-27 19:10:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
|
char UITask::checkDisplayOn(char c) {
|
2025-05-27 19:10:56 -07:00
|
|
|
|
if (_display != NULL) {
|
2025-08-08 20:01:31 +10:00
|
|
|
|
if (!_display->isOn()) {
|
2026-05-10 18:01:59 +02:00
|
|
|
|
_display->turnOn();
|
|
|
|
|
|
#ifdef PIN_LED
|
|
|
|
|
|
digitalWrite(PIN_LED, LOW); // ensure LED is off when waking display (userLedHandler takes over)
|
|
|
|
|
|
#endif
|
2025-08-08 20:01:31 +10:00
|
|
|
|
c = 0;
|
2025-05-27 19:10:56 -07:00
|
|
|
|
}
|
2026-05-10 15:18:35 +02:00
|
|
|
|
{ uint32_t aoff = autoOffMillis(); if (aoff > 0) _auto_off = millis() + aoff; } // extend auto-off timer
|
2025-08-08 20:01:31 +10:00
|
|
|
|
_next_refresh = 0; // trigger refresh
|
2025-05-27 19:10:56 -07:00
|
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
|
return c;
|
2025-05-27 19:10:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
|
char UITask::handleLongPress(char c) {
|
|
|
|
|
|
if (millis() - ui_started_at < 8000) { // long press in first 8 seconds since startup -> CLI/rescue
|
|
|
|
|
|
the_mesh.enterCLIRescue();
|
2026-05-11 18:56:26 +02:00
|
|
|
|
return 0;
|
2025-05-29 13:06:24 -07:00
|
|
|
|
}
|
2026-05-11 18:56:26 +02:00
|
|
|
|
if (c == KEY_ENTER) return KEY_CONTEXT_MENU;
|
2025-08-08 20:01:31 +10:00
|
|
|
|
return c;
|
2025-05-27 19:10:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-03 08:25:59 +10:00
|
|
|
|
char UITask::handleDoubleClick(char c) {
|
|
|
|
|
|
MESH_DEBUG_PRINTLN("UITask: double click triggered");
|
2025-09-03 08:31:38 +10:00
|
|
|
|
checkDisplayOn(c);
|
2025-09-03 08:25:59 +10:00
|
|
|
|
return c;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
char UITask::handleTripleClick(char c) {
|
|
|
|
|
|
MESH_DEBUG_PRINTLN("UITask: triple click triggered");
|
2025-09-03 08:31:38 +10:00
|
|
|
|
checkDisplayOn(c);
|
2025-09-03 08:25:59 +10:00
|
|
|
|
toggleBuzzer();
|
|
|
|
|
|
c = 0;
|
|
|
|
|
|
return c;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-23 10:39:43 +02:00
|
|
|
|
bool UITask::getGPSState() {
|
|
|
|
|
|
if (_sensors != NULL) {
|
|
|
|
|
|
int num = _sensors->getNumSettings();
|
|
|
|
|
|
for (int i = 0; i < num; i++) {
|
|
|
|
|
|
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
|
|
|
|
|
return !strcmp(_sensors->getSettingValue(i), "1");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-03 08:25:59 +10:00
|
|
|
|
void UITask::toggleGPS() {
|
|
|
|
|
|
if (_sensors != NULL) {
|
|
|
|
|
|
// toggle GPS on/off
|
|
|
|
|
|
int num = _sensors->getNumSettings();
|
|
|
|
|
|
for (int i = 0; i < num; i++) {
|
|
|
|
|
|
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
|
|
|
|
|
if (strcmp(_sensors->getSettingValue(i), "1") == 0) {
|
|
|
|
|
|
_sensors->setSettingValue("gps", "0");
|
2025-11-29 16:37:10 +08:00
|
|
|
|
_node_prefs->gps_enabled = 0;
|
2025-09-17 08:53:50 +08:00
|
|
|
|
notify(UIEventType::ack);
|
2025-09-03 08:25:59 +10:00
|
|
|
|
} else {
|
|
|
|
|
|
_sensors->setSettingValue("gps", "1");
|
2025-11-29 16:37:10 +08:00
|
|
|
|
_node_prefs->gps_enabled = 1;
|
2025-09-17 08:53:50 +08:00
|
|
|
|
notify(UIEventType::ack);
|
2025-09-03 08:25:59 +10:00
|
|
|
|
}
|
2025-11-29 16:37:10 +08:00
|
|
|
|
the_mesh.savePrefs();
|
2025-12-11 09:26:09 +01:00
|
|
|
|
showAlert(_node_prefs->gps_enabled ? "GPS: Enabled" : "GPS: Disabled", 800);
|
2025-09-03 08:25:59 +10:00
|
|
|
|
_next_refresh = 0;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
|
void UITask::applyTxPower() {
|
|
|
|
|
|
if (_node_prefs == NULL) return;
|
|
|
|
|
|
radio_set_tx_power(_node_prefs->tx_power_dbm);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UITask::applyGPSInterval() {
|
|
|
|
|
|
if (_node_prefs == NULL) return;
|
|
|
|
|
|
char buf[12];
|
|
|
|
|
|
sprintf(buf, "%u", _node_prefs->gps_interval);
|
|
|
|
|
|
sensors.setSettingValue("gps_interval", buf);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UITask::applyBrightness() {
|
|
|
|
|
|
if (_display != NULL && _node_prefs != NULL) {
|
|
|
|
|
|
_display->setBrightness(_node_prefs->display_brightness);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UITask::setBrightnessLevel(uint8_t level) {
|
|
|
|
|
|
if (_node_prefs == NULL) return;
|
|
|
|
|
|
if (level > 4) level = 4;
|
|
|
|
|
|
_node_prefs->display_brightness = level;
|
|
|
|
|
|
applyBrightness();
|
|
|
|
|
|
_next_refresh = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-03 08:25:59 +10:00
|
|
|
|
void UITask::toggleBuzzer() {
|
|
|
|
|
|
// Toggle buzzer quiet mode
|
2025-05-27 19:10:56 -07:00
|
|
|
|
#ifdef PIN_BUZZER
|
2025-05-30 22:55:53 -07:00
|
|
|
|
if (buzzer.isQuiet()) {
|
|
|
|
|
|
buzzer.quiet(false);
|
2025-09-17 08:53:50 +08:00
|
|
|
|
notify(UIEventType::ack);
|
2025-05-30 22:55:53 -07:00
|
|
|
|
} else {
|
|
|
|
|
|
buzzer.quiet(true);
|
|
|
|
|
|
}
|
2025-11-20 18:55:39 -08:00
|
|
|
|
_node_prefs->buzzer_quiet = buzzer.isQuiet();
|
|
|
|
|
|
the_mesh.savePrefs();
|
2025-12-11 09:26:09 +01:00
|
|
|
|
showAlert(buzzer.isQuiet() ? "Buzzer: OFF" : "Buzzer: ON", 800);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
_next_refresh = 0; // trigger refresh
|
2025-05-27 19:10:56 -07:00
|
|
|
|
#endif
|
|
|
|
|
|
}
|