ESP32: ужесточить touch UX и обновить инструкции

This commit is contained in:
AidarKC 2026-06-09 18:30:12 +04:00
parent b5276890fb
commit 471fde78c1
5 changed files with 46 additions and 12 deletions

View File

@ -24,6 +24,13 @@
- Подробные служебные правила Telegram-обработчика, его очередь, история, systemd-запуск и особенности ответов описывать в `SHiNE-agent-bot-coder/AGENT.md`.
- Если в сообщениях пользователя встречается «агент MD» или похожая формулировка про файл инструкций Codex, считать, что имеется в виду автоматически читаемый `AGENTS.md`.
## ESP32 UI сабсервера
- Для UI-скетча устройства `ESP32-S3-Touch-AMOLED-2.16` документ-спецификация и Arduino-скетч должны всегда оставаться синхронными.
- Актуальный документ по экранной логике, состояниям, кнопкам, полям, статусам и ограничениям UI считать источником истины для скетча.
- При изменении документа обязательно в том же наборе изменений приводить в соответствие скетч.
- При изменении скетча обязательно в том же наборе изменений обновлять документ, если поменялись экраны, тексты, переходы, статусы, кнопки, поля или поведение.
- Для нового ESP32 UI-прототипа сабсервера использовать русский язык как основной и отдельно следить, чтобы текст реально отображался на устройстве, а не только логически присутствовал в коде.
## Solana-модуль
- В проекте есть отдельный Solana/Anchor-модуль в папке `shine-solana/shine/`.
- Модуль логически связан с SHiNE, но не должен автоматически подключаться к сборке или деплою основного сервера без отдельного решения.

View File

@ -34,7 +34,10 @@
- визуальный курсор в поле ввода не показывается;
- новые символы всегда дописываются только в конец строки;
- основные 3 ряда клавиш и нижний служебный ряд стали выше;
- внизу остаётся отдельная тёмная полоса с версией `NAV v7`, а рамка клавиатурного блока заканчивается выше неё;
- внизу остаётся отдельная тёмная полоса с версией `SHiNE subserver (v.0.18)`, а рамка клавиатурного блока заканчивается выше неё;
- одно непрерывное касание вызывает не более одного действия кнопки;
- скольжение пальцем по клавиатуре не нажимает подряд несколько клавиш;
- медленный свайп по экрану не должен превращаться в случайное нажатие кнопки;
- `ABC/123`, `SHIFT`, `DEL`, `SAVE`, `CANCEL` работают;
- при успехе SSID и пароль сохраняются, а `HOME` показывает `Wi-Fi connected`;
- при ошибке показывается `Connection failed`;

View File

@ -253,7 +253,7 @@
- `DEL`
- `SAVE`
- `CANCEL`
- ниже рамки клавиатурного блока остаётся отдельная тёмная полоса с версией `NAV v7`.
- ниже рамки клавиатурного блока остаётся отдельная тёмная полоса с версией `SHiNE subserver (v.0.18)`.
## Жесты
@ -279,6 +279,9 @@
- если палец ушёл из зоны обычного тапа и жест определяется как свайп, случайное попадание на кнопку по пути не должно превращать свайп в нажатие;
- приоритет у свайпа, а не у `click`.
- одно непрерывное касание может вызвать не более одного действия кнопки;
- скольжение пальцем по экранной клавиатуре не должно вызывать цепочку из нескольких нажатий;
- периодический refresh на `HOME` не должен срабатывать во время активного касания.
## Язык

View File

@ -137,6 +137,9 @@ static int16_t gTouchLastX = 0;
static int16_t gTouchLastY = 0;
static SwipeDirection gPendingSwipe = SWIPE_NONE;
static bool gBlockClick = false;
static bool gSuppressTouchUntilRelease = false;
static uint32_t gTouchSequence = 0;
static uint32_t gLastHandledTouchSequence = 0;
static String gWifiSavedSsid;
static String gWifiSavedPassword;
@ -257,13 +260,23 @@ static void lvglTouchRead(lv_indev_drv_t *indevDrv, lv_indev_data_t *data) {
if (touching) {
if (!gTouchDown) {
gTouchDown = true;
gTouchSequence++;
gTouchStartX = x;
gTouchStartY = y;
gBlockClick = false;
gSuppressTouchUntilRelease = false;
}
gTouchLastX = x;
gTouchLastY = y;
if (movedTooFarForTap(gTouchStartX, gTouchStartY, gTouchLastX, gTouchLastY)) {
gBlockClick = true;
gSuppressTouchUntilRelease = true;
}
if (gSuppressTouchUntilRelease) {
data->state = LV_INDEV_STATE_REL;
data->point.x = gTouchLastX;
data->point.y = gTouchLastY;
return;
}
data->state = LV_INDEV_STATE_PR;
data->point.x = x;
@ -274,11 +287,11 @@ static void lvglTouchRead(lv_indev_drv_t *indevDrv, lv_indev_data_t *data) {
if (gTouchDown) {
gTouchDown = false;
gPendingSwipe = detectSwipe(gTouchStartX, gTouchStartY, gTouchLastX, gTouchLastY);
if (gPendingSwipe == SWIPE_NONE && !movedTooFarForTap(gTouchStartX, gTouchStartY, gTouchLastX, gTouchLastY)) {
gBlockClick = false;
}
}
gSuppressTouchUntilRelease = false;
gBlockClick = false;
data->state = LV_INDEV_STATE_REL;
data->point.x = gTouchLastX;
data->point.y = gTouchLastY;
@ -800,9 +813,12 @@ static void restoreTextareaFromEditValue() {
}
static void networkSelectCb(lv_event_t *event) {
if (lv_event_get_code(event) != LV_EVENT_CLICKED || gBlockClick) {
if (lv_event_get_code(event) != LV_EVENT_CLICKED || gBlockClick || gLastHandledTouchSequence == gTouchSequence) {
return;
}
gLastHandledTouchSequence = gTouchSequence;
gSuppressTouchUntilRelease = true;
gBlockClick = true;
int index = static_cast<int>(reinterpret_cast<uintptr_t>(lv_event_get_user_data(event)));
if (index < 0 || index >= gScanResultCount) {
@ -819,9 +835,12 @@ static void networkSelectCb(lv_event_t *event) {
}
static void editorKeyCb(lv_event_t *event) {
if (lv_event_get_code(event) != LV_EVENT_CLICKED || gBlockClick || !gInputTextArea) {
if (lv_event_get_code(event) != LV_EVENT_CLICKED || gBlockClick || gLastHandledTouchSequence == gTouchSequence || !gInputTextArea) {
return;
}
gLastHandledTouchSequence = gTouchSequence;
gSuppressTouchUntilRelease = true;
gBlockClick = true;
const char *token = static_cast<const char *>(lv_event_get_user_data(event));
if (!token) {
@ -874,9 +893,12 @@ static void editorKeyCb(lv_event_t *event) {
}
static void actionButtonCb(lv_event_t *event) {
if (lv_event_get_code(event) != LV_EVENT_CLICKED || gBlockClick) {
if (lv_event_get_code(event) != LV_EVENT_CLICKED || gBlockClick || gLastHandledTouchSequence == gTouchSequence) {
return;
}
gLastHandledTouchSequence = gTouchSequence;
gSuppressTouchUntilRelease = true;
gBlockClick = true;
ActionId action = static_cast<ActionId>(reinterpret_cast<uintptr_t>(lv_event_get_user_data(event)));
switch (action) {
@ -1665,7 +1687,7 @@ void loop() {
manageWifiReconnect();
static unsigned long lastHomeRefreshMs = 0;
if (gCurrentScreen == SCREEN_HOME && millis() - lastHomeRefreshMs >= 1000) {
if (gCurrentScreen == SCREEN_HOME && !gTouchDown && millis() - lastHomeRefreshMs >= 1000) {
lastHomeRefreshMs = millis();
rebuildScreen();
}
@ -1690,7 +1712,6 @@ void loop() {
SwipeDirection swipe = gPendingSwipe;
gPendingSwipe = SWIPE_NONE;
handleSwipe(swipe);
gBlockClick = false;
}
delay(5);

View File

@ -1,2 +1,2 @@
client.version=1.2.146
server.version=1.2.138
client.version=1.2.147
server.version=1.2.139