From f4e7210a4009d612b0addd035a4f37cab22b35034012db180460a9e292eb153d Mon Sep 17 00:00:00 2001 From: AidarKC Date: Tue, 9 Jun 2026 16:14:24 +0400 Subject: [PATCH] =?UTF-8?q?ESP32:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20NAV=20v8=20=D1=81=20account=20=D0=B8=20Wi-Fi=20r?= =?UTF-8?q?econnect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-06-08_1940_esp32_nav_minimal_test.md | 22 +- .../shine_subserver_ui_nav_minimal_spec.md | 73 +++- .../lvgl_nav_minimal_test.ino | 322 ++++++++++++++++-- VERSION.properties | 4 +- 4 files changed, 386 insertions(+), 35 deletions(-) diff --git a/Dev_Docs/Pending_Features/2026-06-08_1940_esp32_nav_minimal_test.md b/Dev_Docs/Pending_Features/2026-06-08_1940_esp32_nav_minimal_test.md index 7a74109..705a71f 100644 --- a/Dev_Docs/Pending_Features/2026-06-08_1940_esp32_nav_minimal_test.md +++ b/Dev_Docs/Pending_Features/2026-06-08_1940_esp32_nav_minimal_test.md @@ -3,7 +3,7 @@ - Краткое описание: минимальный UI-прототип для сабсервера на базе `LVGL + subserver touch`, с Wi-Fi flow, серверными адресами и общим экраном редактирования текста. - Что проверять: - стартует экран `HOME`; - - на `HOME` видны `login`, `subserver1`, `W BAT`, `STATUS`, Wi-Fi статус и кнопка `SETTINGS`; + - на `HOME` видны реальное значение логина или `login not set`, реальное значение сабсервера или `subserver not set`, при отсутствии секрета строка `secret not set`, а также `W BAT`, `STATUS`, Wi-Fi статус и кнопка `SETTINGS`; - Wi-Fi статус на `HOME` корректно показывает одно из состояний: - `Wi-Fi not configured` - `Wi-Fi disconnected` @@ -21,16 +21,25 @@ - выбор SSID открывает общий экран редактирования текста для пароля; - на этом экране видно старое значение, курсор стоит в конце; - две верхние служебные строки над полем ввода отсутствуют; + - при вводе пароля Wi-Fi текст показывается открыто, без точек; - большая клавиатура реально видна на экране и занимает большую часть высоты; - буквы разбиты на 2 страницы; - режим символов тоже разбит на 2 страницы; - на правой странице кнопки стоят в ровных вертикальных колонках; - свайп влево/вправо на экране ввода переключает страницы клавиатуры; - при этом свайп страниц клавиатуры срабатывает только из нижней клавиатурной зоны, а не из верхней части экрана; + - при переключении `ABC/123` и `SHIFT` уже введённый текст не пропадает; + - при свайпе между левой и правой половиной клавиатуры уже введённый текст тоже не пропадает, в том числе для цифр, символов и заглавных букв; + - визуальный курсор в поле ввода не показывается; + - новые символы всегда дописываются только в конец строки; + - основные 3 ряда клавиш и нижний служебный ряд стали выше; + - внизу остаётся отдельная тёмная полоса с версией `NAV v7`, а рамка клавиатурного блока заканчивается выше неё; - `ABC/123`, `SHIFT`, `DEL`, `SAVE`, `CANCEL` работают; - при успехе SSID и пароль сохраняются, а `HOME` показывает `Wi-Fi connected`; - при ошибке показывается `Connection failed`; - `CLEAR SAVED WI-FI` очищает сохранённые настройки; + - если сеть была ранее успешно сохранена, после потери связи устройство автоматически пытается переподключиться; + - первые повторные попытки идут раз в `10` секунд, а после долгого отсутствия связи интервал увеличивается до `30` секунд; - нажатие `Server` открывает `SERVER_SCREEN`; - в `SERVER_SCREEN` видны и редактируются два значения: - `https://api.devnet.solana.com` @@ -38,7 +47,18 @@ - нажатие `SOLANA RPC` открывает общий экран редактирования; - нажатие `SHINE SERVER` открывает общий экран редактирования; - после `SAVE` новые адреса сохраняются в NVS; + - нажатие `Account` открывает `ACCOUNT_SCREEN`; + - `ACCOUNT_SCREEN` показывает 3 кнопки: + - `Login ()` + - `Subserver ()` + - `Secret (<*****|not set>)` + - `Login` открывает общий экран редактирования и сохраняется в NVS; + - `Subserver` открывает промежуточный экран с `USE SUBSERVER1` и `EDIT MANUALLY`; + - `USE SUBSERVER1` возвращает стандартное значение `subserver1`; + - `EDIT MANUALLY` открывает общий экран редактирования и сохраняет значение в NVS; + - `Secret` открывает экран-заглушку, где сказано, что настройка ещё не реализована; - свайп вправо из внутренних экранов возвращает в `SETTINGS_MENU`; + - свайп вправо из `ACCOUNT_SUBSERVER_SCREEN` и `ACCOUNT_SECRET_SCREEN` возвращает в `ACCOUNT_SCREEN`; - если во время реального свайпа палец проходит по кнопке, это не должно открывать кнопку как обычный `click`. - Ожидаемый результат: новый скетч даёт чистый навигационный каркас и уже умеет настраивать Wi-Fi и серверные адреса на самой ESP32. - Статус: pending diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_subserver_ui_nav_minimal_spec.md b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_subserver_ui_nav_minimal_spec.md index ebe096d..ec1e71f 100644 --- a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_subserver_ui_nav_minimal_spec.md +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_subserver_ui_nav_minimal_spec.md @@ -19,19 +19,22 @@ ## Экраны -Прототип содержит 6 экранов: +Прототип содержит 8 экранов: - `HOME` - `SETTINGS_MENU` - `WIFI_SCREEN` - `SERVER_SCREEN` - `ACCOUNT_SCREEN` +- `ACCOUNT_SUBSERVER_SCREEN` +- `ACCOUNT_SECRET_SCREEN` - `TEXT_EDIT_SCREEN` ## HOME Показывает: -- сверху слева `login`; -- ниже `subserver1`; +- сверху слева значение логина или `login not set`; +- ниже значение сабсервера или `subserver not set`; +- третьей строкой `secret not set`, если секрет ещё не помечен как установленный; - сверху справа простые индикаторы `W BAT`; - по центру крупный текст `STATUS`; - статус Wi-Fi; @@ -128,12 +131,44 @@ ## ACCOUNT_SCREEN Показывает: -- `Account` -- `Account screen` +- заголовок `ACCOUNT`; +- статусное сообщение; +- кнопку `Login ()`; +- кнопку `Subserver ()`; +- кнопку `Secret (<*****|not set>)`. Переходы: - свайп вправо -> `SETTINGS_MENU` -- кнопка `BACK` -> `SETTINGS_MENU` +- `Login` -> `TEXT_EDIT_SCREEN` +- `Subserver` -> `ACCOUNT_SUBSERVER_SCREEN` +- `Secret` -> `ACCOUNT_SECRET_SCREEN` + +## ACCOUNT_SUBSERVER_SCREEN + +Показывает: +- текущий `subserver`; +- рекомендацию оставить `subserver1`, если устройство одно; +- кнопку `USE SUBSERVER1`; +- кнопку `EDIT MANUALLY`; +- кнопку `BACK`. + +Переходы: +- `USE SUBSERVER1` -> сохраняет `subserver1` и возвращает в `ACCOUNT_SCREEN` +- `EDIT MANUALLY` -> `TEXT_EDIT_SCREEN` +- свайп вправо -> `ACCOUNT_SCREEN` + +## ACCOUNT_SECRET_SCREEN + +Пока это заглушка. + +Показывает: +- текущий статус секрета `set/not set`; +- сообщение, что настройка секрета пока не реализована; +- кнопку `BACK`. + +Переходы: +- свайп вправо -> `ACCOUNT_SCREEN` +- `BACK` -> `ACCOUNT_SCREEN` ## TEXT_EDIT_SCREEN @@ -147,7 +182,9 @@ Показывает: - заголовок; - поле ввода, уже заполненное старым значением; -- курсор установлен в конец текста; +- новые символы всегда добавляются только в конец текста; +- визуальный курсор не показывается; +- пароль Wi-Fi показывается открыто, без маскировки точками; - кнопки `SAVE`, `CANCEL`, `DEL`, `CLR`; - большую экранную клавиатуру. @@ -161,6 +198,8 @@ - переключение страниц выполняется свайпом влево/вправо внутри `TEXT_EDIT_SCREEN`; - страница 1 содержит в основном буквы и базовые URL-символы; - страница 2 содержит цифры и дополнительные URL/символьные кнопки; +- переключение `ABC/123` и `SHIFT` не должно очищать уже введённый текст; +- переключение страниц клавиатуры свайпом тоже не должно очищать уже введённый текст, включая цифры, символы и уже набранные заглавные буквы; - есть специальные действия `DEL` и `CLR`. ## Хранение Wi-Fi @@ -168,29 +207,45 @@ Используется `Preferences` (NVS памяти ESP32): - `wifi_ssid` - `wifi_pass` +- `wifi_known_good` При старте устройства, если сохранён SSID, выполняется попытка подключения к сохранённой сети. +Если сеть раньше уже была успешно подключена и помечена как валидная: +- после потери связи устройство автоматически пытается переподключиться; +- первые повторные попытки идут раз в `10` секунд; +- если связи нет долго, интервал увеличивается до `30` секунд. + ## Хранение серверов Используется `Preferences` (NVS памяти ESP32): - `solana_rpc` - `shine_server` +## Хранение аккаунта + +Используется `Preferences` (NVS памяти ESP32): +- `login` +- `subserver` +- `secret_set` + ## Детали клавиатуры - клавиатура занимает примерно `2/3`-`3/4` высоты экрана; -- сверху остаются только заголовок, подсказка и поле ввода; +- сверху остаются только заголовок и поле ввода; - буквы занимают 3 ряда; - половина букв находится на левой странице, половина на правой; - на правой странице кнопки тоже стоят в ровных колонках, без сдвига рядов вправо; - отдельный режим `symbols` тоже разделён на 2 страницы; +- 3 основных ряда клавиш и нижний служебный ряд увеличены по высоте; +- клавиши дополнительно увеличены по высоте по сравнению с предыдущим промежуточным вариантом; - четвёртый ряд содержит: - переключение `ABC/123` - `SHIFT` - `DEL` - `SAVE` - `CANCEL` +- ниже рамки клавиатурного блока остаётся отдельная тёмная полоса с версией `NAV v7`. ## Жесты @@ -207,6 +262,8 @@ - `WIFI_SCREEN`: свайп вправо -> `SETTINGS_MENU` - `SERVER_SCREEN`: свайп вправо -> `SETTINGS_MENU` - `ACCOUNT_SCREEN`: свайп вправо -> `SETTINGS_MENU` +- `ACCOUNT_SUBSERVER_SCREEN`: свайп вправо -> `ACCOUNT_SCREEN` +- `ACCOUNT_SECRET_SCREEN`: свайп вправо -> `ACCOUNT_SCREEN` - `TEXT_EDIT_SCREEN`: свайп влево/вправо -> переключение страниц клавиатуры - переключение страниц клавиатуры срабатывает только если свайп начался в зоне самой клавиатуры, а не по всему экрану редактора diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/test_sketches/lvgl_nav_minimal_test/lvgl_nav_minimal_test.ino b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/test_sketches/lvgl_nav_minimal_test/lvgl_nav_minimal_test.ino index 11b22f0..3070b84 100644 --- a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/test_sketches/lvgl_nav_minimal_test/lvgl_nav_minimal_test.ino +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/test_sketches/lvgl_nav_minimal_test/lvgl_nav_minimal_test.ino @@ -25,11 +25,14 @@ #define TAP_CANCEL_THRESHOLD 18 #define MAX_SCAN_RESULTS 8 #define WIFI_CONNECT_TIMEOUT_MS 12000 +#define WIFI_RECONNECT_FAST_MS 10000 +#define WIFI_RECONNECT_SLOW_MS 30000 +#define WIFI_RECONNECT_SLOW_AFTER_MS 90000 #define TEXT_EDIT_PANEL_X 10 -#define TEXT_EDIT_PANEL_Y 126 +#define TEXT_EDIT_PANEL_Y 112 #define TEXT_EDIT_PANEL_W 460 -#define TEXT_EDIT_PANEL_H 344 -#define TEST_VERSION "NAV v6" +#define TEXT_EDIT_PANEL_H 330 +#define TEST_VERSION "NAV v8" enum Screen { SCREEN_HOME, @@ -37,6 +40,8 @@ enum Screen { SCREEN_WIFI, SCREEN_SERVER, SCREEN_ACCOUNT, + SCREEN_ACCOUNT_SUBSERVER, + SCREEN_ACCOUNT_SECRET, SCREEN_TEXT_EDIT, }; @@ -60,6 +65,12 @@ enum ActionId { ACTION_WIFI_CLEAR, ACTION_SERVER_EDIT_SOLANA, ACTION_SERVER_EDIT_SHINE, + ACTION_ACCOUNT_EDIT_LOGIN, + ACTION_ACCOUNT_EDIT_SUBSERVER, + ACTION_ACCOUNT_EDIT_SECRET, + ACTION_ACCOUNT_SUBSERVER_USE_DEFAULT, + ACTION_ACCOUNT_SUBSERVER_EDIT_MANUAL, + ACTION_BACK_ACCOUNT, ACTION_EDITOR_SAVE, ACTION_EDITOR_CANCEL, }; @@ -74,6 +85,8 @@ enum EditContext { EDIT_CONTEXT_WIFI_PASSWORD, EDIT_CONTEXT_SOLANA_RPC, EDIT_CONTEXT_SHINE_SERVER, + EDIT_CONTEXT_LOGIN, + EDIT_CONTEXT_SUBSERVER, }; enum KeyboardMode { @@ -118,6 +131,16 @@ static WifiViewMode gWifiViewMode = WIFI_VIEW_OVERVIEW; static String gSolanaRpcUrl = "https://api.devnet.solana.com"; static String gShineServerUrl = "https://shineup.me"; static String gServerStatusMessage = "Edit RPC or shine host"; +static String gLoginValue; +static String gSubserverValue = "subserver1"; +static bool gSecretConfigured = false; +static String gAccountStatusMessage = "Edit account fields"; +static bool gWifiKnownGood = false; +static bool gWifiReconnectEnabled = false; +static bool gWifiOperationBusy = false; +static unsigned long gWifiDisconnectedSinceMs = 0; +static unsigned long gWifiLastReconnectAttemptMs = 0; +static wl_status_t gLastWifiStatus = WL_IDLE_STATUS; static EditContext gEditContext = EDIT_CONTEXT_NONE; static Screen gEditReturnScreen = SCREEN_HOME; @@ -136,11 +159,14 @@ static void loadPrefs(); static void saveWifiPrefs(); static void clearWifiPrefs(); static void saveServerPrefs(); +static void saveAccountPrefs(); static void beginSavedWifi(); static void scanWifiNetworks(); static bool connectWifiNow(const String &ssid, const String &password); static String wifiHomeStatus(); static String wifiSavedLabel(); +static void updateWifiReconnectState(); +static void manageWifiReconnect(); static void openEditor(EditContext context, Screen returnScreen, const String &title, @@ -149,6 +175,13 @@ static void openEditor(EditContext context, bool isPassword); static void applyEditorValue(); static bool isTextEditKeyboardSwipeArea(int16_t x, int16_t y); +static void syncEditValueFromTextarea(); +static void keepCursorAtEnd(); +static void restoreTextareaFromEditValue(); +static String loginDisplayValue(); +static String subserverDisplayValue(); +static String homeSecretStatus(); +static String secretButtonValue(); static void lvglFlushCb(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *colorP) { uint32_t w = area->x2 - area->x1 + 1; @@ -263,13 +296,18 @@ static void showMessageAt(const String &text, lv_coord_t y) { static void loadPrefs() { gWifiSavedSsid = gPrefs.getString("wifi_ssid", ""); gWifiSavedPassword = gPrefs.getString("wifi_pass", ""); + gWifiKnownGood = gPrefs.getBool("wifi_known_good", false); gSolanaRpcUrl = gPrefs.getString("solana_rpc", "https://api.devnet.solana.com"); gShineServerUrl = gPrefs.getString("shine_server", "https://shineup.me"); + gLoginValue = gPrefs.getString("login", ""); + gSubserverValue = gPrefs.getString("subserver", "subserver1"); + gSecretConfigured = gPrefs.getBool("secret_set", false); } static void saveWifiPrefs() { gPrefs.putString("wifi_ssid", gWifiSavedSsid); gPrefs.putString("wifi_pass", gWifiSavedPassword); + gPrefs.putBool("wifi_known_good", gWifiKnownGood); } static void saveServerPrefs() { @@ -277,22 +315,36 @@ static void saveServerPrefs() { gPrefs.putString("shine_server", gShineServerUrl); } +static void saveAccountPrefs() { + gPrefs.putString("login", gLoginValue); + gPrefs.putString("subserver", gSubserverValue); + gPrefs.putBool("secret_set", gSecretConfigured); +} + static void clearWifiPrefs() { gWifiSavedSsid = ""; gWifiSavedPassword = ""; gWifiSelectedSsid = ""; + gWifiKnownGood = false; + gWifiReconnectEnabled = false; + gWifiDisconnectedSinceMs = 0; + gWifiLastReconnectAttemptMs = 0; saveWifiPrefs(); WiFi.disconnect(true, false); gWifiStatusMessage = "Saved Wi-Fi cleared"; } static void beginSavedWifi() { - if (gWifiSavedSsid.isEmpty()) { + if (gWifiSavedSsid.isEmpty() || !gWifiKnownGood) { + gWifiReconnectEnabled = false; return; } WiFi.mode(WIFI_STA); WiFi.begin(gWifiSavedSsid.c_str(), gWifiSavedPassword.c_str()); + gWifiReconnectEnabled = true; + gWifiDisconnectedSinceMs = millis(); + gWifiLastReconnectAttemptMs = millis(); } static String wifiHomeStatus() { @@ -312,9 +364,78 @@ static String wifiSavedLabel() { return String("Saved: ") + gWifiSavedSsid; } +static String loginDisplayValue() { + return gLoginValue.isEmpty() ? "login not set" : gLoginValue; +} + +static String subserverDisplayValue() { + return gSubserverValue.isEmpty() ? "subserver not set" : gSubserverValue; +} + +static String homeSecretStatus() { + return gSecretConfigured ? "" : "secret not set"; +} + +static String secretButtonValue() { + return gSecretConfigured ? "*****" : "not set"; +} + +static void updateWifiReconnectState() { + wl_status_t status = WiFi.status(); + if (status == WL_CONNECTED) { + if (gLastWifiStatus != WL_CONNECTED) { + gWifiStatusMessage = String("Connected: ") + WiFi.localIP().toString(); + } + gWifiDisconnectedSinceMs = 0; + gLastWifiStatus = status; + return; + } + + if (gLastWifiStatus == WL_CONNECTED && gWifiReconnectEnabled) { + gWifiDisconnectedSinceMs = millis(); + gWifiLastReconnectAttemptMs = 0; + gWifiStatusMessage = "Wi-Fi lost. Reconnecting..."; + } + gLastWifiStatus = status; +} + +static void manageWifiReconnect() { + updateWifiReconnectState(); + + if (!gWifiReconnectEnabled || gWifiSavedSsid.isEmpty() || gWifiOperationBusy) { + return; + } + + if (WiFi.status() == WL_CONNECTED) { + return; + } + + if (gWifiDisconnectedSinceMs == 0) { + gWifiDisconnectedSinceMs = millis(); + } + + unsigned long disconnectedFor = millis() - gWifiDisconnectedSinceMs; + unsigned long interval = disconnectedFor >= WIFI_RECONNECT_SLOW_AFTER_MS + ? WIFI_RECONNECT_SLOW_MS + : WIFI_RECONNECT_FAST_MS; + if (gWifiLastReconnectAttemptMs != 0 && + millis() - gWifiLastReconnectAttemptMs < interval) { + return; + } + + gWifiLastReconnectAttemptMs = millis(); + gWifiStatusMessage = disconnectedFor >= WIFI_RECONNECT_SLOW_AFTER_MS + ? "Wi-Fi retry every 30s" + : "Wi-Fi retry every 10s"; + WiFi.disconnect(false, false); + WiFi.mode(WIFI_STA); + WiFi.begin(gWifiSavedSsid.c_str(), gWifiSavedPassword.c_str()); +} + static void scanWifiNetworks() { gScanResultCount = 0; gWifiStatusMessage = "Scanning networks..."; + gWifiOperationBusy = true; WiFi.mode(WIFI_STA); WiFi.disconnect(false, false); delay(100); @@ -323,6 +444,7 @@ static void scanWifiNetworks() { if (found <= 0) { gWifiStatusMessage = "No networks found"; gWifiViewMode = WIFI_VIEW_OVERVIEW; + gWifiOperationBusy = false; return; } @@ -333,6 +455,7 @@ static void scanWifiNetworks() { WiFi.scanDelete(); gWifiStatusMessage = String("Found ") + found + " networks"; gWifiViewMode = WIFI_VIEW_SCAN_RESULTS; + gWifiOperationBusy = false; } static bool connectWifiNow(const String &ssid, const String &password) { @@ -340,6 +463,7 @@ static bool connectWifiNow(const String &ssid, const String &password) { rebuildScreen(); lv_timer_handler(); + gWifiOperationBusy = true; WiFi.mode(WIFI_STA); WiFi.disconnect(false, false); delay(150); @@ -350,8 +474,14 @@ static bool connectWifiNow(const String &ssid, const String &password) { if (WiFi.status() == WL_CONNECTED) { gWifiSavedSsid = ssid; gWifiSavedPassword = password; + gWifiKnownGood = true; + gWifiReconnectEnabled = true; + gWifiDisconnectedSinceMs = 0; + gWifiLastReconnectAttemptMs = 0; + gLastWifiStatus = WL_CONNECTED; saveWifiPrefs(); gWifiStatusMessage = String("Connected: ") + WiFi.localIP().toString(); + gWifiOperationBusy = false; return true; } delay(200); @@ -359,6 +489,7 @@ static bool connectWifiNow(const String &ssid, const String &password) { WiFi.disconnect(false, false); gWifiStatusMessage = "Connection failed"; + gWifiOperationBusy = false; return false; } @@ -409,6 +540,24 @@ static void applyEditorValue() { showScreen(SCREEN_SERVER); return; } + + if (gEditContext == EDIT_CONTEXT_LOGIN) { + value.trim(); + gLoginValue = value; + saveAccountPrefs(); + gAccountStatusMessage = gLoginValue.isEmpty() ? "Login cleared" : "Login saved"; + showScreen(SCREEN_ACCOUNT); + return; + } + + if (gEditContext == EDIT_CONTEXT_SUBSERVER) { + value.trim(); + gSubserverValue = value; + saveAccountPrefs(); + gAccountStatusMessage = gSubserverValue.isEmpty() ? "Subserver cleared" : "Subserver saved"; + showScreen(SCREEN_ACCOUNT); + return; + } } static void cancelEditor() { @@ -419,6 +568,25 @@ static void cancelEditor() { } } +static void syncEditValueFromTextarea() { + if (gInputTextArea) { + gEditValue = String(lv_textarea_get_text(gInputTextArea)); + } +} + +static void keepCursorAtEnd() { + if (gInputTextArea) { + lv_textarea_set_cursor_pos(gInputTextArea, LV_TEXTAREA_CURSOR_LAST); + } +} + +static void restoreTextareaFromEditValue() { + if (gInputTextArea) { + lv_textarea_set_text(gInputTextArea, gEditValue.c_str()); + keepCursorAtEnd(); + } +} + static void networkSelectCb(lv_event_t *event) { if (lv_event_get_code(event) != LV_EVENT_CLICKED || gBlockClick) { return; @@ -450,10 +618,14 @@ static void editorKeyCb(lv_event_t *event) { if (strcmp(token, "") == 0) { lv_textarea_del_char(gInputTextArea); + syncEditValueFromTextarea(); + keepCursorAtEnd(); return; } if (strcmp(token, "") == 0) { lv_textarea_set_text(gInputTextArea, ""); + syncEditValueFromTextarea(); + keepCursorAtEnd(); return; } if (strcmp(token, "") == 0) { @@ -465,12 +637,14 @@ static void editorKeyCb(lv_event_t *event) { return; } if (strcmp(token, "") == 0) { + syncEditValueFromTextarea(); gKeyboardMode = (gKeyboardMode == KEYBOARD_MODE_ALPHA) ? KEYBOARD_MODE_SYMBOLS : KEYBOARD_MODE_ALPHA; gKeyboardPage = 0; rebuildScreen(); return; } if (strcmp(token, "") == 0) { + syncEditValueFromTextarea(); gKeyboardShift = !gKeyboardShift; rebuildScreen(); return; @@ -481,7 +655,10 @@ static void editorKeyCb(lv_event_t *event) { out.toUpperCase(); } + keepCursorAtEnd(); lv_textarea_add_text(gInputTextArea, out.c_str()); + syncEditValueFromTextarea(); + keepCursorAtEnd(); } static void actionButtonCb(lv_event_t *event) { @@ -504,6 +681,9 @@ static void actionButtonCb(lv_event_t *event) { case ACTION_OPEN_ACCOUNT: showScreen(SCREEN_ACCOUNT); break; + case ACTION_BACK_ACCOUNT: + showScreen(SCREEN_ACCOUNT); + break; case ACTION_BACK_HOME: showScreen(SCREEN_HOME); break; @@ -524,7 +704,7 @@ static void actionButtonCb(lv_event_t *event) { openEditor(EDIT_CONTEXT_SOLANA_RPC, SCREEN_SERVER, "EDIT SOLANA RPC", - "Cursor starts at the end.", + "", gSolanaRpcUrl, false); break; @@ -532,10 +712,38 @@ static void actionButtonCb(lv_event_t *event) { openEditor(EDIT_CONTEXT_SHINE_SERVER, SCREEN_SERVER, "EDIT SHINE HOST", - "Cursor starts at the end.", + "", gShineServerUrl, false); break; + case ACTION_ACCOUNT_EDIT_LOGIN: + openEditor(EDIT_CONTEXT_LOGIN, + SCREEN_ACCOUNT, + "EDIT LOGIN", + "", + gLoginValue, + false); + break; + case ACTION_ACCOUNT_EDIT_SUBSERVER: + showScreen(SCREEN_ACCOUNT_SUBSERVER); + break; + case ACTION_ACCOUNT_EDIT_SECRET: + showScreen(SCREEN_ACCOUNT_SECRET); + break; + case ACTION_ACCOUNT_SUBSERVER_USE_DEFAULT: + gSubserverValue = "subserver1"; + saveAccountPrefs(); + gAccountStatusMessage = "Subserver set to subserver1"; + showScreen(SCREEN_ACCOUNT); + break; + case ACTION_ACCOUNT_SUBSERVER_EDIT_MANUAL: + openEditor(EDIT_CONTEXT_SUBSERVER, + SCREEN_ACCOUNT, + "EDIT SUBSERVER", + "", + gSubserverValue, + false); + break; case ACTION_EDITOR_SAVE: applyEditorValue(); break; @@ -569,6 +777,9 @@ static lv_obj_t *makeButton(const char *text, lv_obj_t *label = lv_label_create(btn); lv_label_set_text(label, text); + lv_obj_set_width(label, w - 24); + lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0); + lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP); lv_obj_set_style_text_font(label, font, 0); lv_obj_set_style_text_color(label, lv_color_hex(0xFFFFFF), 0); lv_obj_center(label); @@ -622,17 +833,25 @@ static void drawHome() { setRootStyle(); lv_obj_t *login = lv_label_create(gRoot); - lv_label_set_text(login, "login"); + lv_label_set_text(login, loginDisplayValue().c_str()); lv_obj_set_style_text_font(login, &lv_font_montserrat_18, 0); lv_obj_set_style_text_color(login, lv_color_hex(0xFFFFFF), 0); lv_obj_align(login, LV_ALIGN_TOP_LEFT, 24, 18); lv_obj_t *subserver = lv_label_create(gRoot); - lv_label_set_text(subserver, "subserver1"); + lv_label_set_text(subserver, subserverDisplayValue().c_str()); lv_obj_set_style_text_font(subserver, &lv_font_montserrat_18, 0); lv_obj_set_style_text_color(subserver, lv_color_hex(0xD5DEE7), 0); lv_obj_align_to(subserver, login, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 6); + if (!gSecretConfigured) { + lv_obj_t *secret = lv_label_create(gRoot); + lv_label_set_text(secret, homeSecretStatus().c_str()); + lv_obj_set_style_text_font(secret, &lv_font_montserrat_16, 0); + lv_obj_set_style_text_color(secret, lv_color_hex(0xB8C6D3), 0); + lv_obj_align_to(secret, subserver, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 6); + } + makeTopIcons(); makeTitle("STATUS", 150, &lv_font_montserrat_28); showMessageAt(wifiHomeStatus(), 204); @@ -747,10 +966,38 @@ static void drawServerScreen() { static void drawAccountScreen() { setRootStyle(); - makeTitle("ACCOUNT", 56, &lv_font_montserrat_28); - makeBody("Account screen", 132, 400); - makeBody("Swipe right to return to Settings.", 204, 400); - makeButton("BACK", 140, 344, 200, 72, 0x5A6570, ACTION_BACK_SETTINGS, &lv_font_montserrat_22); + + makeTitle("ACCOUNT", 18, &lv_font_montserrat_24); + showMessageAt(gAccountStatusMessage, 56); + + String loginButton = String("Login (") + (gLoginValue.isEmpty() ? "not set" : gLoginValue) + ")"; + String subserverButton = String("Subserver (") + (gSubserverValue.isEmpty() ? "not set" : gSubserverValue) + ")"; + String secretButton = String("Secret (") + secretButtonValue() + ")"; + + makeButton(loginButton.c_str(), 22, 118, 436, 84, 0x355C7D, ACTION_ACCOUNT_EDIT_LOGIN, &lv_font_montserrat_20); + makeButton(subserverButton.c_str(), 22, 222, 436, 84, 0x355C7D, ACTION_ACCOUNT_EDIT_SUBSERVER, &lv_font_montserrat_20); + makeButton(secretButton.c_str(), 22, 326, 436, 84, 0x355C7D, ACTION_ACCOUNT_EDIT_SECRET, &lv_font_montserrat_20); + makeBody("Swipe right to return to Settings.", 420, 420); + makeVersionTag(); +} + +static void drawAccountSubserverScreen() { + setRootStyle(); + makeTitle("SUBSERVER", 18, &lv_font_montserrat_24); + showMessageAt(String("Current: ") + subserverDisplayValue(), 56); + makeBody("If you only use one subserver, keep the default name subserver1.", 98, 420); + makeButton("USE SUBSERVER1", 22, 202, 436, 84, 0x2A9D8F, ACTION_ACCOUNT_SUBSERVER_USE_DEFAULT, &lv_font_montserrat_22); + makeButton("EDIT MANUALLY", 22, 306, 436, 84, 0x355C7D, ACTION_ACCOUNT_SUBSERVER_EDIT_MANUAL, &lv_font_montserrat_22); + makeButton("BACK", 140, 402, 200, 54, 0x5A6570, ACTION_BACK_ACCOUNT, &lv_font_montserrat_20); + makeVersionTag(); +} + +static void drawAccountSecretScreen() { + setRootStyle(); + makeTitle("SECRET", 18, &lv_font_montserrat_24); + showMessageAt(String("Status: ") + (gSecretConfigured ? "set" : "not set"), 56); + makeBody("Secret setup is not implemented yet.", 116, 420); + makeButton("BACK", 140, 360, 200, 72, 0x5A6570, ACTION_BACK_ACCOUNT, &lv_font_montserrat_22); makeVersionTag(); } @@ -788,14 +1035,14 @@ static void drawTextEditorKeyboard(lv_coord_t originY) { static const char *symRightRow1[] = {"?", "&", "=", "+", "-"}; static const char *symRightRow2[] = {"%", "#", "~", ":", "/"}; - const lv_coord_t keyH = 50; + const lv_coord_t keyH = 62; const lv_coord_t keyW5 = 84; - const lv_coord_t keyW4 = 104; + const lv_coord_t keyW4 = 84; const lv_coord_t gap = 8; const lv_coord_t row0Y = originY; - const lv_coord_t row1Y = originY + 58; - const lv_coord_t row2Y = originY + 116; - const lv_coord_t row3Y = originY + 180; + const lv_coord_t row1Y = originY + 68; + const lv_coord_t row2Y = originY + 136; + const lv_coord_t row3Y = originY + 208; const bool uppercase = gKeyboardMode == KEYBOARD_MODE_ALPHA && gKeyboardShift; if (gKeyboardMode == KEYBOARD_MODE_ALPHA) { @@ -820,11 +1067,11 @@ static void drawTextEditorKeyboard(lv_coord_t originY) { } } - makeKeyboardButton(gKeyboardMode == KEYBOARD_MODE_ALPHA ? "123" : "ABC", "", 20, row3Y, 88, 54, 0x5C6F82, &lv_font_montserrat_18); - makeKeyboardButton(gKeyboardShift ? "SHIFT*" : "SHIFT", "", 116, row3Y, 88, 54, 0x7A5C9B, &lv_font_montserrat_16); - makeKeyboardButton("DEL", "", 212, row3Y, 76, 54, 0x7D3A3A, &lv_font_montserrat_18); - makeKeyboardButton("SAVE", "", 296, row3Y, 76, 54, 0x2A9D8F, &lv_font_montserrat_18); - makeKeyboardButton("CANCEL", "", 380, row3Y, 80, 54, 0x5A6570, &lv_font_montserrat_16); + makeKeyboardButton(gKeyboardMode == KEYBOARD_MODE_ALPHA ? "123" : "ABC", "", 20, row3Y, 88, 64, 0x5C6F82, &lv_font_montserrat_18); + makeKeyboardButton(gKeyboardShift ? "SHIFT*" : "SHIFT", "", 116, row3Y, 88, 64, 0x7A5C9B, &lv_font_montserrat_16); + makeKeyboardButton("DEL", "", 212, row3Y, 76, 64, 0x7D3A3A, &lv_font_montserrat_18); + makeKeyboardButton("SAVE", "", 296, row3Y, 76, 64, 0x2A9D8F, &lv_font_montserrat_18); + makeKeyboardButton("CANCEL", "", 380, row3Y, 80, 64, 0x5A6570, &lv_font_montserrat_16); } static void drawTextEditScreen() { @@ -839,13 +1086,21 @@ static void drawTextEditScreen() { lv_textarea_set_one_line(gInputTextArea, true); lv_textarea_set_text(gInputTextArea, gEditValue.c_str()); lv_textarea_set_cursor_pos(gInputTextArea, LV_TEXTAREA_CURSOR_LAST); - lv_textarea_set_password_mode(gInputTextArea, gEditIsPassword); + lv_textarea_set_password_mode(gInputTextArea, false); + lv_textarea_set_cursor_click_pos(gInputTextArea, false); + lv_textarea_set_text_selection(gInputTextArea, false); lv_obj_set_style_text_font(gInputTextArea, &lv_font_montserrat_18, 0); lv_obj_set_style_bg_color(gInputTextArea, lv_color_hex(0x132131), 0); lv_obj_set_style_text_color(gInputTextArea, lv_color_hex(0xFFFFFF), 0); lv_obj_set_style_border_color(gInputTextArea, lv_color_hex(0x6E8AA3), 0); lv_obj_set_style_border_width(gInputTextArea, 2, 0); + lv_obj_set_style_bg_opa(gInputTextArea, LV_OPA_TRANSP, LV_PART_CURSOR); + lv_obj_set_style_border_opa(gInputTextArea, LV_OPA_TRANSP, LV_PART_CURSOR); + lv_obj_set_style_outline_opa(gInputTextArea, LV_OPA_TRANSP, LV_PART_CURSOR); + lv_obj_set_style_pad_left(gInputTextArea, 12, 0); + lv_obj_set_style_pad_right(gInputTextArea, 12, 0); lv_obj_add_state(gInputTextArea, LV_STATE_FOCUSED); + restoreTextareaFromEditValue(); lv_obj_t *panel = lv_obj_create(gRoot); lv_obj_set_size(panel, TEXT_EDIT_PANEL_W, TEXT_EDIT_PANEL_H); @@ -857,7 +1112,7 @@ static void drawTextEditScreen() { lv_obj_set_style_border_color(panel, lv_color_hex(0x2E455A), 0); lv_obj_clear_flag(panel, LV_OBJ_FLAG_SCROLLABLE); - drawTextEditorKeyboard(TEXT_EDIT_PANEL_Y + 10); + drawTextEditorKeyboard(TEXT_EDIT_PANEL_Y + 8); makeVersionTag(); } @@ -885,6 +1140,12 @@ static void rebuildScreen() { case SCREEN_ACCOUNT: drawAccountScreen(); break; + case SCREEN_ACCOUNT_SUBSERVER: + drawAccountSubserverScreen(); + break; + case SCREEN_ACCOUNT_SECRET: + drawAccountSecretScreen(); + break; case SCREEN_TEXT_EDIT: drawTextEditScreen(); break; @@ -945,10 +1206,17 @@ static void handleAccountSwipe(SwipeDirection swipe) { } } +static void handleAccountSubscreenSwipe(SwipeDirection swipe) { + if (swipe == SWIPE_RIGHT) { + showScreen(SCREEN_ACCOUNT); + } +} + static void handleTextEditSwipe(SwipeDirection swipe) { if (!isTextEditKeyboardSwipeArea(gTouchStartX, gTouchStartY)) { return; } + syncEditValueFromTextarea(); if (swipe == SWIPE_LEFT) { gKeyboardPage = min(gKeyboardPage + 1, 1); rebuildScreen(); @@ -986,6 +1254,10 @@ static void handleSwipe(SwipeDirection swipe) { case SCREEN_ACCOUNT: handleAccountSwipe(swipe); break; + case SCREEN_ACCOUNT_SUBSERVER: + case SCREEN_ACCOUNT_SECRET: + handleAccountSubscreenSwipe(swipe); + break; case SCREEN_TEXT_EDIT: handleTextEditSwipe(swipe); break; @@ -1010,6 +1282,7 @@ void setup() { gPrefs.begin("shine-nav-ui", false); loadPrefs(); beginSavedWifi(); + gLastWifiStatus = WiFi.status(); lv_init(); @@ -1047,6 +1320,7 @@ void setup() { void loop() { lv_timer_handler(); + manageWifiReconnect(); if (gPendingSwipe != SWIPE_NONE) { SwipeDirection swipe = gPendingSwipe; diff --git a/VERSION.properties b/VERSION.properties index df68587..e599638 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.143 -server.version=1.2.135 +client.version=1.2.144 +server.version=1.2.136