Add touch-down and long-press input detection

Implement touch-down event detection to provide immediate visual feedback when touching list items, mirroring button navigation behavior. Add long-press detection (500ms threshold) to distinguish between tap and hold gestures. Apply touch-down selection updates to file browser, home menu, and recent books activities.
This commit is contained in:
Justin Mitchell
2026-06-15 16:12:07 -04:00
parent 9176e76f5a
commit d739827db2
23 changed files with 223 additions and 33 deletions
+4
View File
@@ -245,6 +245,10 @@ unsigned long HalGPIO::getPowerButtonHeldTime() const { return inputMgr.getPower
bool HalGPIO::wasTouchTap(float& nx, float& ny) const { return inputMgr.wasTouchTap(nx, ny); }
bool HalGPIO::wasTouchDown(float& nx, float& ny) const { return inputMgr.wasTouchPressedAt(nx, ny); }
unsigned long HalGPIO::lastTouchHeldMs() const { return inputMgr.lastTouchHeldMs(); }
bool HalGPIO::hasTouch() const { return inputMgr.hasTouch(); }
void HalGPIO::startDeepSleep() {
+8
View File
@@ -77,6 +77,14 @@ class HalGPIO {
// primitive; see MappedInputManager for the top-left = Back mapping.)
bool wasTouchTap(float& nx, float& ny) const;
// Press-edge of a touch: true on touch-down with the down position normalized
// 0..1 (panel native). For showing the pressed/selected element before release.
bool wasTouchDown(float& nx, float& ny) const;
// Duration (ms) of the last touch contact, latched on release. Valid on the
// release frame (alongside wasTouchTap). For tap-vs-long-press decisions.
unsigned long lastTouchHeldMs() const;
// True if a touch controller is present/active (runtime gate; false on the C3).
bool hasTouch() const;
+18
View File
@@ -82,6 +82,24 @@ bool MappedInputManager::wasItemTapped(int& id) const {
return TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Item, id);
}
bool MappedInputManager::wasItemTouchedDown(int& id) const {
float nx = 0.0f, ny = 0.0f;
if (!gpio.wasTouchDown(nx, ny)) return false;
int lx = 0, ly = 0;
renderer.tapToLogical(nx, ny, lx, ly);
return TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Item, id);
}
bool MappedInputManager::wasItemLongPressed(int& id) const {
static constexpr unsigned long TOUCH_LONG_PRESS_MS = 500;
float nx = 0.0f, ny = 0.0f;
if (!gpio.wasTouchTap(nx, ny)) return false; // release frame
if (gpio.lastTouchHeldMs() < TOUCH_LONG_PRESS_MS) return false;
int lx = 0, ly = 0;
renderer.tapToLogical(nx, ny, lx, ly);
return TouchRegistry::getInstance().hitTest(lx, ly, TouchRegistry::Item, id);
}
bool MappedInputManager::wasTabTapped(int& id) const {
float nx = 0.0f, ny = 0.0f;
if (!gpio.wasTouchTap(nx, ny)) return false;
+9
View File
@@ -31,6 +31,15 @@ class MappedInputManager {
// element's id. Activities treat the id as "select + activate". False on
// non-touch devices or when the tap missed every target.
bool wasItemTapped(int& id) const;
// Press-edge analogue of wasItemTapped: fires on touch-DOWN over an item, so the
// activity can move its selection to that item (showing the selected state) before
// release. Release still activates via wasItemTapped. Mirrors button nav (move
// selection, then confirm).
bool wasItemTouchedDown(int& id) const;
// Long-press variant of wasItemTapped: true on release of a touch over an item
// held past the long-press threshold (a subset of wasItemTapped's releases, so
// check this first). Lets a screen distinguish tap vs press-and-hold on touch.
bool wasItemLongPressed(int& id) const;
// Like wasItemTapped, but for tab-bar tabs (id = tab index) and cover/card
// targets (id = item index). Distinct kinds so screens with both a list and a
// tab bar / cover (Home, Settings) don't confuse them.
@@ -206,6 +206,14 @@ void FileBrowserActivity::loop() {
const int pathReserved = renderer.getLineHeight(SMALL_FONT_ID) + UITheme::getInstance().getMetrics().verticalSpacing;
const int pageItems = UITheme::getNumberOfItemsPerPage(renderer, true, false, true, false, pathReserved);
// Touch-down moves the selector to the pressed entry (shows selected state); release
// opens it below.
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(files.size())) {
selectorIndex = downId;
requestUpdate();
}
// A tap opens the tapped entry (held-time is 0 on a tap, so it takes the short-press
// open path below, never the long-press delete).
int tappedId = -1;
+8
View File
@@ -183,6 +183,14 @@ void HomeActivity::loop() {
// menu-local ids (it is drawn with selectorIndex offset by recentBooks.size()),
// so map the tapped id back into the global selector space. (The recent-book
// cover is a separate, single-item draw path — tappable in a later phase.)
// Touch-down moves the selector to the pressed menu button (shows selected state),
// like Up/Down; release opens it below.
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId)) {
selectorIndex = static_cast<int>(recentBooks.size()) + downId;
requestUpdate();
}
int tappedId = -1;
const bool tapped = mappedInput.wasItemTapped(tappedId);
if (tapped) {
@@ -63,6 +63,12 @@ void RecentBooksActivity::loop() {
return;
}
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(recentBooks.size())) {
selectorIndex = downId;
requestUpdate();
}
int tappedId = -1;
const bool tapped = mappedInput.wasItemTapped(tappedId);
if (tapped && tappedId >= 0 && tappedId < static_cast<int>(recentBooks.size())) selectorIndex = tappedId;
@@ -4,13 +4,13 @@
#include <GfxRenderer.h>
#include <I18n.h>
#include <WiFi.h>
#include <esp_task_wdt.h>
#include "MappedInputManager.h"
#include "SilentRestart.h"
#include "WifiSelectionActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "network/TaskWatchdog.h"
namespace {
constexpr const char* HOSTNAME = "crosspoint";
@@ -110,12 +110,12 @@ void CalibreConnectActivity::loop() {
LOG_DBG("CAL", "WARNING: %lu ms gap since last handleClient", timeSinceLastHandleClient);
}
esp_task_wdt_reset();
feedTaskWatchdog();
constexpr int MAX_ITERATIONS = 80;
for (int i = 0; i < MAX_ITERATIONS && webServer->isRunning(); i++) {
webServer->handleClient();
if ((i & 0x07) == 0x07) {
esp_task_wdt_reset();
feedTaskWatchdog();
}
if ((i & 0x0F) == 0x0F) {
yield();
@@ -5,7 +5,6 @@
#include <GfxRenderer.h>
#include <I18n.h>
#include <WiFi.h>
#include <esp_task_wdt.h>
#include <cstddef>
@@ -16,6 +15,7 @@
#include "activities/network/CalibreConnectActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "network/TaskWatchdog.h"
#include "util/QrUtils.h"
namespace {
@@ -328,7 +328,7 @@ void CrossPointWebServerActivity::loop() {
}
// Reset watchdog BEFORE processing - HTTP header parsing can be slow
esp_task_wdt_reset();
feedTaskWatchdog();
// Process HTTP requests in tight loop for maximum throughput
// More iterations = more data processed per main loop cycle
@@ -337,7 +337,7 @@ void CrossPointWebServerActivity::loop() {
webServer->handleClient();
// Reset watchdog every 32 iterations
if ((i & 0x1F) == 0x1F) {
esp_task_wdt_reset();
feedTaskWatchdog();
}
// Yield and check for exit button every 64 iterations
if ((i & 0x3F) == 0x3F) {
@@ -30,6 +30,12 @@ void NetworkModeSelectionActivity::loop() {
return;
}
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(MENU_ITEM_COUNT)) {
selectedIndex = downId;
requestUpdate();
}
// Handle confirm button (or a tap) - select current option
int tappedId = -1;
const bool tapped = mappedInput.wasItemTapped(tappedId);
@@ -409,6 +409,19 @@ void WifiSelectionActivity::loop() {
// Handle network list state
if (state == WifiSelectionState::NETWORK_LIST) {
// Touch: down-select highlights the pressed network, tap selects it (like Confirm).
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(networks.size())) {
selectedNetworkIndex = downId;
requestUpdate();
}
int tappedId = -1;
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0 && tappedId < static_cast<int>(networks.size())) {
selectedNetworkIndex = tappedId;
selectNetwork(selectedNetworkIndex);
return;
}
// Check for Back button to exit (cancel)
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
onComplete(false);
@@ -103,6 +103,14 @@ void EpubReaderBookmarksActivity::loop() {
}
}
if (confirmingDelete < DELETE_MODE_DISPLAY) {
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(bookmarks.size())) {
selectorIndex = downId;
requestUpdate();
}
}
int tappedId = -1;
const bool tapped = (confirmingDelete < DELETE_MODE_DISPLAY) && mappedInput.wasItemTapped(tappedId);
if (tapped && tappedId >= 0 && tappedId < static_cast<int>(bookmarks.size())) selectorIndex = tappedId;
@@ -31,6 +31,12 @@ void EpubReaderChapterSelectionActivity::loop() {
const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false);
const int totalItems = getTotalItems();
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < totalItems) {
selectorIndex = downId;
requestUpdate();
}
int tappedId = -1;
const bool tapped = mappedInput.wasItemTapped(tappedId);
if (tapped && tappedId >= 0 && tappedId < totalItems) selectorIndex = tappedId;
@@ -57,6 +57,14 @@ void EpubReaderMenuActivity::loop() {
requestUpdate();
});
// Touch-down moves the selection to the pressed item (shows selected state), like
// moving with Up/Down; release activates it below.
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < static_cast<int>(menuItems.size())) {
selectedIndex = downId;
requestUpdate();
}
// A tap selects the item and activates it in one gesture (falls into Confirm below).
int tappedId = -1;
const bool tapped = mappedInput.wasItemTapped(tappedId);
@@ -54,6 +54,12 @@ void FontSelectionActivity::loop() {
return;
}
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0) {
selectedIndex_ = downId;
requestUpdate();
}
int tappedId = -1;
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0) {
selectedIndex_ = tappedId;
@@ -32,6 +32,12 @@ void LanguageSelectActivity::loop() {
return;
}
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0) {
selectedIndex = downId;
requestUpdate();
}
int tappedId = -1;
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0) {
selectedIndex = tappedId;
@@ -41,6 +41,12 @@ void OpdsServerListActivity::loop() {
return;
}
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0) {
selectedIndex = downId;
requestUpdate();
}
int tappedId = -1;
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0) {
selectedIndex = tappedId;
@@ -118,6 +118,14 @@ void SettingsActivity::loop() {
// A tap on a settings row selects + activates it in one gesture. The list is drawn
// with selectedIndex = selectedSettingIndex - 1 (row 0 is the category tab), so map
// the tapped 0-based row back by +1. (Category tab bar is tappable in a later phase.)
// Touch-down moves the selection to the pressed row (shows selected state); release
// toggles/activates it below. (Row 0 is the tab bar, so settings list id 0 -> index 1.)
int downId = -1;
if (mappedInput.wasItemTouchedDown(downId) && downId >= 0 && downId < settingsCount) {
selectedSettingIndex = downId + 1;
requestUpdate();
}
int tappedId = -1;
if (mappedInput.wasItemTapped(tappedId) && tappedId >= 0 && tappedId < settingsCount) {
selectedSettingIndex = tappedId + 1;
@@ -133,6 +141,7 @@ void SettingsActivity::loop() {
selectedCategoryIndex = tabId;
selectedSettingIndex = 0;
hasChangedCategory = true;
requestUpdate();
}
// Handle actions with early return
@@ -3,6 +3,7 @@
#include <I18n.h>
#include "HalDisplay.h"
#include "components/TouchRegistry.h"
#include "components/UITheme.h"
ConfirmationActivity::ConfirmationActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
@@ -46,8 +47,26 @@ void ConfirmationActivity::render(RenderLock&& lock) {
// Draw Body
if (!safeBody.empty()) {
renderer.drawCenteredText(fontId, currentY, safeBody.c_str(), true, EpdFontFamily::REGULAR);
currentY += lineHeight;
}
// On-screen Cancel / Confirm buttons (also tappable). The footer hints below map
// the same actions to Left/Right for button devices, but on touch-only devices
// the hints are hidden, so these are the only affordance.
const int btnH = lineHeight + 20;
const int totalW = renderer.getScreenWidth() - margin * 2;
const int btnW = (totalW - spacing) / 2;
const int btnY = currentY + spacing * 3;
const Rect cancelRect{margin, btnY, btnW, btnH};
const Rect confirmRect{margin + btnW + spacing, btnY, btnW, btnH};
renderer.drawRect(cancelRect.x, cancelRect.y, cancelRect.width, cancelRect.height);
renderer.drawRect(confirmRect.x, confirmRect.y, confirmRect.width, confirmRect.height);
const int btnTextY = btnY + (btnH - lineHeight) / 2;
UITheme::drawCenteredText(renderer, cancelRect, fontId, btnTextY, I18N.get(StrId::STR_CANCEL));
UITheme::drawCenteredText(renderer, confirmRect, fontId, btnTextY, I18N.get(StrId::STR_CONFIRM));
TouchRegistry::getInstance().add(cancelRect, 0, TouchRegistry::Item);
TouchRegistry::getInstance().add(confirmRect, 1, TouchRegistry::Item);
// Draw UI Elements
const auto labels = mappedInput.mapLabels("", "", I18N.get(StrId::STR_CANCEL), I18N.get(StrId::STR_CONFIRM));
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
@@ -56,6 +75,16 @@ void ConfirmationActivity::render(RenderLock&& lock) {
}
void ConfirmationActivity::loop() {
// Tap the on-screen buttons: id 1 = Confirm, id 0 = Cancel.
int tappedId = -1;
if (mappedInput.wasItemTapped(tappedId)) {
ActivityResult res;
res.isCancelled = (tappedId != 1);
setResult(std::move(res));
finish();
return;
}
if (mappedInput.wasReleased(MappedInputManager::Button::Right)) {
ActivityResult res;
res.isCancelled = false;
+14 -3
View File
@@ -337,13 +337,24 @@ void KeyboardEntryActivity::loop() {
}
}
// A tap selects the key and presses it in one gesture. Encoded id = row*100+col
// (bottom function row = getContentRowCount()). Skipped in cursor mode, where a
// tap on a key would be ambiguous with cursor editing.
// A tap selects the key and presses it. Encoded id = row*100+col (bottom function
// row = getContentRowCount()). A touch-and-hold inserts the alternate character
// (numbers/symbols on a letter), mirroring the button long-press — wasItemLongPressed
// is checked first since it's a subset of wasItemTapped's releases. Skipped in
// cursor mode, where a tap on a key would be ambiguous with cursor editing.
int tappedKey = -1;
if (!cursorMode && mappedInput.wasItemTapped(tappedKey)) {
selectedRow = tappedKey / 100;
selectedCol = tappedKey % 100;
int longKey = -1;
if (mappedInput.wasItemLongPressed(longKey)) {
const char alt = getAlternativeChar();
if (alt != '\0') {
insertChar(alt);
requestUpdate();
return;
}
}
if (handleKeyPress()) {
requestUpdate();
}
+20 -20
View File
@@ -6,7 +6,6 @@
#include <HalStorage.h>
#include <Logging.h>
#include <WiFi.h>
#include <esp_task_wdt.h>
#include <algorithm>
@@ -15,6 +14,7 @@
#include "OpdsServerStore.h"
#include "SdCardFontSystem.h"
#include "SettingsList.h"
#include "TaskWatchdog.h"
#include "WebDAVHandler.h"
#include "WifiCredentialStore.h"
#include "html/FilesPageHtml.generated.h"
@@ -424,8 +424,8 @@ void CrossPointWebServer::scanFiles(const char* path, const std::function<void(F
}
file.close();
yield(); // Yield to allow WiFi and other tasks to process during long scans
esp_task_wdt_reset(); // Reset watchdog to prevent timeout on large directories
yield(); // Yield to allow WiFi and other tasks to process during long scans
feedTaskWatchdog(); // Reset watchdog to prevent timeout on large directories
file = root.openNextFile();
}
root.close();
@@ -556,7 +556,7 @@ void CrossPointWebServer::handleDownload() const {
size_t bytesRead = static_cast<size_t>(result);
size_t totalWritten = 0;
while (totalWritten < bytesRead) {
esp_task_wdt_reset();
feedTaskWatchdog();
size_t wrote = client.write(buffer + totalWritten, bytesRead - totalWritten);
if (wrote == 0) {
downloadOk = false;
@@ -576,12 +576,12 @@ static size_t writeCount = 0;
static bool flushUploadBuffer(CrossPointWebServer::UploadState& state) {
if (state.bufferPos > 0 && state.file) {
esp_task_wdt_reset(); // Reset watchdog before potentially slow SD write
feedTaskWatchdog(); // Reset watchdog before potentially slow SD write
const unsigned long writeStart = millis();
const size_t written = state.file.write(state.buffer.data(), state.bufferPos);
totalWriteTime += millis() - writeStart;
writeCount++;
esp_task_wdt_reset(); // Reset watchdog after SD write
feedTaskWatchdog(); // Reset watchdog after SD write
if (written != state.bufferPos) {
LOG_DBG("WEB", "[UPLOAD] Buffer flush failed: expected %d, wrote %d", state.bufferPos, written);
@@ -597,7 +597,7 @@ void CrossPointWebServer::handleUpload(UploadState& state) const {
static size_t lastLoggedSize = 0;
// Reset watchdog at start of every upload callback - HTTP parsing can be slow
esp_task_wdt_reset();
feedTaskWatchdog();
// Safety check: ensure server is still valid
if (!running || !server) {
@@ -609,7 +609,7 @@ void CrossPointWebServer::handleUpload(UploadState& state) const {
if (upload.status == UPLOAD_FILE_START) {
// Reset watchdog - this is the critical 1% crash point
esp_task_wdt_reset();
feedTaskWatchdog();
state.fileName = upload.filename;
state.size = 0;
@@ -647,21 +647,21 @@ void CrossPointWebServer::handleUpload(UploadState& state) const {
filePath += state.fileName;
// Check if file already exists - SD operations can be slow
esp_task_wdt_reset();
feedTaskWatchdog();
if (Storage.exists(filePath.c_str())) {
LOG_DBG("WEB", "[UPLOAD] Overwriting existing file: %s", filePath.c_str());
esp_task_wdt_reset();
feedTaskWatchdog();
Storage.remove(filePath.c_str());
}
// Open file for writing - this can be slow due to FAT cluster allocation
esp_task_wdt_reset();
feedTaskWatchdog();
if (!Storage.openFileForWrite("WEB", filePath, state.file)) {
state.error = "Failed to create file on SD card";
LOG_DBG("WEB", "[UPLOAD] FAILED to create file: %s", filePath.c_str());
return;
}
esp_task_wdt_reset();
feedTaskWatchdog();
LOG_DBG("WEB", "[UPLOAD] File created successfully: %s", filePath.c_str());
} else if (upload.status == UPLOAD_FILE_WRITE) {
@@ -1603,20 +1603,20 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t*
filePath.c_str());
// Check if file exists and remove it
esp_task_wdt_reset();
feedTaskWatchdog();
if (Storage.exists(filePath.c_str())) {
Storage.remove(filePath.c_str());
}
// Open file for writing
esp_task_wdt_reset();
feedTaskWatchdog();
if (!Storage.openFileForWrite("WS", filePath, wsUploadFile)) {
wsServer->sendTXT(num, "ERROR:Failed to create file");
wsUploadInProgress = false;
wsUploadClientNum = 255;
return;
}
esp_task_wdt_reset();
feedTaskWatchdog();
// Zero-byte upload: complete immediately without waiting for BIN frames
if (wsUploadSize == 0) {
@@ -1655,9 +1655,9 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t*
wsServer->sendTXT(num, "ERROR:Upload overflow");
return;
}
esp_task_wdt_reset();
feedTaskWatchdog();
size_t written = wsUploadFile.write(payload, length);
esp_task_wdt_reset();
feedTaskWatchdog();
if (written != length) {
abortWsUpload("WS");
@@ -1761,7 +1761,7 @@ void CrossPointWebServer::handleFontUploadData() {
switch (upload.status) {
case UPLOAD_FILE_START: {
esp_task_wdt_reset();
feedTaskWatchdog();
String family = server->arg("family");
fontUpload.file = HalFile();
fontUpload.familyName.clear();
@@ -1812,7 +1812,7 @@ void CrossPointWebServer::handleFontUploadData() {
case UPLOAD_FILE_WRITE: {
if (!fontUpload.valid) break;
esp_task_wdt_reset();
feedTaskWatchdog();
// Validate magic bytes on first chunk only
if (!fontUpload.magicChecked && upload.currentSize >= 8) {
@@ -1839,7 +1839,7 @@ void CrossPointWebServer::handleFontUploadData() {
fontUpload.file.write(fontUpload.buffer.data(), fontUpload.bufferPos);
fontUpload.bytesWritten += fontUpload.bufferPos;
fontUpload.bufferPos = 0;
esp_task_wdt_reset();
feedTaskWatchdog();
}
}
break;
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <esp_task_wdt.h>
// Feed the Task Watchdog Timer only when the calling task is actually
// subscribed to it. esp_task_wdt_reset() logs
// "task_wdt: esp_task_wdt_reset(...): task not found"
// on every call when the current task was never registered via
// esp_task_wdt_add(). Whether the Arduino loopTask is auto-subscribed depends
// on the chip target's framework sdkconfig: the ESP32-C3 (X4) build subscribes
// it, the classic-ESP32 (m5paper) build does not, so the unguarded resets in
// the web server / WiFi paths spammed the log there.
//
// esp_task_wdt_status(nullptr) returns ESP_OK only when the current task is
// subscribed, so this guard makes the reset a no-op on builds where the loop
// task is not watchdog-monitored, while preserving the reset where it is.
static inline void feedTaskWatchdog() {
if (esp_task_wdt_status(nullptr) == ESP_OK) {
esp_task_wdt_reset();
}
}
+4 -4
View File
@@ -3,8 +3,8 @@
#include <FsHelpers.h>
#include <HalStorage.h>
#include <Logging.h>
#include <esp_task_wdt.h>
#include "TaskWatchdog.h"
#include "util/BookCacheUtils.h"
namespace {
@@ -84,7 +84,7 @@ void WebDAVHandler::raw(WebServer& server, const String& uri, HTTPRaw& raw) {
} else if (raw.status == RAW_WRITE) {
if (_putFile && _putOk) {
esp_task_wdt_reset();
feedTaskWatchdog();
size_t written = _putFile.write(raw.buf, raw.currentSize);
if (written != raw.currentSize) {
_putOk = false;
@@ -252,7 +252,7 @@ void WebDAVHandler::handlePropfind(WebServer& s) {
file.close();
yield();
esp_task_wdt_reset();
feedTaskWatchdog();
file = root.openNextFile();
}
}
@@ -628,7 +628,7 @@ void WebDAVHandler::handleCopy(WebServer& s) {
uint8_t buf[4096];
bool copyOk = true;
while (srcFile.available()) {
esp_task_wdt_reset();
feedTaskWatchdog();
int bytesRead = srcFile.read(buf, sizeof(buf));
if (bytesRead <= 0) break;
size_t written = dstFile.write(buf, bytesRead);