mirror of
https://github.com/pelgraine/Meck.git
synced 2026-03-28 17:42:44 +01:00
implemented search functionality with DuckDuckGo Lite
This commit is contained in:
@@ -1548,12 +1548,13 @@ void handleKeyboardInput() {
|
||||
bool urlEdit = wr ? wr->isUrlEditing() : false;
|
||||
bool passEdit = wr ? wr->isPasswordEntry() : false;
|
||||
bool formEdit = wr ? wr->isFormFilling() : false;
|
||||
if (wr && (urlEdit || passEdit || formEdit)) {
|
||||
bool searchEdit = wr ? wr->isSearchEditing() : false;
|
||||
if (wr && (urlEdit || passEdit || formEdit || searchEdit)) {
|
||||
webReaderTextEntry = true; // Suppress ui_task.loop() in main loop
|
||||
wr->handleInput(key); // Updates buffer instantly, no render
|
||||
|
||||
// Check if text entry ended (submitted, cancelled, etc.)
|
||||
if (!wr->isUrlEditing() && !wr->isPasswordEntry() && !wr->isFormFilling()) {
|
||||
if (!wr->isUrlEditing() && !wr->isPasswordEntry() && !wr->isFormFilling() && !wr->isSearchEditing()) {
|
||||
// Text entry ended
|
||||
webReaderTextEntry = false;
|
||||
webReaderNeedsRefresh = false;
|
||||
@@ -1570,7 +1571,7 @@ void handleKeyboardInput() {
|
||||
webReaderTextEntry = false;
|
||||
|
||||
// Q from HOME mode exits the web reader entirely (like text reader)
|
||||
if ((key == 'q' || key == 'Q') && wr && wr->isHome() && !wr->isUrlEditing()) {
|
||||
if ((key == 'q' || key == 'Q') && wr && wr->isHome() && !wr->isUrlEditing() && !wr->isSearchEditing()) {
|
||||
Serial.println("Exiting web reader");
|
||||
ui_task.gotoHomeScreen();
|
||||
return;
|
||||
@@ -1786,22 +1787,14 @@ void handleKeyboardInput() {
|
||||
// Export contacts to SD card (contacts screen only)
|
||||
if (ui_task.isOnContactsScreen()) {
|
||||
Serial.println("Contacts: Exporting to SD...");
|
||||
// Show "working" toaster immediately (force e-ink render before blocking SD I/O)
|
||||
ui_task.showAlert("Exporting to SD...", 10000);
|
||||
ui_task.forceRefresh();
|
||||
ui_task.loop(); // immediate render so user sees the popup
|
||||
|
||||
int exported = exportContactsToSD();
|
||||
|
||||
// Update toaster with result
|
||||
if (exported >= 0) {
|
||||
char alertBuf[48];
|
||||
snprintf(alertBuf, sizeof(alertBuf), "Exported %d to SD", exported);
|
||||
ui_task.showAlert(alertBuf, 2500);
|
||||
ui_task.showAlert(alertBuf, 2000);
|
||||
} else {
|
||||
ui_task.showAlert("Export failed (no SD?)", 2500);
|
||||
ui_task.showAlert("Export failed (no SD?)", 2000);
|
||||
}
|
||||
ui_task.forceRefresh();
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1809,15 +1802,9 @@ void handleKeyboardInput() {
|
||||
// Import/merge contacts from SD backup (contacts screen only)
|
||||
if (ui_task.isOnContactsScreen()) {
|
||||
Serial.println("Contacts: Importing from SD...");
|
||||
// Show "working" toaster immediately (force e-ink render before blocking SD I/O)
|
||||
ui_task.showAlert("Importing from SD...", 10000);
|
||||
ui_task.forceRefresh();
|
||||
ui_task.loop(); // immediate render so user sees the popup
|
||||
|
||||
int added = importContactsFromSD();
|
||||
|
||||
// Update toaster with result
|
||||
if (added > 0) {
|
||||
// Invalidate the contacts screen cache so it rebuilds
|
||||
ContactsScreen* cs2 = (ContactsScreen*)ui_task.getContactsScreen();
|
||||
if (cs2) cs2->invalidateCache();
|
||||
char alertBuf[48];
|
||||
@@ -1825,11 +1812,10 @@ void handleKeyboardInput() {
|
||||
added, (int)the_mesh.getNumContacts());
|
||||
ui_task.showAlert(alertBuf, 2500);
|
||||
} else if (added == 0) {
|
||||
ui_task.showAlert("No new contacts to add", 2500);
|
||||
ui_task.showAlert("No new contacts to add", 2000);
|
||||
} else {
|
||||
ui_task.showAlert("Import failed (no backup?)", 2500);
|
||||
ui_task.showAlert("Import failed (no backup?)", 2000);
|
||||
}
|
||||
ui_task.forceRefresh();
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -1072,9 +1072,12 @@ private:
|
||||
// Bookmarks & History
|
||||
std::vector<String> _bookmarks;
|
||||
std::vector<String> _history;
|
||||
int _homeSelected; // Selected item in home view (0=URL bar, then bookmarks, then history)
|
||||
int _homeSelected; // Selected item in home view (0=IRC, 1=URL, 2=Search, then bookmarks, then history)
|
||||
int _homeScrollY; // Pixel scroll offset for home view
|
||||
bool _urlEditing; // True when URL bar is active for text entry
|
||||
bool _searchEditing; // True when search bar is active for text entry
|
||||
char _searchBuffer[128]; // Search query text
|
||||
int _searchLen;
|
||||
|
||||
// Link selection
|
||||
int _linkInput; // Accumulated link number digits
|
||||
@@ -2778,7 +2781,7 @@ private:
|
||||
const int sectionH = listLineH; // Section header height
|
||||
int maxChars = _charsPerLine - 2; // Account for "> " prefix
|
||||
if (maxChars < 10) maxChars = 10;
|
||||
int totalItems = 2 + (int)_bookmarks.size() + (int)_history.size();
|
||||
int totalItems = 3 + (int)_bookmarks.size() + (int)_history.size();
|
||||
|
||||
// ---- Layout pass: compute virtual Y extent of each item ----
|
||||
// We track: for each selectable item, its (virtualY, height).
|
||||
@@ -2804,6 +2807,12 @@ private:
|
||||
virtualY += urlBarH;
|
||||
itemIdx++;
|
||||
|
||||
// Item 2: Search bar
|
||||
int searchBarH = listLineH + 2;
|
||||
if (itemIdx == _homeSelected) { selectedTop = virtualY; selectedBot = virtualY + searchBarH; }
|
||||
virtualY += searchBarH;
|
||||
itemIdx++;
|
||||
|
||||
// Bookmarks
|
||||
if (_bookmarks.size() > 0) {
|
||||
virtualY += sectionH; // "-- Bookmarks --" header
|
||||
@@ -2924,6 +2933,39 @@ private:
|
||||
itemIdx++;
|
||||
}
|
||||
|
||||
// Item 2: Search bar
|
||||
{
|
||||
bool selected = (_homeSelected == itemIdx);
|
||||
if (HOME_VISIBLE(y, searchBarH)) {
|
||||
if (selected) {
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.fillRect(0, y + 5, display.width() - (needsScroll ? scrollbarW + 1 : 0), listLineH);
|
||||
display.setColor(DisplayDriver::DARK);
|
||||
} else {
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
}
|
||||
display.setCursor(0, y);
|
||||
if (_searchEditing) {
|
||||
char searchDisp[140];
|
||||
int maxShow = maxChars - 8; // "Search: " prefix + cursor
|
||||
int start = 0;
|
||||
if (_searchLen > maxShow) start = _searchLen - maxShow;
|
||||
snprintf(searchDisp, sizeof(searchDisp), "Search: %s_", _searchBuffer + start);
|
||||
display.print(searchDisp);
|
||||
} else if (_searchLen > 0) {
|
||||
char searchDisp[140];
|
||||
int maxShow = maxChars - 7;
|
||||
snprintf(searchDisp, sizeof(searchDisp), "Search: %s",
|
||||
_searchLen > maxShow ? (_searchBuffer + _searchLen - maxShow) : _searchBuffer);
|
||||
display.print(searchDisp);
|
||||
} else {
|
||||
display.print("Search: [DuckDuckGo Lite]");
|
||||
}
|
||||
}
|
||||
y += searchBarH;
|
||||
itemIdx++;
|
||||
}
|
||||
|
||||
// Bookmarks section
|
||||
if (_bookmarks.size() > 0) {
|
||||
// Section header
|
||||
@@ -3056,15 +3098,43 @@ private:
|
||||
display.setColor(DisplayDriver::YELLOW);
|
||||
if (_urlEditing) {
|
||||
display.print("Type URL Ent:Go");
|
||||
} else if (_searchEditing) {
|
||||
display.print("Type query Ent:Search");
|
||||
} else {
|
||||
char footerBuf[48];
|
||||
bool hasData = (_cookieCount > 0 || !_history.empty());
|
||||
if (hasData)
|
||||
snprintf(footerBuf, sizeof(footerBuf), "Q:Bk W/S Ent:Go X:Clr");
|
||||
bool onBookmark = (_homeSelected >= 3 && _homeSelected < 3 + (int)_bookmarks.size());
|
||||
if (onBookmark && hasData)
|
||||
snprintf(footerBuf, sizeof(footerBuf), "Ent:Go Del:Del Bkmk X:Clr Ckies");
|
||||
else if (onBookmark)
|
||||
snprintf(footerBuf, sizeof(footerBuf), "Q:Bk Ent:Go Del:Del Bkmk");
|
||||
else if (hasData)
|
||||
snprintf(footerBuf, sizeof(footerBuf), "Q:Bk W/S Ent:Go X:Clr Ckies");
|
||||
else
|
||||
snprintf(footerBuf, sizeof(footerBuf), "Q:Bk W/S:Nav Ent:Go");
|
||||
display.print(footerBuf);
|
||||
}
|
||||
|
||||
// Toast notification overlay (for bookmark deleted, etc.)
|
||||
if (_toastMsg[0] && (millis() - _toastTime < 1500)) {
|
||||
display.setTextSize(1);
|
||||
int tw = display.getTextWidth(_toastMsg);
|
||||
int bw = tw + 16;
|
||||
int bh = 20;
|
||||
int bx = (display.width() - bw) / 2;
|
||||
int by = (display.height() - bh) / 2;
|
||||
display.setColor(DisplayDriver::DARK);
|
||||
display.fillRect(bx, by, bw, bh);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawRect(bx, by, bw, 1);
|
||||
display.drawRect(bx, by + bh - 1, bw, 1);
|
||||
display.drawRect(bx, by, 1, bh);
|
||||
display.drawRect(bx + bw - 1, by, 1, bh);
|
||||
display.setCursor(bx + 8, by + 5);
|
||||
display.print(_toastMsg);
|
||||
} else if (_toastMsg[0]) {
|
||||
_toastMsg[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void renderFetching(DisplayDriver& display) {
|
||||
@@ -3409,7 +3479,7 @@ private:
|
||||
}
|
||||
|
||||
bool handleHomeInput(char c) {
|
||||
int totalItems = 2 + _bookmarks.size() + _history.size(); // IRC + URL + bookmarks + history
|
||||
int totalItems = 3 + _bookmarks.size() + _history.size(); // IRC + URL + Search + bookmarks + history
|
||||
|
||||
if (_urlEditing) {
|
||||
// URL text entry mode
|
||||
@@ -3464,6 +3534,67 @@ private:
|
||||
return true; // Consume all keys in editing mode
|
||||
}
|
||||
|
||||
// Search text entry mode
|
||||
if (_searchEditing) {
|
||||
if (c == '\r' || c == 13) {
|
||||
if (_searchLen > 0) {
|
||||
_searchEditing = false;
|
||||
// Build DuckDuckGo Lite search URL
|
||||
// URL-encode the query: spaces become +, special chars become %XX
|
||||
char encoded[256];
|
||||
int ei = 0;
|
||||
for (int i = 0; i < _searchLen && ei < (int)sizeof(encoded) - 4; i++) {
|
||||
char ch = _searchBuffer[i];
|
||||
if (ch == ' ') {
|
||||
encoded[ei++] = '+';
|
||||
} else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
|
||||
(ch >= '0' && ch <= '9') || ch == '-' || ch == '_' || ch == '.' || ch == '~') {
|
||||
encoded[ei++] = ch;
|
||||
} else {
|
||||
if (ei < (int)sizeof(encoded) - 4) {
|
||||
snprintf(encoded + ei, 4, "%%%02X", (unsigned char)ch);
|
||||
ei += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
encoded[ei] = '\0';
|
||||
snprintf(_urlBuffer, WEB_MAX_URL_LEN, "https://html.duckduckgo.com/lite/?q=%s", encoded);
|
||||
_urlLen = strlen(_urlBuffer);
|
||||
if (!isNetworkAvailable()) {
|
||||
_mode = WIFI_SETUP;
|
||||
if (!loadAndAutoConnect()) {
|
||||
startWifiScan();
|
||||
} else {
|
||||
fetchWithSelfRef(_urlBuffer);
|
||||
}
|
||||
} else {
|
||||
fetchWithSelfRef(_urlBuffer);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (c == '\b' || c == 127) {
|
||||
if (_searchLen > 0) {
|
||||
_searchBuffer[--_searchLen] = '\0';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (c == 'q' && _searchLen == 0) {
|
||||
_searchEditing = false;
|
||||
return true;
|
||||
}
|
||||
if (c == 0x1B) { // ESC
|
||||
_searchEditing = false;
|
||||
return true;
|
||||
}
|
||||
if (c >= 32 && c < 127 && _searchLen < (int)sizeof(_searchBuffer) - 1) {
|
||||
_searchBuffer[_searchLen++] = c;
|
||||
_searchBuffer[_searchLen] = '\0';
|
||||
return true;
|
||||
}
|
||||
return true; // Consume all keys in search editing mode
|
||||
}
|
||||
|
||||
// Normal navigation
|
||||
if (c == 'w' || c == 'W' || c == 0xF2) {
|
||||
if (_homeSelected > 0) _homeSelected--;
|
||||
@@ -3494,9 +3625,14 @@ private:
|
||||
_urlEditing = true;
|
||||
return true;
|
||||
}
|
||||
// Bookmark or history item selected (offset by 2 for IRC + URL)
|
||||
if (_homeSelected == 2) {
|
||||
// Activate search editing
|
||||
_searchEditing = true;
|
||||
return true;
|
||||
}
|
||||
// Bookmark or history item selected (offset by 3 for IRC + URL + Search)
|
||||
const char* selectedUrl = nullptr;
|
||||
int bmIdx = _homeSelected - 2;
|
||||
int bmIdx = _homeSelected - 3;
|
||||
if (bmIdx < (int)_bookmarks.size()) {
|
||||
selectedUrl = _bookmarks[bmIdx].c_str();
|
||||
} else {
|
||||
@@ -3522,6 +3658,25 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
// Delete/Backspace - remove selected bookmark
|
||||
if (c == '\b' || c == 127) {
|
||||
int bmIdx = _homeSelected - 3;
|
||||
if (bmIdx >= 0 && bmIdx < (int)_bookmarks.size()) {
|
||||
_bookmarks.erase(_bookmarks.begin() + bmIdx);
|
||||
saveBookmarks();
|
||||
// Adjust selection if we deleted the last item
|
||||
int newTotal = 3 + _bookmarks.size() + _history.size();
|
||||
if (_homeSelected >= newTotal && _homeSelected > 0) {
|
||||
_homeSelected--;
|
||||
}
|
||||
strncpy(_toastMsg, "Bookmark deleted", sizeof(_toastMsg));
|
||||
_toastTime = millis();
|
||||
Serial.printf("WebReader: Deleted bookmark %d\n", bmIdx);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// X - clear all cookies
|
||||
if (c == 'x' || c == 'X') {
|
||||
bool hadData = (_cookieCount > 0 || !_history.empty());
|
||||
@@ -4809,6 +4964,7 @@ public:
|
||||
_textBuffer(nullptr), _textLen(0), _links(nullptr), _linkCount(0),
|
||||
_currentPage(0), _totalPages(0),
|
||||
_homeSelected(0), _homeScrollY(0), _urlEditing(false),
|
||||
_searchEditing(false), _searchLen(0),
|
||||
_linkInput(0), _linkInputActive(false),
|
||||
_formCount(0), _forms(nullptr), _activeForm(-1), _activeField(0),
|
||||
_formFieldEditing(false), _formEditLen(0), _formLastCharAt(0),
|
||||
@@ -4825,6 +4981,7 @@ public:
|
||||
_ircLastDataTime(0), _ircReconnectAt(0),
|
||||
_ircDirty(false), _ircLastRender(0) {
|
||||
_urlBuffer[0] = '\0';
|
||||
_searchBuffer[0] = '\0';
|
||||
_wifiPass[0] = '\0';
|
||||
_pageTitle[0] = '\0';
|
||||
_currentUrl[0] = '\0';
|
||||
@@ -4963,6 +5120,7 @@ public:
|
||||
_homeSelected = 0;
|
||||
_homeScrollY = 0;
|
||||
_urlEditing = false;
|
||||
_searchEditing = false;
|
||||
|
||||
Serial.printf("WebReader: exitReader - heap after: %d, largest: %d\n",
|
||||
ESP.getFreeHeap(), ESP.getMaxAllocHeap());
|
||||
@@ -4973,6 +5131,7 @@ public:
|
||||
bool wantsTextReader() const { return _requestTextReader; }
|
||||
void clearTextReaderRequest() { _requestTextReader = false; }
|
||||
bool isUrlEditing() const { return _urlEditing && _mode == HOME; }
|
||||
bool isSearchEditing() const { return _searchEditing && _mode == HOME; }
|
||||
bool isWifiSetup() const { return _mode == WIFI_SETUP; }
|
||||
bool isPasswordEntry() const {
|
||||
return _mode == WIFI_SETUP && _wifiState == WIFI_ENTERING_PASS;
|
||||
|
||||
Reference in New Issue
Block a user