ESP32: доработать home экран и wallet QR
This commit is contained in:
parent
7edc0ba901
commit
423d490939
@ -6,7 +6,8 @@
|
||||
|
||||
- для `WALLET_QR` включён штатный `LVGL`-виджет `lv_qrcode`;
|
||||
- кнопка на главном экране оставлена текстовой `QR`;
|
||||
- экран должен показывать реальный `QR` для `solana:<wallet_address>`;
|
||||
- экран должен показывать реальный крупный `QR` для `solana:<wallet_address>`;
|
||||
- адрес кошелька под `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` и адреса визуально достаточен для удобного сканирования;
|
||||
- возврат на главный экран работает обычным тапом;
|
||||
- устройство не перезагружается, сетевые подключения не теряются.
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
# ESP32 home status layout и авто-баланс
|
||||
|
||||
- Статус: `pending`
|
||||
|
||||
## Что сделано
|
||||
|
||||
- на `HOME` блок `STATUS` поднят выше;
|
||||
- строка логина теперь идёт после статусного кружка, сам кружок расположен слева;
|
||||
- строка `Wi-Fi` показывает `Wi-Fi: <SSID> connected|disconnected`;
|
||||
- строка `SHiNE` показывает `SHiNE: <server address> 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` показывает нужную нумерацию.
|
||||
@ -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();
|
||||
|
||||
@ -157,14 +157,22 @@
|
||||
- крупный статус регистрации;
|
||||
- имя логина;
|
||||
- имя homeserver;
|
||||
- короткий статус Wi-Fi;
|
||||
- короткий статус сервера;
|
||||
- строку `Wi-Fi: <SSID> connected|disconnected`;
|
||||
- строку `SHiNE: <server address> connected|unavailable`;
|
||||
- короткий статус баланса.
|
||||
|
||||
Особенности верхнего блока:
|
||||
|
||||
- зелёный/контурный статусный кружок аккаунта расположен слева от строки логина;
|
||||
- блок `STATUS` поднят выше относительно предыдущей версии;
|
||||
- если состояние хорошее, слово `connected` в строках `Wi-Fi` и `SHiNE` показывается зелёным.
|
||||
|
||||
В зоне баланса:
|
||||
|
||||
- основная кнопка показа/обновления баланса занимает примерно 80% строки;
|
||||
- текст на кнопке баланса выровнен левее центра;
|
||||
- справа от неё стоит отдельная кнопка `QR`;
|
||||
- после старта устройства баланс пытается загрузиться автоматически, если уже есть секрет и `Wi-Fi`;
|
||||
- нажатие на кнопку `QR` открывает экран `WALLET_QR`.
|
||||
|
||||
Нижние кнопки:
|
||||
@ -420,6 +428,16 @@
|
||||
|
||||
## Экран WALLET_QR
|
||||
|
||||
Экран показывает:
|
||||
|
||||
- крупный реальный `QR` для строки `solana:<wallet_address>`;
|
||||
- снизу крупный текст самого адреса кошелька.
|
||||
|
||||
Поведение:
|
||||
|
||||
- отдельная текстовая подсказка возврата не показывается;
|
||||
- возврат на главный экран выполняется обычным тапом по экрану.
|
||||
|
||||
Показывает:
|
||||
|
||||
- QR-код для строки вида:
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
client.version=1.2.186
|
||||
server.version=1.2.175
|
||||
client.version=1.2.187
|
||||
server.version=1.2.176
|
||||
|
||||
Loading…
Reference in New Issue
Block a user