Add native address search with geocoding for location presets, remove itilog.com link

Adds a new AddressSearchScreen: enter an address on a dedicated
keyboard (with a special-character page for common European accented
letters), geocode it via the free OpenStreetMap Nominatim service,
confirm the found place, optionally name it, and save it as a new
location preset - no external website needed anymore.

Reachable both from Location Presets > "+" (choice between "by
address" and manual coordinates) and, for new users, from a one-time
FirstRunLocationScreen shown right after the language picker on first
boot, explaining why an exact address beats automatic IP geolocation
and letting them set it up immediately (skippable).

Removes the itilog.com link/tip added in the previous commit (Location
Presets info screen, all 6 languages, and the README) - superseded by
this native in-app flow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Eiswolf-BG
2026-08-11 00:14:01 +02:00
parent 866744bb88
commit 43b073a0eb
15 changed files with 790 additions and 21 deletions

View File

@@ -0,0 +1,87 @@
#include "first_run_location_screen.h"
#include "address_search_screen.h"
#include "location_presets.h"
#include "touch_input.h"
#include "menu_stars.h"
#include "config.h"
#include "i18n.h"
namespace FirstRunLocationScreen {
namespace {
struct Rect {
int16_t x, y, w, h;
bool contains(int16_t px, int16_t py) const {
return px >= x && px < x + w && py >= y && py < y + h;
}
};
void drawButton(TFT_eSPI& tft, const Rect& r, const String& label) {
tft.drawRoundRect(r.x, r.y, r.w, r.h, 4, TFT_GREEN);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.drawString(label, r.x + r.w / 2, r.y + r.h / 2);
tft.setTextDatum(TL_DATUM);
}
// Lokale Kopie, siehe Konvention in location_presets_screen.cpp.
int16_t layoutWrapped(TFT_eSPI& tft, int16_t x, int16_t startY, int16_t maxWidth,
int16_t lineHeight, const String& text) {
int16_t y = startY;
int32_t start = 0;
int32_t len = text.length();
while (start < len) {
while (start < len && text[start] == ' ') start++;
if (start >= len) break;
String line = text.substring(start, len);
while (tft.textWidth(line) > maxWidth) {
int32_t lastSpace = line.lastIndexOf(' ');
if (lastSpace <= 0) break;
line = line.substring(0, lastSpace);
}
tft.setCursor(x, y);
tft.print(line);
y += lineHeight;
start += line.length();
}
return y;
}
}
void run(TFT_eSPI& tft) {
MenuStars::reset();
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 14);
tft.println(I18n::t(StringId::FIRST_RUN_LOCATION_TITLE));
tft.setTextColor(TFT_WHITE, TFT_BLACK);
layoutWrapped(tft, 10, 40, (int16_t)(Config::SCREEN_WIDTH - 20), 18,
I18n::t(StringId::FIRST_RUN_LOCATION_BODY));
Rect setBtn = {10, (int16_t)(Config::SCREEN_HEIGHT - 96), (int16_t)(Config::SCREEN_WIDTH - 20), 40};
Rect skipBtn = {10, (int16_t)(Config::SCREEN_HEIGHT - 50), (int16_t)(Config::SCREEN_WIDTH - 20), 40};
drawButton(tft, setBtn, I18n::t(StringId::FIRST_RUN_LOCATION_SET_BTN));
drawButton(tft, skipBtn, I18n::t(StringId::FIRST_RUN_LOCATION_SKIP_BTN));
while (true) {
TouchInput::Point tap;
if (!TouchInput::wasTapped(tap)) { MenuStars::update(tft); delay(20); continue; }
if (setBtn.contains(tap.x, tap.y)) {
if (AddressSearchScreen::run(tft)) {
// Direkt aktivieren - das ist der ganze Sinn dieses
// Screens (sofort beim ersten Start den praezisen
// Standort nutzen, statt weiter auf Auto/IP zu bleiben).
uint8_t idx = LocationPresets::count();
if (idx > 0) LocationPresets::setActiveIndex((int8_t)(idx - 1));
}
return;
}
if (skipBtn.contains(tap.x, tap.y)) {
return;
}
}
}
}