From 423d490939eeb87a1162f0944623a81750e4e2b3e681cef9953a57f7fb05cfbd Mon Sep 17 00:00:00 2001 From: AidarKC Date: Sun, 14 Jun 2026 10:50:31 +0400 Subject: [PATCH] =?UTF-8?q?ESP32:=20=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D1=82=D1=8C=20home=20=D1=8D=D0=BA=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=20=D0=B8=20wallet=20QR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-06-14_1450_esp32_wallet_qr_lvgl.md | 6 +- ...026-06-14_1535_esp32_home_status_layout.md | 29 +++++ .../shine_homeserver_main.ino | 110 ++++++++++++++---- .../reference/shine_homeserver_ui_spec.md | 22 +++- VERSION.properties | 4 +- 5 files changed, 142 insertions(+), 29 deletions(-) create mode 100644 Dev_Docs/Pending_Features/2026-06-14_1535_esp32_home_status_layout.md diff --git a/Dev_Docs/Pending_Features/2026-06-14_1450_esp32_wallet_qr_lvgl.md b/Dev_Docs/Pending_Features/2026-06-14_1450_esp32_wallet_qr_lvgl.md index 63d70bc..5c0cd22 100644 --- a/Dev_Docs/Pending_Features/2026-06-14_1450_esp32_wallet_qr_lvgl.md +++ b/Dev_Docs/Pending_Features/2026-06-14_1450_esp32_wallet_qr_lvgl.md @@ -6,7 +6,8 @@ - для `WALLET_QR` включён штатный `LVGL`-виджет `lv_qrcode`; - кнопка на главном экране оставлена текстовой `QR`; -- экран должен показывать реальный `QR` для `solana:`; +- экран должен показывать реальный крупный `QR` для `solana:`; +- адрес кошелька под `QR` увеличен; - тап по экрану должен возвращать на `HOME`, без перезагрузки устройства и без потери `Wi-Fi`/`SHiNE`. ## Что проверять @@ -14,7 +15,7 @@ 1. На главном экране нажать кнопку `QR`. 2. Убедиться, что открывается экран `WALLET QR`. 3. Проверить, что виден настоящий QR-код. -4. Проверить, что внизу мелким текстом показан адрес кошелька. +4. Проверить, что под `QR` крупно показан адрес кошелька. 5. Нажать в любое место экрана и убедиться, что устройство возвращается на `HOME`. 6. Убедиться, что после открытия и закрытия QR-экрана не рвутся `Wi-Fi` и подключение к `SHiNE`. @@ -22,5 +23,6 @@ - QR-экран открывается стабильно; - QR-код читается приложением кошелька; +- размер `QR` и адреса визуально достаточен для удобного сканирования; - возврат на главный экран работает обычным тапом; - устройство не перезагружается, сетевые подключения не теряются. diff --git a/Dev_Docs/Pending_Features/2026-06-14_1535_esp32_home_status_layout.md b/Dev_Docs/Pending_Features/2026-06-14_1535_esp32_home_status_layout.md new file mode 100644 index 0000000..085b559 --- /dev/null +++ b/Dev_Docs/Pending_Features/2026-06-14_1535_esp32_home_status_layout.md @@ -0,0 +1,29 @@ +# ESP32 home status layout и авто-баланс + +- Статус: `pending` + +## Что сделано + +- на `HOME` блок `STATUS` поднят выше; +- строка логина теперь идёт после статусного кружка, сам кружок расположен слева; +- строка `Wi-Fi` показывает `Wi-Fi: connected|disconnected`; +- строка `SHiNE` показывает `SHiNE: connected|unavailable`; +- слово `connected` на обеих строках окрашивается зелёным; +- текст на кнопке баланса сдвинут левее; +- после старта устройство автоматически пытается подгрузить баланс при готовых секрете и `Wi-Fi`; +- в `SETTINGS` первые три пункта переименованы в `1. Wi-Fi`, `2. Server`, `3. Account`. + +## Что проверять + +1. Перезагрузить устройство. +2. Проверить, что на `HOME` баланс подгружается сам, без нажатия на кнопку. +3. Проверить, что строки `Wi-Fi` и `SHiNE` читаются и слово `connected` зелёное при хорошем состоянии. +4. Проверить, что кружок аккаунта расположен слева от логина. +5. Открыть `SETTINGS` и убедиться, что первые три пункта нумеруются как `1. Wi-Fi`, `2. Server`, `3. Account`. + +## Ожидаемый результат + +- `HOME` выглядит компактнее и читаемее; +- баланс после старта появляется автоматически; +- статусные строки не перекрываются; +- меню `SETTINGS` показывает нужную нумерацию. diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino index a04ac43..9e38eb4 100644 --- a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino @@ -224,7 +224,7 @@ struct SimpleWebSocketClient { uint16_t port = 0; }; -static const char *kMenuItems[] = {"Wi-Fi", "Server", "Account"}; +static const char *kMenuItems[] = {"1. Wi-Fi", "2. Server", "3. Account"}; static const size_t kMenuCount = sizeof(kMenuItems) / sizeof(kMenuItems[0]); static lv_disp_draw_buf_t gDrawBuf; @@ -276,6 +276,8 @@ static String gSecretBase58; static uint8_t gSecretBytes[32] = {}; static String gAccountStatusMessage = "Edit account fields"; static String gBalanceStatusMessage = "Balance: tap to load"; +static bool gBalanceAutoRefreshPending = false; +static unsigned long gLastBalanceAutoRefreshAttemptMs = 0; static bool gWifiKnownGood = false; static bool gWifiReconnectEnabled = false; static bool gWifiOperationBusy = false; @@ -1143,6 +1145,25 @@ static String balanceHomeLine() { return gBalanceStatusMessage; } +static String wifiHomeRichLine() { + String ssid = gWifiSavedSsid.isEmpty() ? String("not configured") : gWifiSavedSsid; + if (WiFi.status() == WL_CONNECTED) { + return String("Wi-Fi: ") + ssid + " #38B26D connected#"; + } + return String("Wi-Fi: ") + ssid + " disconnected"; +} + +static String shineHomeRichLine() { + String serverLabel = gShineServerUrl.isEmpty() ? String("not set") : gShineServerUrl; + if (gShineStatusLine.endsWith(" connected")) { + return String("SHiNE: ") + serverLabel + " #38B26D connected#"; + } + if (gShineStatusLine.endsWith(" account not configured")) { + return String("SHiNE: ") + serverLabel + " account not configured"; + } + return String("SHiNE: ") + serverLabel + " unavailable"; +} + static String walletQrUri() { if (gDevicePubB58.isEmpty()) { return ""; @@ -3446,6 +3467,8 @@ static void loadPrefs() { loadRegisterDiagDetailsFromPrefs(); gLastRegisterDiagTime = gPrefs.getString("reg_diag_time", ""); gBalanceStatusMessage = gDevicePubB58.isEmpty() ? "Balance: secret not set" : "Balance: tap to load"; + gBalanceAutoRefreshPending = !gDevicePubB58.isEmpty(); + gLastBalanceAutoRefreshAttemptMs = 0; gAccountCheckPending = true; gLastAccountCheckMs = 0; gAccountPdaStatus = ACCOUNT_PDA_UNKNOWN; @@ -3809,6 +3832,8 @@ static void clearSecretValue() { memset(gSecretBytes, 0, sizeof(gSecretBytes)); refreshDerivedKeys(); gBalanceStatusMessage = "Balance: secret not set"; + gBalanceAutoRefreshPending = false; + gLastBalanceAutoRefreshAttemptMs = 0; saveAccountPrefs(); markAccountStateDirty(); } @@ -3821,6 +3846,8 @@ static void setSecretValue(const uint8_t *bytes32) { gSecretConfigured = true; refreshDerivedKeys(); gBalanceStatusMessage = "Balance: tap to load"; + gBalanceAutoRefreshPending = true; + gLastBalanceAutoRefreshAttemptMs = 0; saveAccountPrefs(); markAccountStateDirty(); } @@ -4382,6 +4409,39 @@ static lv_obj_t *makeButton(const char *text, return btn; } +static lv_obj_t *makeButtonLeftText(const char *text, + lv_coord_t x, + lv_coord_t y, + lv_coord_t w, + lv_coord_t h, + uint32_t bgColor, + ActionId action, + const lv_font_t *font = &lv_font_montserrat_22) { + lv_obj_t *btn = makeButton(text, x, y, w, h, bgColor, action, font); + lv_obj_t *label = lv_obj_get_child(btn, 0); + if (label) { + lv_obj_set_width(label, w - 40); + lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_LEFT, 0); + lv_obj_align(label, LV_ALIGN_LEFT_MID, 20, 0); + } + return btn; +} + +static void makeRichStatusLine(const String &text, + lv_coord_t x, + lv_coord_t y, + lv_coord_t w, + const lv_font_t *font = &lv_font_montserrat_18) { + lv_obj_t *label = lv_label_create(gRoot); + lv_label_set_recolor(label, true); + lv_label_set_text(label, text.c_str()); + lv_obj_set_width(label, w); + 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(0xD5DEE7), 0); + lv_obj_set_pos(label, x, y); +} + static lv_obj_t *makeKeyboardButton(const char *label, const char *token, lv_coord_t x, @@ -4431,11 +4491,11 @@ static void drawHome() { lv_label_set_text(login, loginDisplayValue().c_str()); lv_obj_set_style_text_font(login, &lv_font_montserrat_22, 0); lv_obj_set_style_text_color(login, lv_color_hex(0xD5DEE7), 0); - lv_obj_align_to(login, homeserver, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 6); + lv_obj_align_to(login, homeserver, LV_ALIGN_OUT_BOTTOM_LEFT, 22, 6); lv_obj_t *accountDot = lv_obj_create(gRoot); lv_obj_set_size(accountDot, 14, 14); - lv_obj_set_pos(accountDot, 250, 52); + lv_obj_set_pos(accountDot, 24, 52); lv_obj_set_style_radius(accountDot, 7, 0); lv_obj_set_style_border_width(accountDot, 2, 0); lv_obj_set_style_bg_opa(accountDot, LV_OPA_COVER, 0); @@ -4465,7 +4525,7 @@ static void drawHome() { lv_label_set_text(accountStatus, gAccountPdaStatusMessage.c_str()); lv_obj_set_style_text_font(accountStatus, &lv_font_montserrat_14, 0); lv_obj_set_style_text_color(accountStatus, lv_color_hex(statusColor), 0); - lv_obj_set_pos(accountStatus, 272, 48); + lv_obj_set_pos(accountStatus, 24, 80); } if (!gSecretConfigured) { @@ -4477,11 +4537,11 @@ static void drawHome() { } drawTopStatusIndicators(); - makeTitle("STATUS", 138, &lv_font_montserrat_28); - showMessageAt(wifiHomeSummary(), 214); - makeButton(balanceHomeLine().c_str(), 22, 254, 340, 56, 0x355C7D, ACTION_REFRESH_BALANCE, &lv_font_montserrat_18); + makeTitle("STATUS", 112, &lv_font_montserrat_28); + makeRichStatusLine(wifiHomeRichLine(), 24, 164, 432, &lv_font_montserrat_18); + makeRichStatusLine(shineHomeRichLine(), 24, 202, 432, &lv_font_montserrat_16); + makeButtonLeftText(balanceHomeLine().c_str(), 22, 254, 340, 56, 0x355C7D, ACTION_REFRESH_BALANCE, &lv_font_montserrat_18); makeButton("QR", 374, 254, 84, 56, 0x2A6F97, ACTION_OPEN_WALLET_QR, &lv_font_montserrat_20); - showMessageAt(shineHomeLine(), 322); if (gShowRegisterAccountButton) { makeButton("REGISTER ACCOUNT", 20, 360, 210, 78, 0x2A9D8F, ACTION_REGISTER_ACCOUNT, &lv_font_montserrat_20); } else if (gShowHomeserverPdaActionButton) { @@ -4515,8 +4575,8 @@ static void drawWalletQrScreen() { lv_obj_set_pos(error, 50, 174); } else { lv_obj_t *panel = lv_obj_create(gRoot); - lv_obj_set_size(panel, 300, 220); - lv_obj_set_pos(panel, 90, 88); + lv_obj_set_size(panel, 356, 356); + lv_obj_set_pos(panel, 62, 68); lv_obj_set_style_radius(panel, 18, 0); lv_obj_set_style_bg_color(panel, lv_color_hex(0xFFFFFF), 0); lv_obj_set_style_bg_opa(panel, LV_OPA_COVER, 0); @@ -4524,7 +4584,7 @@ static void drawWalletQrScreen() { lv_obj_set_style_shadow_width(panel, 0, 0); lv_obj_clear_flag(panel, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_t *qr = lv_qrcode_create(panel, 180, lv_color_hex(0x111111), lv_color_hex(0xFFFFFF)); + lv_obj_t *qr = lv_qrcode_create(panel, 320, lv_color_hex(0x111111), lv_color_hex(0xFFFFFF)); if (qr == nullptr) { saveUiErrorDiag("Wallet QR create failed", "stage=create\n"); lv_obj_t *error = lv_label_create(panel); @@ -4558,19 +4618,9 @@ static void drawWalletQrScreen() { lv_obj_set_width(address, 420); lv_label_set_long_mode(address, LV_LABEL_LONG_WRAP); lv_obj_set_style_text_align(address, LV_TEXT_ALIGN_CENTER, 0); - lv_obj_set_style_text_font(address, &lv_font_montserrat_12, 0); + lv_obj_set_style_text_font(address, &lv_font_montserrat_24, 0); lv_obj_set_style_text_color(address, lv_color_hex(0xB8C6D3), 0); - lv_obj_set_pos(address, 30, 344); - - lv_obj_t *hint = lv_label_create(gRoot); - lv_label_set_text(hint, "Tap anywhere to return"); - lv_obj_set_width(hint, 420); - lv_obj_set_style_text_align(hint, LV_TEXT_ALIGN_CENTER, 0); - lv_obj_set_style_text_font(hint, &lv_font_montserrat_14, 0); - lv_obj_set_style_text_color(hint, lv_color_hex(0x8398AD), 0); - lv_obj_set_pos(hint, 30, 410); - - makeVersionTag(); + lv_obj_set_pos(address, 30, 430); } static void drawSettingsMenu() { @@ -5296,6 +5346,20 @@ void loop() { manageAccountPdaRefresh(); manageShineConnection(); + if (gBalanceAutoRefreshPending && gSecretConfigured && WiFi.status() == WL_CONNECTED) { + unsigned long now = millis(); + if (gLastBalanceAutoRefreshAttemptMs == 0 || now - gLastBalanceAutoRefreshAttemptMs >= 15000) { + gLastBalanceAutoRefreshAttemptMs = now; + String message; + if (refreshWalletBalance(message)) { + gBalanceAutoRefreshPending = false; + } + if (gCurrentScreen == SCREEN_HOME) { + rebuildScreen(); + } + } + } + static unsigned long lastHomeRefreshMs = 0; if (gCurrentScreen == SCREEN_HOME && !gTouchDown && millis() - lastHomeRefreshMs >= HOME_REFRESH_MS) { lastHomeRefreshMs = millis(); diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_spec.md b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_spec.md index b3f7f5f..7273343 100644 --- a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_spec.md +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_spec.md @@ -157,14 +157,22 @@ - крупный статус регистрации; - имя логина; - имя homeserver; -- короткий статус Wi-Fi; -- короткий статус сервера; +- строку `Wi-Fi: connected|disconnected`; +- строку `SHiNE: connected|unavailable`; - короткий статус баланса. +Особенности верхнего блока: + +- зелёный/контурный статусный кружок аккаунта расположен слева от строки логина; +- блок `STATUS` поднят выше относительно предыдущей версии; +- если состояние хорошее, слово `connected` в строках `Wi-Fi` и `SHiNE` показывается зелёным. + В зоне баланса: - основная кнопка показа/обновления баланса занимает примерно 80% строки; +- текст на кнопке баланса выровнен левее центра; - справа от неё стоит отдельная кнопка `QR`; +- после старта устройства баланс пытается загрузиться автоматически, если уже есть секрет и `Wi-Fi`; - нажатие на кнопку `QR` открывает экран `WALLET_QR`. Нижние кнопки: @@ -420,6 +428,16 @@ ## Экран WALLET_QR +Экран показывает: + +- крупный реальный `QR` для строки `solana:`; +- снизу крупный текст самого адреса кошелька. + +Поведение: + +- отдельная текстовая подсказка возврата не показывается; +- возврат на главный экран выполняется обычным тапом по экрану. + Показывает: - QR-код для строки вида: diff --git a/VERSION.properties b/VERSION.properties index f92f98e..30a3d30 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.186 -server.version=1.2.175 +client.version=1.2.187 +server.version=1.2.176