Files
MeshCore-Solo/examples/companion_radio/ui-lvgl/UITask.h
T
JakubandClaude Opus 5.5 a40a87d02c feat(ui-lvgl): settings kit, choice picker, reorganised Settings
group() / settingRow / listRow / actionRow / choiceRow / groupNote: one card
per section, hairlines between rows, hints that wrap. Dropdowns replaced by
a centred picker list. Settings regrouped (Device, Connections, Map & data,
System); Display & power split into Display, Power and Time on L2 only
(the shared schema is unchanged), Keyboard and About get their own pages.
Radio and Repeater share one LoRa parameter block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 01:17:48 +02:00

529 lines
19 KiB
C++

#pragma once
// ui-lvgl: the rich (colour + touch) frontend of the UI Core. Wio Tracker L2
// first. All application state lives in the Core (../ui-core); this class owns
// only LVGL screens, display power and input. See docs/development/ui-core.md.
#include <MeshCore.h>
#include <Arduino.h>
#include <helpers/ui/DisplayDriver.h>
#include <helpers/SensorManager.h>
#include <helpers/BaseSerialInterface.h>
#include <lvgl.h>
#ifdef PIN_BUZZER
#include <helpers/ui/buzzer.h>
#endif
#include "../AbstractUITask.h"
#include "../NodePrefs.h"
#include "../ui-core/UiCoreHost.h"
class UiCore;
class NearbyModel;
struct UiEvent;
class UITask : public UITaskBase, public UiCoreHost {
public:
UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : UITaskBase(board, serial) {}
void begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs);
void loop() override;
void notify(UIEventType t = UIEventType::none) override;
void shutdown(bool restart = false) override;
MyMesh::Listener* meshListener() override;
// UiCoreHost
bool isViewingChannel(uint8_t channel_idx) override;
bool isViewingDM(const uint8_t* pub_key) override;
void onViewedHistoryGrew(bool channel) override { (void)channel; _thread_dirty = true; }
void botSetGPS(bool on) override;
void applySoundPrefs() override;
// ── Navigation (called from LVGL event callbacks) ────────────────────────
void showHome();
void setHomePage(int page);
void showChats();
void showContacts();
void showSettings();
void showSchemaSettings(int page);
// Home > Admin: the repeaters / room servers to log in to (AdminScreen.h)
void showAdminPick();
// Settings > System > Firmware update (OtaScreen.h)
void showOta();
void otaAction();
void adminPick(int row);
void pruneContacts();
void setBrightnessPct(uint8_t pct, bool save);
void applyDisplayPrefs() override;
void setSchemaValue(int idx, int v);
void showNearby();
void showMap(); // in its current mode
void openMap(bool nav); // Nodes map (false) or Navigation map (true)
void mapLongPress(int x, int y);
void mapZoom(int delta);
void mapCenterOnMe();
void mapPan(int dx, int dy);
void mapOpenMarker(int idx);
void mapDownloadPopup();
void setTapWake(bool on);
void mapDownloadClose();
void mapDownloadStart();
void mapDownloadStop();
void mapDownloadResume();
void mapDownloadDiscard();
void mapDownloadZmax(int delta);
void setLiveTiles(bool on); // Map tools > Live tiles
// Navigation map (NavMap.h)
void navTargetsPopup();
void navClosePopup();
void navPick(int code);
void navWaypointMenu(int idx);
void navWaypointAction(uint8_t act);
void navRenameDone(bool ok);
void navMarkHere();
void navAveragingCancel();
void navToNode(const uint8_t* key, int32_t lat, int32_t lon, const char* name);
void navToolsPopup();
void navToolAction(uint8_t act);
void navSetShareTarget(int sel);
void navWaypointsPopup();
void navCoordsPopup();
void navCoordsDone(bool ok);
void shareToMessage(const char* text);
void messageLocationAction(int idx, bool save);
void showWifi();
void wifiSetAllowed(bool on);
void wifiScan();
void wifiPick(int idx);
void wifiSave();
void wifiEdit(lv_obj_t* ta);
void wifiKeyboardHide();
void setNearbyFilter(uint8_t f);
void toggleNearbySort();
void startNearbyScan();
void closeScanPopup();
void openScanNode(int row);
void openNode(int row);
void nodeAction(uint8_t action);
void setKeyboardAlphabets(int main_idx, int alt_sel);
void openChannel(uint8_t channel_idx);
void openDM(const uint8_t* pub_key);
void back();
void sendFromCompose();
void setKeyboardVisible(bool show);
void showToast(const char* text, uint32_t ms = 2500);
// Clock tools (ClockScreen.h)
void showClock();
void clockTab(int tab);
void clockAction(uint8_t act);
void setAlarm(int which, int v);
void dismissRing();
// Sound (SoundScreen.h): the speaker, Settings > Sound, the melody editor
void playMelody(const char* melody); // through mute (alarms, previews)
void stopMelody();
bool melodyPlaying();
int melodyNote(); // the note sounding now, -1 silent
void setSoundMode(int mode);
void setSoundVolume(int level);
void showMelodies(int slot);
void melodySlot(int slot);
void melodyPick(int idx);
void melodySet(uint8_t which, int v);
void melodyAction(uint8_t act);
void conversationMelody(int v);
void hearMelody(int slot);
#ifdef PIN_BUZZER
// For the sim page's Web Audio speaker (sim_buzzer_* in UITask.cpp).
bool isBuzzerPlaying() { return _buzzer.isPlaying(); }
uint16_t buzzerFreqHz() const;
uint8_t buzzerVolume() const { return _buzzer.getVolume(); }
#endif
// Quick messages, placeholders, advert, Bluetooth (QuickScreen.h)
void quickPopup(); // the compose bar's "+"
void quickSend(int slot);
void quickInsert(int ph);
void showQuickMsgs();
void quickEdit(int slot);
void quickEditDone(bool ok);
void quickEditInsert(int ph);
void advertPopup();
void advertSend(bool flood);
void setBluetooth(bool on);
void markAllRead();
// Settings > Radio (RadioScreen.h)
void showRadio();
void radioSet(int which, int v);
void radioFreqPopup(bool repeater); // the companion's frequency, or the repeater profile's
void radioFreqDone(bool ok);
// Settings > Radio: my presets, scopes (RadioExtras.h)
void presetMenu(int slot);
void presetAction(uint8_t act);
void radioNamePopup(uint8_t what);
void radioNameDone(bool ok);
void showScopes();
void scopeMenu(int idx);
void scopeAction(uint8_t act);
// Settings > Repeater (RepeaterScreen.h)
void showRepeater();
void repeaterSet(int which, int v);
void repeaterScopesPopup();
void repeaterScopeSet(uint8_t i, bool on);
// Channels (ChannelScreen.h)
void channelMenu(int idx);
void channelSet(uint8_t which, int v);
void channelAction(uint8_t act);
void showChannelEdit(int idx);
void channelEditType(int type);
void channelEditHex(bool hex);
void channelEditField(lv_obj_t* ta);
void channelEditKbHide();
void channelEditSave();
// Remote admin (AdminScreen.h)
void openAdmin(const uint8_t* pub_key);
void adminTab(int tab);
void adminRow(int row);
void adminValue(uint8_t act);
void adminChoice(int idx);
void adminLogin(bool ok);
void adminTextDone(bool ok);
void adminCancelWait();
// Bot settings (BotScreen.h)
void showBot();
void botTab(int tab);
void botRow(int row);
void botToggle(int row, bool on);
void botPick(int idx);
void botHour(uint8_t act);
void botTextDone(bool ok);
// ConversationScreen.h
void openRoom(const uint8_t* pub_key);
void roomLoginPopup(const uint8_t* pub_key);
void roomLoginDone(bool ok);
void conversationMenu(const uint8_t* pub_key); // nullptr: the open thread's
void conversationAction(uint8_t act);
void conversationNotif(int v);
void messageMenu(int idx);
void messageAction(uint8_t act);
void toggleChatFilter(uint8_t which);
// DeviceScreen.h
void nodeNamePopup();
void nodeNameDone(bool ok);
void powerPopup(bool restart);
void unlockScreen();
void threadPage(int dir); // +1 older, -1 newer
// Saved trails on the SD card (NavMap.h)
bool saveTrailToCard(char* name_out, size_t n);
void savedTrailsPopup();
void savedTrailPopup(int idx); // -1 = the device's own slot
void savedTrailAction(uint8_t act);
void navFrameTrail();
// Settings > Storage (StorageScreen.h)
void showStorage();
void showGps(bool from_settings);
void buildGps();
void refreshGps();
void buildStorage();
void pollStorage();
void storageKeep(int idx);
void storageClearHistory();
void storageLiveCap(int idx);
void storageClearLive();
bool locked() const;
void pinKey(const char* key); // the lock screen's keypad
void pinSetupPopup(); // Settings > Display & power > Screen PIN
void pinSetupKey(const char* key);
void pinRemove();
void pinRowRefresh();
void accentRow(lv_obj_t* body); // Settings > Display & power > Accent colour
void setAccent(int idx);
void showDiag();
void diagTab(int tab);
void diagResetPopup();
void diagReset();
void showCompass();
void favTap(int slot);
void favHold(int slot);
void favAction(uint8_t act);
void favPick(int code);
void pinPopup(bool channel, uint8_t ch_idx, const uint8_t* pub_key);
void pinTo(int slot);
void setGps(bool on);
bool ensureGps(); // true with a fix; else turns GPS on / says it's waiting
// Kept for main.cpp's sim hooks (ui-new API).
void openContactDM(const ContactInfo& ci) { openDM(ci.id.pub_key); }
void openAdminFor(const ContactInfo& ci, bool from_picker) { (void)from_picker; openAdmin(ci.id.pub_key); }
private:
enum Screen : uint8_t { SCR_HOME, SCR_CHATS, SCR_CONTACTS, SCR_THREAD, SCR_SETTINGS, SCR_NEARBY, SCR_NODE, SCR_MAP, SCR_WIFI, SCR_SETTINGS_NAV, SCR_CLOCK, SCR_RADIO, SCR_CHANNEL_EDIT, SCR_ADMIN, SCR_BOT, SCR_DIAG, SCR_COMPASS, SCR_SCOPES, SCR_REPEATER, SCR_MELODY, SCR_QUICK, SCR_ADMIN_PICK, SCR_OTA, SCR_STORAGE, SCR_GPS };
void buildStatusBar();
void refreshStatusBar();
lv_obj_t* newScreen(const char* title, bool with_back);
void showSplash(); // Splash.h
// The screen being shown: newScreen()'s, already while it slides in (popups
// and keyboards go on it, not on the one sliding out).
lv_obj_t* screen() const { return _scr ? _scr : lv_screen_active(); }
void buildHome();
void refreshHome();
void homeSwipePoll();
void buildChats();
void buildContacts();
void buildSettings();
void buildSchemaSettings();
void schemaRows(lv_obj_t* body, uint8_t page);
void schemaRow(lv_obj_t* card, int idx);
void buildKeyboardPage(lv_obj_t* body);
void buildAboutPage(lv_obj_t* body);
void buildAdminPick();
void buildOta();
void refreshOta();
void otaTick();
void otaLeave();
bool otaBusy() const;
bool wifiInUse() const; // a map download or an update holds the WiFi
void buildNearby();
void refreshNearbyList();
uint32_t nearbySignature() const;
void showScanPopup();
void refreshScanPopup();
void buildMap();
void layoutMap();
void mapLoop();
void rebuildMapMarkers();
void rebuildNodeMarkers();
void addMapMark(uint8_t kind, int idx, int32_t lat_e6, int32_t lon_e6, uint32_t col, const char* text);
void mapPointToLatLon(int x, int y, int32_t& lat_e6, int32_t& lon_e6) const;
double mapCenterBias() const;
void buildNavLayers();
void buildNavControls(lv_obj_t* body);
void rebuildNavMarkers();
void layoutNav();
void refreshNavBar();
void navFrameTarget();
void navSetTarget(uint8_t kind, const uint8_t* key, int32_t lat, int32_t lon, const char* name);
lv_obj_t* navPopupPanel(const char* title, bool full);
void navRenamePopup(int idx);
void navAddWaypoint(int32_t lat, int32_t lon);
void navDropAt(int x, int y);
void navSpotPopup(int32_t lat, int32_t lon);
void placeCard(lv_obj_t* parent, int32_t lat, int32_t lon);
void refreshNavTools();
void navPollAveraging();
void navPollTrackBack();
bool navCurrentTarget(int32_t& lat, int32_t& lon, char* name, int n, bool& person, bool& set);
bool exportTrailGpx(char* name_out, size_t n);
void mapDownloadTick();
void mapLiveBegin();
void refreshDownloadPopup();
void buildWifi();
void pollWifiScan();
void buildNode();
void refreshNode();
void buildThread();
void refreshThread();
uint32_t threadSignature() const;
void buildClock();
void buildRadio();
void rebuildRadio();
void buildRadioExtras(lv_obj_t* body);
void buildScopes();
void buildRepeater();
void rebuildRepeater();
void buildSoundRows(lv_obj_t* body, bool top);
void buildMelodies();
void buildQuickMsgs();
bool sendThreadText(const char* text);
void refreshMelody();
void melodySave();
void radioCloseFreq();
void buildChannelEdit();
void buildAdmin();
void buildBot();
void refreshAdminStatus();
void adminLeave();
void adminPoll();
void roomPoll();
void lockScreen();
void refreshLock();
void lockPoll();
void favGrid(lv_obj_t* grid);
void favRefresh();
int unreadTotal(); // DMs, channels and rooms
void buildDiag();
void refreshDiag();
void buildCompass();
void refreshCompass();
void favPickPopup(int slot);
void adminValuePopup();
void adminTextPopup(const char* text, bool digits);
void adminReplyPopup(const char* text);
bool radioPopupOpen() const;
void refreshClock();
void showRing(const char* text);
void hideRing();
void drainCoreEvents();
void onMessageArrived(const UiEvent& ev);
void wake();
void sleep();
void toggleMute();
uint32_t _next_wake_poll_ms = 0;
uint32_t _next_touch_poll_ms = 0; // tap to wake, while the screen is off
bool _wake_down = false;
uint32_t autoOffMillis() const;
uint32_t idleMillis(uint32_t lv_next);
void checkLowBattery();
#ifdef PIN_BUZZER
genericBuzzer _buzzer;
char _notif_mel_buf[220]; // a user melody's RTTTL while it plays (the player copies it)
#endif
// Who the message being notified came from (SoundNotifier's overrides).
bool _notif_dm_valid = false;
uint8_t _notif_dm_prefix[4] = {0};
int _notif_ch_idx = -1;
DisplayDriver* _display = nullptr;
SensorManager* _sensors = nullptr;
NodePrefs* _prefs = nullptr;
bool _gps_from_settings = false; // GPS screen: Back returns there (else Home)
UiCore* _core = nullptr;
Screen _screen = SCR_HOME;
// What newScreen() last loaded, for the slide direction (Anim.h)
Screen _shown_screen = SCR_HOME;
char _shown_title[32] = "";
bool _nav_back = false; // set by back() around the screen it shows
lv_obj_t* _scr = nullptr; // the screen newScreen() built (active once its slide starts)
static int screenDepth(Screen s);
bool _asleep = false;
uint32_t _next_status_ms = 0;
uint32_t _next_thread_check_ms = 0;
uint32_t _next_trackback_ms = 0;
uint32_t _next_clock_ms = 0;
uint8_t _settings_page = 0; // settings::Page shown in SCR_SETTINGS_NAV
lv_obj_t* _ota_status = nullptr;
lv_obj_t* _ota_bar = nullptr;
lv_obj_t* _ota_btn = nullptr;
lv_obj_t* _ota_btn_lbl = nullptr;
uint32_t _map_left_ms = 0; // when the map was left, for dropping live tiles' WiFi
bool _admin_from_pick = false; // Admin opened from its Home tile (back returns there)
uint32_t _prune_armed_ms = 0;
uint16_t _batt_mv = 0; // smoothed, read every 8 s: the status bar and the low-battery shutdown
uint32_t _next_batt_ms = 0;
lv_obj_t* _prune_lbl = nullptr;
// Screen PIN (DeviceScreen.h); stored in NVS (lvport::loadPin)
char _pin[9] = ""; // "" = no PIN
bool _tap_wake = true; // a touch wakes the dark screen (NVS)
char _pin_entry[9] = ""; // digits typed so far
char _pin_new[9] = ""; // setup: the first entry, waiting for its confirmation
uint8_t _pin_fails = 0;
uint32_t _pin_block_until = 0; // millis() until which entry is refused after 5 misses
// Open conversation (SCR_THREAD)
bool _thread_is_channel = false;
int _thread_skip = 0; // newest messages above the page shown (Older / Newer)
bool _thread_scroll_top = false;
int loadThreadPage(int& total);
uint8_t _thread_channel = 0;
uint8_t _thread_key[PUB_KEY_SIZE] = {0};
bool _thread_dirty = false;
uint32_t _thread_sig = 0;
// Widgets
lv_obj_t* _status_time = nullptr;
lv_obj_t* _status_icons = nullptr; // a row of icon labels, rebuilt with the state
lv_obj_t* _status_batt = nullptr;
char _status_sig[128] = ""; // what the icon row shows, to skip unchanged rebuilds
lv_obj_t* _toast = nullptr;
lv_timer_t* _toast_timer = nullptr;
lv_obj_t* _home_clock = nullptr;
lv_obj_t* _home_date = nullptr;
lv_obj_t* _home_unread = nullptr;
lv_obj_t* _header = nullptr; // current screen's title bar (nullptr on Home)
lv_obj_t* _body = nullptr;
lv_obj_t* _thread_list = nullptr;
lv_obj_t* _compose_ta = nullptr;
lv_obj_t* _keyboard = nullptr;
// Nearby (SCR_NEARBY) / node detail (SCR_NODE); list data in NearbyModel
NearbyModel* _nearby = nullptr;
NearbyModel* _scan = nullptr; // NODE_DISCOVER results: nodes in range now (popup over the list)
bool _scanning = false;
bool _node_from_scan = false;
uint32_t _scan_sig = 0;
lv_obj_t* _scan_overlay = nullptr;
lv_obj_t* _scan_list = nullptr;
lv_obj_t* _scan_status = nullptr;
uint32_t _scan_until_ms = 0;
uint32_t _nearby_sig = 0;
uint32_t _next_nearby_ms = 0;
bool _pinging = false;
uint32_t _ping_started_ms = 0;
uint32_t _delete_armed_ms = 0;
lv_obj_t* _nearby_list = nullptr;
lv_obj_t* _nearby_status = nullptr;
lv_obj_t* _nearby_sort_lbl = nullptr;
lv_obj_t* _nearby_chips = nullptr;
lv_obj_t* _node_info = nullptr;
lv_obj_t* _node_ping = nullptr;
lv_obj_t* _node_delete_lbl = nullptr;
bool _node_from_map = false;
// Map (SCR_MAP): view centre in fractional tile coords at zoom _map_z
double _map_cx = 0, _map_cy = 0;
int _map_z = 0; // 0 = not initialised yet
bool _map_follow = true; // keep centred on own position until panned
bool _map_pending = false; // visible tiles still to decode
uint32_t _next_map_marks_ms = 0;
lv_obj_t* _map_area = nullptr;
lv_obj_t* _map_tiles[6] = {nullptr};
lv_obj_t* _map_marks = nullptr;
lv_obj_t* _map_me = nullptr;
lv_obj_t* _map_zoom_lbl = nullptr;
lv_obj_t* _map_hint = nullptr;
lv_obj_t* _map_dl_pill = nullptr; // download progress over the map
lv_obj_t* _dl_overlay = nullptr; // download popup
lv_obj_t* _dl_info = nullptr;
lv_obj_t* _dl_bar = nullptr; // progress while downloading
lv_obj_t* _dl_err = nullptr; // failures, one line
lv_obj_t* _dl_zoom_lbl = nullptr;
lv_obj_t* _dl_start_lbl = nullptr;
lv_obj_t* _dl_job_row = nullptr; // "Unfinished ... Resume" in the popup
lv_obj_t* _dl_job_lbl = nullptr;
int _dl_zmax = 0;
uint8_t _dl_last_state = 0;
// Navigation map (SCR_MAP with _map_nav)
bool _map_nav = false;
uint32_t _next_nav_bar_ms = 0;
lv_obj_t* _nav_bar = nullptr; // target bar along the bottom
lv_obj_t* _nav_title = nullptr;
lv_obj_t* _nav_info = nullptr;
lv_obj_t* _nav_clear = nullptr;
lv_obj_t* _nav_overlay = nullptr; // target list / waypoint menu / rename
lv_obj_t* _nav_ta = nullptr;
lv_obj_t* _nav_kb = nullptr;
lv_obj_t* _nav_del_lbl = nullptr;
uint32_t _nav_del_armed_ms = 0;
int _nav_wp = -1; // waypoint the menu / rename is about
lv_obj_t* _nav_rec = nullptr; // "REC 1.2 km LIVE 58m" pill
lv_obj_t* _nav_avg_pill = nullptr; // GPS averaging progress (tap: cancel)
lv_obj_t* _nav_trail_lbl = nullptr; // tools panel
lv_obj_t* _nav_trail_btn = nullptr;
lv_obj_t* _nav_reset_lbl = nullptr;
lv_obj_t* _nav_share_lbl = nullptr;
lv_obj_t* _nav_share_btn = nullptr;
lv_obj_t* _nav_tb_btn = nullptr;
uint32_t _nav_reset_armed_ms = 0;
int32_t _nav_spot_lat = 0, _nav_spot_lon = 0; // the spot a long-press picked
char _share_text[96] = ""; // waiting for a conversation to be picked (shareToMessage)
// WiFi settings (SCR_WIFI)
bool _wifi_scanning = false;
lv_obj_t* _wifi_ssid = nullptr;
lv_obj_t* _wifi_pass = nullptr;
lv_obj_t* _wifi_kb = nullptr;
lv_obj_t* _wifi_list = nullptr;
lv_obj_t* _wifi_status = nullptr;
};