feat: User-Interface I18n System (#728)
**What is the goal of this PR?** This PR introduces Internationalization (i18n) support, enabling users to switch the UI language dynamically. **What changes are included?** - Core Logic: Added I18n class (`lib/I18n/I18n.h/cpp`) to manage language state and string retrieval. - Data Structures: - `lib/I18n/I18nStrings.h/cpp`: Static string arrays for each supported language. - `lib/I18n/I18nKeys.h`: Enum definitions for type-safe string access. - `lib/I18n/translations.csv`: single source of truth. - Documentation: Added `docs/i18n.md` detailing the workflow for developers and translators. - New Settings activity: `src/activities/settings/LanguageSelectActivity.h/cpp` This implementation (building on concepts from #505) prioritizes performance and memory efficiency. The core approach is to store all localized strings for each language in dedicated arrays and access them via enums. This provides O(1) access with zero runtime overhead, and avoids the heap allocations, hashing, and collision handling required by `std::map` or `std::unordered_map`. The main trade-off is that enums and string arrays must remain perfectly synchronized—any mismatch would result in incorrect strings being displayed in the UI. To eliminate this risk, I added a Python script that automatically generates `I18nStrings.h/.cpp` and `I18nKeys.h` from a CSV file, which will serve as the single source of truth for all translations. The full design and workflow are documented in `docs/i18n.md`. - [x] Python script `generate_i18n.py` to auto-generate C++ files from CSV - [x] Populate translations.csv with initial translations. Currently available translations: English, Español, Français, Deutsch, Čeština, Português (Brasil), Русский, Svenska. Thanks, community! **Status:** EDIT: ready to be merged. As a proof of concept, the SPANISH strings currently mirror the English ones, but are fully uppercased. --- Did you use AI tools to help write this code? _**< PARTIALLY >**_ I used AI for the black work of replacing strings with I18n references across the project, and for generating the documentation. EDIT: also some help with merging changes from master. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: yeyeto2788 <juanernestobiondi@gmail.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <I18n.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
@@ -30,36 +32,54 @@ static_assert(kFontFamilyMappingCount > 0, "At least one font family must be ava
|
||||
// Each entry has a key (for JSON API) and category (for grouping).
|
||||
// ACTION-type entries and entries without a key are device-only.
|
||||
inline std::vector<SettingInfo> getSettingsList() {
|
||||
// Build font family options from the compile-time mapping table
|
||||
std::vector<std::string> fontFamilyOptions;
|
||||
for (size_t i = 0; i < kFontFamilyMappingCount; i++) {
|
||||
fontFamilyOptions.push_back(kFontFamilyMappings[i].name);
|
||||
}
|
||||
// Build font family StrId options from the compile-time mapping table
|
||||
constexpr StrId kFontFamilyStrIds[] = {
|
||||
#ifndef OMIT_BOOKERLY
|
||||
StrId::STR_BOOKERLY,
|
||||
#endif
|
||||
#ifndef OMIT_NOTOSANS
|
||||
StrId::STR_NOTO_SANS,
|
||||
#endif
|
||||
#ifndef OMIT_OPENDYSLEXIC
|
||||
StrId::STR_OPEN_DYSLEXIC,
|
||||
#endif
|
||||
};
|
||||
std::vector<StrId> fontFamilyStrIds(std::begin(kFontFamilyStrIds), std::end(kFontFamilyStrIds));
|
||||
|
||||
return {
|
||||
// --- Display ---
|
||||
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen,
|
||||
{"Dark", "Light", "Custom", "Cover", "None", "Cover + Custom"}, "sleepScreen", "Display"),
|
||||
SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"},
|
||||
"sleepScreenCoverMode", "Display"),
|
||||
SettingInfo::Enum("Sleep Screen Cover Filter", &CrossPointSettings::sleepScreenCoverFilter,
|
||||
{"None", "Contrast", "Inverted"}, "sleepScreenCoverFilter", "Display"),
|
||||
SettingInfo::Enum("Letterbox Fill", &CrossPointSettings::sleepScreenLetterboxFill,
|
||||
{"Dithered", "Solid", "None"}, "sleepScreenLetterboxFill", "Display"),
|
||||
SettingInfo::Enum(StrId::STR_SLEEP_SCREEN, &CrossPointSettings::sleepScreen,
|
||||
{StrId::STR_DARK, StrId::STR_LIGHT, StrId::STR_CUSTOM, StrId::STR_COVER, StrId::STR_NONE_OPT,
|
||||
StrId::STR_COVER_CUSTOM},
|
||||
"sleepScreen", StrId::STR_CAT_DISPLAY),
|
||||
SettingInfo::Enum(StrId::STR_SLEEP_COVER_MODE, &CrossPointSettings::sleepScreenCoverMode,
|
||||
{StrId::STR_FIT, StrId::STR_CROP}, "sleepScreenCoverMode", StrId::STR_CAT_DISPLAY),
|
||||
SettingInfo::Enum(StrId::STR_SLEEP_COVER_FILTER, &CrossPointSettings::sleepScreenCoverFilter,
|
||||
{StrId::STR_NONE_OPT, StrId::STR_FILTER_CONTRAST, StrId::STR_INVERTED},
|
||||
"sleepScreenCoverFilter", StrId::STR_CAT_DISPLAY),
|
||||
SettingInfo::Enum(StrId::STR_LETTERBOX_FILL, &CrossPointSettings::sleepScreenLetterboxFill,
|
||||
{StrId::STR_DITHERED, StrId::STR_SOLID, StrId::STR_NONE_OPT},
|
||||
"sleepScreenLetterboxFill", StrId::STR_CAT_DISPLAY),
|
||||
SettingInfo::Enum(
|
||||
"Status Bar", &CrossPointSettings::statusBar,
|
||||
{"None", "No Progress", "Full w/ Percentage", "Full w/ Book Bar", "Book Bar Only", "Full w/ Chapter Bar"},
|
||||
"statusBar", "Display"),
|
||||
SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"},
|
||||
"hideBatteryPercentage", "Display"),
|
||||
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
||||
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}, "refreshFrequency", "Display"),
|
||||
SettingInfo::Enum("UI Theme", &CrossPointSettings::uiTheme, {"Classic", "Lyra"}, "uiTheme", "Display"),
|
||||
SettingInfo::Toggle("Sunlight Fading Fix", &CrossPointSettings::fadingFix, "fadingFix", "Display"),
|
||||
StrId::STR_STATUS_BAR, &CrossPointSettings::statusBar,
|
||||
{StrId::STR_NONE_OPT, StrId::STR_NO_PROGRESS, StrId::STR_STATUS_BAR_FULL_PERCENT,
|
||||
StrId::STR_STATUS_BAR_FULL_BOOK, StrId::STR_STATUS_BAR_BOOK_ONLY, StrId::STR_STATUS_BAR_FULL_CHAPTER},
|
||||
"statusBar", StrId::STR_CAT_DISPLAY),
|
||||
SettingInfo::Enum(StrId::STR_HIDE_BATTERY, &CrossPointSettings::hideBatteryPercentage,
|
||||
{StrId::STR_NEVER, StrId::STR_IN_READER, StrId::STR_ALWAYS}, "hideBatteryPercentage",
|
||||
StrId::STR_CAT_DISPLAY),
|
||||
SettingInfo::Enum(
|
||||
StrId::STR_REFRESH_FREQ, &CrossPointSettings::refreshFrequency,
|
||||
{StrId::STR_PAGES_1, StrId::STR_PAGES_5, StrId::STR_PAGES_10, StrId::STR_PAGES_15, StrId::STR_PAGES_30},
|
||||
"refreshFrequency", StrId::STR_CAT_DISPLAY),
|
||||
SettingInfo::Enum(StrId::STR_UI_THEME, &CrossPointSettings::uiTheme,
|
||||
{StrId::STR_THEME_CLASSIC, StrId::STR_THEME_LYRA}, "uiTheme", StrId::STR_CAT_DISPLAY),
|
||||
SettingInfo::Toggle(StrId::STR_SUNLIGHT_FADING_FIX, &CrossPointSettings::fadingFix, "fadingFix",
|
||||
StrId::STR_CAT_DISPLAY),
|
||||
|
||||
// --- Reader ---
|
||||
SettingInfo::DynamicEnum(
|
||||
"Font Family", std::move(fontFamilyOptions),
|
||||
StrId::STR_FONT_FAMILY, std::move(fontFamilyStrIds),
|
||||
[]() -> uint8_t {
|
||||
for (uint8_t i = 0; i < kFontFamilyMappingCount; i++) {
|
||||
if (kFontFamilyMappings[i].value == SETTINGS.fontFamily) return i;
|
||||
@@ -71,71 +91,81 @@ inline std::vector<SettingInfo> getSettingsList() {
|
||||
SETTINGS.fontFamily = kFontFamilyMappings[idx].value;
|
||||
}
|
||||
},
|
||||
"fontFamily", "Reader"),
|
||||
SettingInfo::Enum("Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}, "fontSize",
|
||||
"Reader"),
|
||||
SettingInfo::Enum("Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}, "lineSpacing",
|
||||
"Reader"),
|
||||
SettingInfo::Value("Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}, "screenMargin", "Reader"),
|
||||
SettingInfo::Enum("Paragraph Alignment", &CrossPointSettings::paragraphAlignment,
|
||||
{"Justify", "Left", "Center", "Right", "Book's Style"}, "paragraphAlignment", "Reader"),
|
||||
SettingInfo::Toggle("Book's Embedded Style", &CrossPointSettings::embeddedStyle, "embeddedStyle", "Reader"),
|
||||
SettingInfo::Toggle("Hyphenation", &CrossPointSettings::hyphenationEnabled, "hyphenationEnabled", "Reader"),
|
||||
SettingInfo::Enum("Reading Orientation", &CrossPointSettings::orientation,
|
||||
{"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}, "orientation", "Reader"),
|
||||
SettingInfo::Toggle("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing,
|
||||
"extraParagraphSpacing", "Reader"),
|
||||
SettingInfo::Toggle("Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing, "textAntiAliasing", "Reader"),
|
||||
"fontFamily", StrId::STR_CAT_READER),
|
||||
SettingInfo::Enum(StrId::STR_FONT_SIZE, &CrossPointSettings::fontSize,
|
||||
{StrId::STR_SMALL, StrId::STR_MEDIUM, StrId::STR_LARGE, StrId::STR_X_LARGE}, "fontSize",
|
||||
StrId::STR_CAT_READER),
|
||||
SettingInfo::Enum(StrId::STR_LINE_SPACING, &CrossPointSettings::lineSpacing,
|
||||
{StrId::STR_TIGHT, StrId::STR_NORMAL, StrId::STR_WIDE}, "lineSpacing", StrId::STR_CAT_READER),
|
||||
SettingInfo::Value(StrId::STR_SCREEN_MARGIN, &CrossPointSettings::screenMargin, {5, 40, 5}, "screenMargin",
|
||||
StrId::STR_CAT_READER),
|
||||
SettingInfo::Enum(StrId::STR_PARA_ALIGNMENT, &CrossPointSettings::paragraphAlignment,
|
||||
{StrId::STR_JUSTIFY, StrId::STR_ALIGN_LEFT, StrId::STR_CENTER, StrId::STR_ALIGN_RIGHT,
|
||||
StrId::STR_BOOK_S_STYLE},
|
||||
"paragraphAlignment", StrId::STR_CAT_READER),
|
||||
SettingInfo::Toggle(StrId::STR_EMBEDDED_STYLE, &CrossPointSettings::embeddedStyle, "embeddedStyle",
|
||||
StrId::STR_CAT_READER),
|
||||
SettingInfo::Toggle(StrId::STR_HYPHENATION, &CrossPointSettings::hyphenationEnabled, "hyphenationEnabled",
|
||||
StrId::STR_CAT_READER),
|
||||
SettingInfo::Enum(StrId::STR_ORIENTATION, &CrossPointSettings::orientation,
|
||||
{StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED, StrId::STR_LANDSCAPE_CCW},
|
||||
"orientation", StrId::STR_CAT_READER),
|
||||
SettingInfo::Toggle(StrId::STR_EXTRA_SPACING, &CrossPointSettings::extraParagraphSpacing, "extraParagraphSpacing",
|
||||
StrId::STR_CAT_READER),
|
||||
SettingInfo::Toggle(StrId::STR_TEXT_AA, &CrossPointSettings::textAntiAliasing, "textAntiAliasing",
|
||||
StrId::STR_CAT_READER),
|
||||
|
||||
// --- Controls ---
|
||||
SettingInfo::Enum("Side Button Layout (reader)", &CrossPointSettings::sideButtonLayout,
|
||||
{"Prev, Next", "Next, Prev"}, "sideButtonLayout", "Controls"),
|
||||
SettingInfo::Toggle("Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip, "longPressChapterSkip",
|
||||
"Controls"),
|
||||
SettingInfo::Enum("Short Power Button Click", &CrossPointSettings::shortPwrBtn, {"Ignore", "Sleep", "Page Turn"},
|
||||
"shortPwrBtn", "Controls"),
|
||||
SettingInfo::Enum(StrId::STR_SIDE_BTN_LAYOUT, &CrossPointSettings::sideButtonLayout,
|
||||
{StrId::STR_PREV_NEXT, StrId::STR_NEXT_PREV}, "sideButtonLayout", StrId::STR_CAT_CONTROLS),
|
||||
SettingInfo::Toggle(StrId::STR_LONG_PRESS_SKIP, &CrossPointSettings::longPressChapterSkip, "longPressChapterSkip",
|
||||
StrId::STR_CAT_CONTROLS),
|
||||
SettingInfo::Enum(StrId::STR_SHORT_PWR_BTN, &CrossPointSettings::shortPwrBtn,
|
||||
{StrId::STR_IGNORE, StrId::STR_SLEEP, StrId::STR_PAGE_TURN}, "shortPwrBtn",
|
||||
StrId::STR_CAT_CONTROLS),
|
||||
|
||||
// --- System ---
|
||||
SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout,
|
||||
{"1 min", "5 min", "10 min", "15 min", "30 min"}, "sleepTimeout", "System"),
|
||||
SettingInfo::Enum(StrId::STR_TIME_TO_SLEEP, &CrossPointSettings::sleepTimeout,
|
||||
{StrId::STR_MIN_1, StrId::STR_MIN_5, StrId::STR_MIN_10, StrId::STR_MIN_15, StrId::STR_MIN_30},
|
||||
"sleepTimeout", StrId::STR_CAT_SYSTEM),
|
||||
|
||||
// --- KOReader Sync (web-only, uses KOReaderCredentialStore) ---
|
||||
SettingInfo::DynamicString(
|
||||
"KOReader Username", [] { return KOREADER_STORE.getUsername(); },
|
||||
StrId::STR_KOREADER_USERNAME, [] { return KOREADER_STORE.getUsername(); },
|
||||
[](const std::string& v) {
|
||||
KOREADER_STORE.setCredentials(v, KOREADER_STORE.getPassword());
|
||||
KOREADER_STORE.saveToFile();
|
||||
},
|
||||
"koUsername", "KOReader Sync"),
|
||||
"koUsername", StrId::STR_KOREADER_SYNC),
|
||||
SettingInfo::DynamicString(
|
||||
"KOReader Password", [] { return KOREADER_STORE.getPassword(); },
|
||||
StrId::STR_KOREADER_PASSWORD, [] { return KOREADER_STORE.getPassword(); },
|
||||
[](const std::string& v) {
|
||||
KOREADER_STORE.setCredentials(KOREADER_STORE.getUsername(), v);
|
||||
KOREADER_STORE.saveToFile();
|
||||
},
|
||||
"koPassword", "KOReader Sync"),
|
||||
"koPassword", StrId::STR_KOREADER_SYNC),
|
||||
SettingInfo::DynamicString(
|
||||
"Sync Server URL", [] { return KOREADER_STORE.getServerUrl(); },
|
||||
StrId::STR_SYNC_SERVER_URL, [] { return KOREADER_STORE.getServerUrl(); },
|
||||
[](const std::string& v) {
|
||||
KOREADER_STORE.setServerUrl(v);
|
||||
KOREADER_STORE.saveToFile();
|
||||
},
|
||||
"koServerUrl", "KOReader Sync"),
|
||||
"koServerUrl", StrId::STR_KOREADER_SYNC),
|
||||
SettingInfo::DynamicEnum(
|
||||
"Document Matching", {"Filename", "Binary"},
|
||||
StrId::STR_DOCUMENT_MATCHING, {StrId::STR_FILENAME, StrId::STR_BINARY},
|
||||
[] { return static_cast<uint8_t>(KOREADER_STORE.getMatchMethod()); },
|
||||
[](uint8_t v) {
|
||||
KOREADER_STORE.setMatchMethod(static_cast<DocumentMatchMethod>(v));
|
||||
KOREADER_STORE.saveToFile();
|
||||
},
|
||||
"koMatchMethod", "KOReader Sync"),
|
||||
"koMatchMethod", StrId::STR_KOREADER_SYNC),
|
||||
|
||||
// --- OPDS Browser (web-only, uses CrossPointSettings char arrays) ---
|
||||
SettingInfo::String("OPDS Server URL", SETTINGS.opdsServerUrl, sizeof(SETTINGS.opdsServerUrl), "opdsServerUrl",
|
||||
"OPDS Browser"),
|
||||
SettingInfo::String("OPDS Username", SETTINGS.opdsUsername, sizeof(SETTINGS.opdsUsername), "opdsUsername",
|
||||
"OPDS Browser"),
|
||||
SettingInfo::String("OPDS Password", SETTINGS.opdsPassword, sizeof(SETTINGS.opdsPassword), "opdsPassword",
|
||||
"OPDS Browser"),
|
||||
SettingInfo::String(StrId::STR_OPDS_SERVER_URL, SETTINGS.opdsServerUrl, sizeof(SETTINGS.opdsServerUrl),
|
||||
"opdsServerUrl", StrId::STR_OPDS_BROWSER),
|
||||
SettingInfo::String(StrId::STR_USERNAME, SETTINGS.opdsUsername, sizeof(SETTINGS.opdsUsername), "opdsUsername",
|
||||
StrId::STR_OPDS_BROWSER),
|
||||
SettingInfo::String(StrId::STR_PASSWORD, SETTINGS.opdsPassword, sizeof(SETTINGS.opdsPassword), "opdsPassword",
|
||||
StrId::STR_OPDS_BROWSER),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "BootActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include "fontIds.h"
|
||||
#include "images/Logo120.h"
|
||||
@@ -13,8 +14,8 @@ void BootActivity::onEnter() {
|
||||
|
||||
renderer.clearScreen();
|
||||
renderer.drawImage(Logo120, (pageWidth - 120) / 2, (pageHeight - 120) / 2, 120, 120);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "BOOTING");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, tr(STR_CROSSPOINT), true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, tr(STR_BOOTING));
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, CROSSPOINT_VERSION);
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <Epub.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalStorage.h>
|
||||
#include <I18n.h>
|
||||
#include <Logging.h>
|
||||
#include <PlaceholderCoverGenerator.h>
|
||||
#include <Serialization.h>
|
||||
@@ -346,7 +347,7 @@ void drawLetterboxFill(GfxRenderer& renderer, const LetterboxFillData& data, uin
|
||||
|
||||
void SleepActivity::onEnter() {
|
||||
Activity::onEnter();
|
||||
GUI.drawPopup(renderer, "Entering Sleep...");
|
||||
GUI.drawPopup(renderer, tr(STR_ENTERING_SLEEP));
|
||||
|
||||
switch (SETTINGS.sleepScreen) {
|
||||
case (CrossPointSettings::SLEEP_SCREEN_MODE::BLANK):
|
||||
@@ -441,8 +442,8 @@ void SleepActivity::renderDefaultSleepScreen() const {
|
||||
|
||||
renderer.clearScreen();
|
||||
renderer.drawImage(Logo120, (pageWidth - 120) / 2, (pageHeight - 120) / 2, 120, 120);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, tr(STR_CROSSPOINT), true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, tr(STR_SLEEPING));
|
||||
|
||||
// Make sleep screen dark unless light is selected in settings
|
||||
if (SETTINGS.sleepScreen != CrossPointSettings::SLEEP_SCREEN_MODE::LIGHT) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <Epub.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
#include <Logging.h>
|
||||
#include <OpdsStream.h>
|
||||
#include <WiFi.h>
|
||||
@@ -28,7 +29,7 @@ void OpdsBookBrowserActivity::onEnter() {
|
||||
currentPath = ""; // Root path - user provides full URL in settings
|
||||
selectorIndex = 0;
|
||||
errorMessage.clear();
|
||||
statusMessage = "Checking WiFi...";
|
||||
statusMessage = tr(STR_CHECKING_WIFI);
|
||||
requestUpdate();
|
||||
|
||||
// Check WiFi and connect if needed, then fetch feed
|
||||
@@ -60,7 +61,7 @@ void OpdsBookBrowserActivity::loop() {
|
||||
// WiFi connected - just retry fetching the feed
|
||||
LOG_DBG("OPDS", "Retry: WiFi connected, retrying fetch");
|
||||
state = BrowserState::LOADING;
|
||||
statusMessage = "Loading...";
|
||||
statusMessage = tr(STR_LOADING);
|
||||
requestUpdate();
|
||||
fetchFeed(currentPath);
|
||||
} else {
|
||||
@@ -141,11 +142,11 @@ void OpdsBookBrowserActivity::render(Activity::RenderLock&&) {
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "OPDS Browser", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_OPDS_BROWSER), true, EpdFontFamily::BOLD);
|
||||
|
||||
if (state == BrowserState::CHECK_WIFI) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, statusMessage.c_str());
|
||||
const auto labels = mappedInput.mapLabels("« Back", "", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
@@ -153,23 +154,23 @@ void OpdsBookBrowserActivity::render(Activity::RenderLock&&) {
|
||||
|
||||
if (state == BrowserState::LOADING) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, statusMessage.c_str());
|
||||
const auto labels = mappedInput.mapLabels("« Back", "", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == BrowserState::ERROR) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, "Error:");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, tr(STR_ERROR_MSG));
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, errorMessage.c_str());
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Retry", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_RETRY), "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == BrowserState::DOWNLOADING) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 40, "Downloading...");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 40, tr(STR_DOWNLOADING));
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 10, statusMessage.c_str());
|
||||
if (downloadTotal > 0) {
|
||||
const int barWidth = pageWidth - 100;
|
||||
@@ -184,15 +185,15 @@ void OpdsBookBrowserActivity::render(Activity::RenderLock&&) {
|
||||
|
||||
// Browsing state
|
||||
// Show appropriate button hint based on selected entry type
|
||||
const char* confirmLabel = "Open";
|
||||
const char* confirmLabel = tr(STR_OPEN);
|
||||
if (!entries.empty() && entries[selectorIndex].type == OpdsEntryType::BOOK) {
|
||||
confirmLabel = "Download";
|
||||
confirmLabel = tr(STR_DOWNLOAD);
|
||||
}
|
||||
const auto labels = mappedInput.mapLabels("« Back", confirmLabel, "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), confirmLabel, "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
if (entries.empty()) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, "No entries found");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, tr(STR_NO_ENTRIES));
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
@@ -227,7 +228,7 @@ void OpdsBookBrowserActivity::fetchFeed(const std::string& path) {
|
||||
const char* serverUrl = SETTINGS.opdsServerUrl;
|
||||
if (strlen(serverUrl) == 0) {
|
||||
state = BrowserState::ERROR;
|
||||
errorMessage = "No server URL configured";
|
||||
errorMessage = tr(STR_NO_SERVER_URL);
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
@@ -241,7 +242,7 @@ void OpdsBookBrowserActivity::fetchFeed(const std::string& path) {
|
||||
OpdsParserStream stream{parser};
|
||||
if (!HttpDownloader::fetchUrl(url, stream)) {
|
||||
state = BrowserState::ERROR;
|
||||
errorMessage = "Failed to fetch feed";
|
||||
errorMessage = tr(STR_FETCH_FEED_FAILED);
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
@@ -249,7 +250,7 @@ void OpdsBookBrowserActivity::fetchFeed(const std::string& path) {
|
||||
|
||||
if (!parser) {
|
||||
state = BrowserState::ERROR;
|
||||
errorMessage = "Failed to parse feed";
|
||||
errorMessage = tr(STR_PARSE_FEED_FAILED);
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
@@ -260,7 +261,7 @@ void OpdsBookBrowserActivity::fetchFeed(const std::string& path) {
|
||||
|
||||
if (entries.empty()) {
|
||||
state = BrowserState::ERROR;
|
||||
errorMessage = "No entries found";
|
||||
errorMessage = tr(STR_NO_ENTRIES);
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
@@ -275,7 +276,7 @@ void OpdsBookBrowserActivity::navigateToEntry(const OpdsEntry& entry) {
|
||||
currentPath = entry.href;
|
||||
|
||||
state = BrowserState::LOADING;
|
||||
statusMessage = "Loading...";
|
||||
statusMessage = tr(STR_LOADING);
|
||||
entries.clear();
|
||||
selectorIndex = 0;
|
||||
requestUpdate();
|
||||
@@ -293,7 +294,7 @@ void OpdsBookBrowserActivity::navigateBack() {
|
||||
navigationHistory.pop_back();
|
||||
|
||||
state = BrowserState::LOADING;
|
||||
statusMessage = "Loading...";
|
||||
statusMessage = tr(STR_LOADING);
|
||||
entries.clear();
|
||||
selectorIndex = 0;
|
||||
requestUpdate();
|
||||
@@ -340,7 +341,7 @@ void OpdsBookBrowserActivity::downloadBook(const OpdsEntry& book) {
|
||||
requestUpdate();
|
||||
} else {
|
||||
state = BrowserState::ERROR;
|
||||
errorMessage = "Download failed";
|
||||
errorMessage = tr(STR_DOWNLOAD_FAILED);
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
@@ -349,7 +350,7 @@ void OpdsBookBrowserActivity::checkAndConnectWifi() {
|
||||
// Already connected? Verify connection is valid by checking IP
|
||||
if (WiFi.status() == WL_CONNECTED && WiFi.localIP() != IPAddress(0, 0, 0, 0)) {
|
||||
state = BrowserState::LOADING;
|
||||
statusMessage = "Loading...";
|
||||
statusMessage = tr(STR_LOADING);
|
||||
requestUpdate();
|
||||
fetchFeed(currentPath);
|
||||
return;
|
||||
@@ -373,7 +374,7 @@ void OpdsBookBrowserActivity::onWifiSelectionComplete(const bool connected) {
|
||||
if (connected) {
|
||||
LOG_DBG("OPDS", "WiFi connected via selection, fetching feed");
|
||||
state = BrowserState::LOADING;
|
||||
statusMessage = "Loading...";
|
||||
statusMessage = tr(STR_LOADING);
|
||||
requestUpdate();
|
||||
fetchFeed(currentPath);
|
||||
} else {
|
||||
@@ -383,7 +384,7 @@ void OpdsBookBrowserActivity::onWifiSelectionComplete(const bool connected) {
|
||||
WiFi.disconnect();
|
||||
WiFi.mode(WIFI_OFF);
|
||||
state = BrowserState::ERROR;
|
||||
errorMessage = "WiFi connection failed";
|
||||
errorMessage = tr(STR_WIFI_CONN_FAILED);
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
#include <Bitmap.h>
|
||||
#include <Epub.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
#include <HalStorage.h>
|
||||
#include <I18n.h>
|
||||
#include <Utf8.h>
|
||||
#include <PlaceholderCoverGenerator.h>
|
||||
#include <Xtc.h>
|
||||
@@ -63,7 +65,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) {
|
||||
if (!Epub::isValidThumbnailBmp(coverPath)) {
|
||||
if (!showingLoading) {
|
||||
showingLoading = true;
|
||||
popupRect = GUI.drawPopup(renderer, "Loading...");
|
||||
popupRect = GUI.drawPopup(renderer, tr(STR_LOADING));
|
||||
}
|
||||
GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size()));
|
||||
|
||||
@@ -242,10 +244,11 @@ void HomeActivity::render(Activity::RenderLock&&) {
|
||||
std::bind(&HomeActivity::storeCoverBuffer, this));
|
||||
|
||||
// Build menu items dynamically
|
||||
std::vector<const char*> menuItems = {"Browse Files", "Recents", "File Transfer", "Settings"};
|
||||
std::vector<const char*> menuItems = {tr(STR_BROWSE_FILES), tr(STR_MENU_RECENT_BOOKS), tr(STR_FILE_TRANSFER),
|
||||
tr(STR_SETTINGS_TITLE)};
|
||||
if (hasOpdsUrl) {
|
||||
// Insert OPDS Browser after My Library
|
||||
menuItems.insert(menuItems.begin() + 2, "OPDS Browser");
|
||||
menuItems.insert(menuItems.begin() + 2, tr(STR_OPDS_BROWSER));
|
||||
}
|
||||
|
||||
GUI.drawButtonMenu(
|
||||
@@ -256,7 +259,7 @@ void HomeActivity::render(Activity::RenderLock&&) {
|
||||
static_cast<int>(menuItems.size()), selectorIndex - recentBooks.size(),
|
||||
[&menuItems](int index) { return std::string(menuItems[index]); }, nullptr);
|
||||
|
||||
const auto labels = mappedInput.mapLabels("", "Select", "Up", "Down");
|
||||
const auto labels = mappedInput.mapLabels("", tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
renderer.displayBuffer();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalStorage.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -195,13 +196,13 @@ void MyLibraryActivity::render(Activity::RenderLock&&) {
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
auto metrics = UITheme::getInstance().getMetrics();
|
||||
|
||||
auto folderName = basepath == "/" ? "SD card" : basepath.substr(basepath.rfind('/') + 1).c_str();
|
||||
auto folderName = basepath == "/" ? tr(STR_SD_CARD) : basepath.substr(basepath.rfind('/') + 1).c_str();
|
||||
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, folderName);
|
||||
|
||||
const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing;
|
||||
const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing;
|
||||
if (files.empty()) {
|
||||
renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, contentTop + 20, "No books found");
|
||||
renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, contentTop + 20, tr(STR_NO_BOOKS_FOUND));
|
||||
} else {
|
||||
GUI.drawList(
|
||||
renderer, Rect{0, contentTop, pageWidth, contentHeight}, files.size(), selectorIndex,
|
||||
@@ -209,7 +210,8 @@ void MyLibraryActivity::render(Activity::RenderLock&&) {
|
||||
}
|
||||
|
||||
// Help text
|
||||
const auto labels = mappedInput.mapLabels(basepath == "/" ? "« Home" : "« Back", "Open", "Up", "Down");
|
||||
const auto labels = mappedInput.mapLabels(basepath == "/" ? tr(STR_HOME) : tr(STR_BACK), tr(STR_OPEN), tr(STR_DIR_UP),
|
||||
tr(STR_DIR_DOWN));
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
renderer.displayBuffer();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalStorage.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -89,14 +90,14 @@ void RecentBooksActivity::render(Activity::RenderLock&&) {
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
auto metrics = UITheme::getInstance().getMetrics();
|
||||
|
||||
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, "Recent Books");
|
||||
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_MENU_RECENT_BOOKS));
|
||||
|
||||
const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing;
|
||||
const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing;
|
||||
|
||||
// Recent tab
|
||||
if (recentBooks.empty()) {
|
||||
renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, contentTop + 20, "No recent books");
|
||||
renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, contentTop + 20, tr(STR_NO_RECENT_BOOKS));
|
||||
} else {
|
||||
GUI.drawList(
|
||||
renderer, Rect{0, contentTop, pageWidth, contentHeight}, recentBooks.size(), selectorIndex,
|
||||
@@ -105,7 +106,7 @@ void RecentBooksActivity::render(Activity::RenderLock&&) {
|
||||
}
|
||||
|
||||
// Help text
|
||||
const auto labels = mappedInput.mapLabels("« Home", "Open", "Up", "Down");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_HOME), tr(STR_OPEN), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
renderer.displayBuffer();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include <I18n.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <ESPmDNS.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_task_wdt.h>
|
||||
|
||||
@@ -178,9 +179,9 @@ void CalibreConnectActivity::render(Activity::RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
if (state == CalibreConnectState::SERVER_STARTING) {
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, "Starting Calibre...", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, tr(STR_CALIBRE_STARTING), true, EpdFontFamily::BOLD);
|
||||
} else if (state == CalibreConnectState::ERROR) {
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, "Calibre setup failed", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, tr(STR_CONNECTION_FAILED), true, EpdFontFamily::BOLD);
|
||||
}
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
@@ -190,31 +191,32 @@ void CalibreConnectActivity::renderServerRunning() const {
|
||||
constexpr int SMALL_SPACING = 20;
|
||||
constexpr int SECTION_SPACING = 40;
|
||||
constexpr int TOP_PADDING = 14;
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Connect to Calibre", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_CALIBRE_WIRELESS), true, EpdFontFamily::BOLD);
|
||||
|
||||
int y = 55 + TOP_PADDING;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y, "Network", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y, tr(STR_WIFI_NETWORKS), true, EpdFontFamily::BOLD);
|
||||
y += LINE_SPACING;
|
||||
std::string ssidInfo = "Network: " + connectedSSID;
|
||||
std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + connectedSSID;
|
||||
if (ssidInfo.length() > 28) {
|
||||
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
||||
}
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y, ssidInfo.c_str());
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y + LINE_SPACING, ("IP: " + connectedIP).c_str());
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y + LINE_SPACING,
|
||||
(std::string(tr(STR_IP_ADDRESS_PREFIX)) + connectedIP).c_str());
|
||||
|
||||
y += LINE_SPACING * 2 + SECTION_SPACING;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y, "Setup", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y, tr(STR_CALIBRE_SETUP), true, EpdFontFamily::BOLD);
|
||||
y += LINE_SPACING;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y, "1) Install CrossPoint Reader plugin");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING, "2) Be on the same WiFi network");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING * 2, "3) In Calibre: \"Send to device\"");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING * 3, "Keep this screen open while sending");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y, tr(STR_CALIBRE_INSTRUCTION_1));
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING, tr(STR_CALIBRE_INSTRUCTION_2));
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING * 2, tr(STR_CALIBRE_INSTRUCTION_3));
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING * 3, tr(STR_CALIBRE_INSTRUCTION_4));
|
||||
|
||||
y += SMALL_SPACING * 3 + SECTION_SPACING;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y, "Status", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y, tr(STR_CALIBRE_STATUS), true, EpdFontFamily::BOLD);
|
||||
y += LINE_SPACING;
|
||||
if (lastProgressTotal > 0 && lastProgressReceived <= lastProgressTotal) {
|
||||
std::string label = "Receiving";
|
||||
std::string label = tr(STR_CALIBRE_RECEIVING);
|
||||
if (!currentUploadName.empty()) {
|
||||
label += ": " + currentUploadName;
|
||||
if (label.length() > 34) {
|
||||
@@ -230,13 +232,13 @@ void CalibreConnectActivity::renderServerRunning() const {
|
||||
}
|
||||
|
||||
if (lastCompleteAt > 0 && (millis() - lastCompleteAt) < 6000) {
|
||||
std::string msg = "Received: " + lastCompleteName;
|
||||
std::string msg = std::string(tr(STR_CALIBRE_RECEIVED)) + lastCompleteName;
|
||||
if (msg.length() > 36) {
|
||||
msg.replace(33, msg.length() - 33, "...");
|
||||
}
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y, msg.c_str());
|
||||
}
|
||||
|
||||
const auto labels = mappedInput.mapLabels("« Exit", "", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_EXIT), "", "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <DNSServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include <qrcode.h>
|
||||
@@ -345,7 +346,7 @@ void CrossPointWebServerActivity::render(Activity::RenderLock&&) {
|
||||
} else if (state == WebServerActivityState::AP_STARTING) {
|
||||
renderer.clearScreen();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, "Starting Hotspot...", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, tr(STR_STARTING_HOTSPOT), true, EpdFontFamily::BOLD);
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
}
|
||||
@@ -376,21 +377,20 @@ void CrossPointWebServerActivity::renderServerRunning() const {
|
||||
// Use consistent line spacing
|
||||
constexpr int LINE_SPACING = 28; // Space between lines
|
||||
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "File Transfer", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_FILE_TRANSFER), true, EpdFontFamily::BOLD);
|
||||
|
||||
if (isApMode) {
|
||||
// AP mode display - center the content block
|
||||
int startY = 55;
|
||||
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY, "Hotspot Mode", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY, tr(STR_HOTSPOT_MODE), true, EpdFontFamily::BOLD);
|
||||
|
||||
std::string ssidInfo = "Network: " + connectedSSID;
|
||||
std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + connectedSSID;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING, ssidInfo.c_str());
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 2, "Connect your device to this WiFi network");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 2, tr(STR_CONNECT_WIFI_HINT));
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3,
|
||||
"or scan QR code with your phone to connect to Wifi.");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, tr(STR_SCAN_QR_WIFI_HINT));
|
||||
// Show QR code for URL
|
||||
const std::string wifiConfig = std::string("WIFI:S:") + connectedSSID + ";;";
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 4, wifiConfig);
|
||||
@@ -401,24 +401,24 @@ void CrossPointWebServerActivity::renderServerRunning() const {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 3, hostnameUrl.c_str(), true, EpdFontFamily::BOLD);
|
||||
|
||||
// Show IP address as fallback
|
||||
std::string ipUrl = "or http://" + connectedIP + "/";
|
||||
std::string ipUrl = std::string(tr(STR_OR_HTTP_PREFIX)) + connectedIP + "/";
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, ipUrl.c_str());
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "Open this URL in your browser");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, tr(STR_OPEN_URL_HINT));
|
||||
|
||||
// Show QR code for URL
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, "or scan QR code with your phone:");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, tr(STR_SCAN_QR_HINT));
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 7, hostnameUrl);
|
||||
} else {
|
||||
// STA mode display (original behavior)
|
||||
const int startY = 65;
|
||||
|
||||
std::string ssidInfo = "Network: " + connectedSSID;
|
||||
std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + connectedSSID;
|
||||
if (ssidInfo.length() > 28) {
|
||||
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
||||
}
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY, ssidInfo.c_str());
|
||||
|
||||
std::string ipInfo = "IP Address: " + connectedIP;
|
||||
std::string ipInfo = std::string(tr(STR_IP_ADDRESS_PREFIX)) + connectedIP;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING, ipInfo.c_str());
|
||||
|
||||
// Show web server URL prominently
|
||||
@@ -426,16 +426,16 @@ void CrossPointWebServerActivity::renderServerRunning() const {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 2, webInfo.c_str(), true, EpdFontFamily::BOLD);
|
||||
|
||||
// Also show hostname URL
|
||||
std::string hostnameUrl = std::string("or http://") + AP_HOSTNAME + ".local/";
|
||||
std::string hostnameUrl = std::string(tr(STR_OR_HTTP_PREFIX)) + AP_HOSTNAME + ".local/";
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, hostnameUrl.c_str());
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, "Open this URL in your browser");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, tr(STR_OPEN_URL_HINT));
|
||||
|
||||
// Show QR code for URL
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 6, webInfo);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "or scan QR code with your phone:");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, tr(STR_SCAN_QR_HINT));
|
||||
}
|
||||
|
||||
const auto labels = mappedInput.mapLabels("« Exit", "", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_EXIT), "", "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "NetworkModeSelectionActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include "MappedInputManager.h"
|
||||
#include "components/UITheme.h"
|
||||
@@ -8,12 +9,6 @@
|
||||
|
||||
namespace {
|
||||
constexpr int MENU_ITEM_COUNT = 3;
|
||||
const char* MENU_ITEMS[MENU_ITEM_COUNT] = {"Join a Network", "Connect to Calibre", "Create Hotspot"};
|
||||
const char* MENU_DESCRIPTIONS[MENU_ITEM_COUNT] = {
|
||||
"Connect to an existing WiFi network",
|
||||
"Use Calibre wireless device transfers",
|
||||
"Create a WiFi network others can join",
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void NetworkModeSelectionActivity::onEnter() {
|
||||
@@ -66,10 +61,16 @@ void NetworkModeSelectionActivity::render(Activity::RenderLock&&) {
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
// Draw header
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "File Transfer", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_FILE_TRANSFER), true, EpdFontFamily::BOLD);
|
||||
|
||||
// Draw subtitle
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 50, "How would you like to connect?");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 50, tr(STR_HOW_CONNECT));
|
||||
|
||||
// Menu items and descriptions
|
||||
static constexpr StrId menuItems[MENU_ITEM_COUNT] = {StrId::STR_JOIN_NETWORK, StrId::STR_CALIBRE_WIRELESS,
|
||||
StrId::STR_CREATE_HOTSPOT};
|
||||
static constexpr StrId menuDescs[MENU_ITEM_COUNT] = {StrId::STR_JOIN_DESC, StrId::STR_CALIBRE_DESC,
|
||||
StrId::STR_HOTSPOT_DESC};
|
||||
|
||||
// Draw menu items centered on screen
|
||||
constexpr int itemHeight = 50; // Height for each menu item (including description)
|
||||
@@ -86,12 +87,12 @@ void NetworkModeSelectionActivity::render(Activity::RenderLock&&) {
|
||||
|
||||
// Draw text: black=false (white text) when selected (on black background)
|
||||
// black=true (black text) when not selected (on white background)
|
||||
renderer.drawText(UI_10_FONT_ID, 30, itemY, MENU_ITEMS[i], /*black=*/!isSelected);
|
||||
renderer.drawText(SMALL_FONT_ID, 30, itemY + 22, MENU_DESCRIPTIONS[i], /*black=*/!isSelected);
|
||||
renderer.drawText(UI_10_FONT_ID, 30, itemY, I18N.get(menuItems[i]), /*black=*/!isSelected);
|
||||
renderer.drawText(SMALL_FONT_ID, 30, itemY + 22, I18N.get(menuDescs[i]), /*black=*/!isSelected);
|
||||
}
|
||||
|
||||
// Draw help text at bottom
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
renderer.displayBuffer();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "WifiSelectionActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
#include <Logging.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
@@ -38,9 +39,9 @@ void WifiSelectionActivity::onEnter() {
|
||||
// Cache MAC address for display
|
||||
uint8_t mac[6];
|
||||
WiFi.macAddress(mac);
|
||||
char macStr[32];
|
||||
snprintf(macStr, sizeof(macStr), "MAC address: %02x-%02x-%02x-%02x-%02x-%02x", mac[0], mac[1], mac[2], mac[3], mac[4],
|
||||
mac[5]);
|
||||
char macStr[64];
|
||||
snprintf(macStr, sizeof(macStr), "%s %02x-%02x-%02x-%02x-%02x-%02x", tr(STR_MAC_ADDRESS), mac[0], mac[1], mac[2],
|
||||
mac[3], mac[4], mac[5]);
|
||||
cachedMacAddress = std::string(macStr);
|
||||
|
||||
// Trigger first update to show scanning message
|
||||
@@ -191,7 +192,7 @@ void WifiSelectionActivity::selectNetwork(const int index) {
|
||||
state = WifiSelectionState::PASSWORD_ENTRY;
|
||||
// Don't allow screen updates while changing activity
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, "Enter WiFi Password",
|
||||
renderer, mappedInput, tr(STR_ENTER_WIFI_PASSWORD),
|
||||
"", // No initial text
|
||||
50, // Y position
|
||||
64, // Max password length
|
||||
@@ -266,9 +267,9 @@ void WifiSelectionActivity::checkConnectionStatus() {
|
||||
}
|
||||
|
||||
if (status == WL_CONNECT_FAILED || status == WL_NO_SSID_AVAIL) {
|
||||
connectionError = "Error: General failure";
|
||||
connectionError = tr(STR_ERROR_GENERAL_FAILURE);
|
||||
if (status == WL_NO_SSID_AVAIL) {
|
||||
connectionError = "Error: Network not found";
|
||||
connectionError = tr(STR_ERROR_NETWORK_NOT_FOUND);
|
||||
}
|
||||
state = WifiSelectionState::CONNECTION_FAILED;
|
||||
requestUpdate();
|
||||
@@ -278,7 +279,7 @@ void WifiSelectionActivity::checkConnectionStatus() {
|
||||
// Check for timeout
|
||||
if (millis() - connectionStartTime > CONNECTION_TIMEOUT_MS) {
|
||||
WiFi.disconnect();
|
||||
connectionError = "Error: Connection timeout";
|
||||
connectionError = tr(STR_ERROR_CONNECTION_TIMEOUT);
|
||||
state = WifiSelectionState::CONNECTION_FAILED;
|
||||
requestUpdate();
|
||||
return;
|
||||
@@ -510,14 +511,14 @@ void WifiSelectionActivity::renderNetworkList() const {
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
// Draw header
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "WiFi Networks", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_WIFI_NETWORKS), true, EpdFontFamily::BOLD);
|
||||
|
||||
if (networks.empty()) {
|
||||
// No networks found or scan failed
|
||||
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
||||
const auto top = (pageHeight - height) / 2;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top, "No networks found");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, top + height + 10, "Press Connect to scan again");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_NO_NETWORKS));
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, top + height + 10, tr(STR_PRESS_OK_SCAN));
|
||||
} else {
|
||||
// Calculate how many networks we can display
|
||||
constexpr int startY = 60;
|
||||
@@ -572,8 +573,8 @@ void WifiSelectionActivity::renderNetworkList() const {
|
||||
}
|
||||
|
||||
// Show network count
|
||||
char countStr[32];
|
||||
snprintf(countStr, sizeof(countStr), "%zu networks found", networks.size());
|
||||
char countStr[64];
|
||||
snprintf(countStr, sizeof(countStr), tr(STR_NETWORKS_FOUND), networks.size());
|
||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 90, countStr);
|
||||
}
|
||||
|
||||
@@ -581,12 +582,12 @@ void WifiSelectionActivity::renderNetworkList() const {
|
||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 105, cachedMacAddress.c_str());
|
||||
|
||||
// Draw help text
|
||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, "* = Encrypted | + = Saved");
|
||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, tr(STR_NETWORK_LEGEND));
|
||||
|
||||
const bool hasSavedPassword = !networks.empty() && networks[selectedNetworkIndex].hasSavedPassword;
|
||||
const char* forgetLabel = hasSavedPassword ? "Forget" : "";
|
||||
const char* forgetLabel = hasSavedPassword ? tr(STR_FORGET_BUTTON) : "";
|
||||
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Connect", forgetLabel, "Refresh");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_CONNECT), forgetLabel, tr(STR_RETRY));
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
}
|
||||
|
||||
@@ -596,11 +597,11 @@ void WifiSelectionActivity::renderConnecting() const {
|
||||
const auto top = (pageHeight - height) / 2;
|
||||
|
||||
if (state == WifiSelectionState::SCANNING) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top, "Scanning...");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_SCANNING));
|
||||
} else {
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, top - 40, "Connecting...", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, top - 40, tr(STR_CONNECTING), true, EpdFontFamily::BOLD);
|
||||
|
||||
std::string ssidInfo = "to " + selectedSSID;
|
||||
std::string ssidInfo = std::string(tr(STR_TO_PREFIX)) + selectedSSID;
|
||||
if (ssidInfo.length() > 25) {
|
||||
ssidInfo.replace(22, ssidInfo.length() - 22, "...");
|
||||
}
|
||||
@@ -613,19 +614,19 @@ void WifiSelectionActivity::renderConnected() const {
|
||||
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
||||
const auto top = (pageHeight - height * 4) / 2;
|
||||
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, top - 30, "Connected!", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, top - 30, tr(STR_CONNECTED), true, EpdFontFamily::BOLD);
|
||||
|
||||
std::string ssidInfo = "Network: " + selectedSSID;
|
||||
std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + selectedSSID;
|
||||
if (ssidInfo.length() > 28) {
|
||||
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
||||
}
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top + 10, ssidInfo.c_str());
|
||||
|
||||
const std::string ipInfo = "IP Address: " + connectedIP;
|
||||
const std::string ipInfo = std::string(tr(STR_IP_ADDRESS_PREFIX)) + connectedIP;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top + 40, ipInfo.c_str());
|
||||
|
||||
// Use centralized button hints
|
||||
const auto labels = mappedInput.mapLabels("", "Continue", "", "");
|
||||
const auto labels = mappedInput.mapLabels("", tr(STR_DONE), "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
}
|
||||
|
||||
@@ -635,15 +636,15 @@ void WifiSelectionActivity::renderSavePrompt() const {
|
||||
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
||||
const auto top = (pageHeight - height * 3) / 2;
|
||||
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, top - 40, "Connected!", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, top - 40, tr(STR_CONNECTED), true, EpdFontFamily::BOLD);
|
||||
|
||||
std::string ssidInfo = "Network: " + selectedSSID;
|
||||
std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + selectedSSID;
|
||||
if (ssidInfo.length() > 28) {
|
||||
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
||||
}
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top, ssidInfo.c_str());
|
||||
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top + 40, "Save password for next time?");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top + 40, tr(STR_SAVE_PASSWORD));
|
||||
|
||||
// Draw Yes/No buttons
|
||||
const int buttonY = top + 80;
|
||||
@@ -654,20 +655,22 @@ void WifiSelectionActivity::renderSavePrompt() const {
|
||||
|
||||
// Draw "Yes" button
|
||||
if (savePromptSelection == 0) {
|
||||
renderer.drawText(UI_10_FONT_ID, startX, buttonY, "[Yes]");
|
||||
std::string text = "[" + std::string(tr(STR_YES)) + "]";
|
||||
renderer.drawText(UI_10_FONT_ID, startX, buttonY, text.c_str());
|
||||
} else {
|
||||
renderer.drawText(UI_10_FONT_ID, startX + 4, buttonY, "Yes");
|
||||
renderer.drawText(UI_10_FONT_ID, startX + 4, buttonY, tr(STR_YES));
|
||||
}
|
||||
|
||||
// Draw "No" button
|
||||
if (savePromptSelection == 1) {
|
||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing, buttonY, "[No]");
|
||||
std::string text = "[" + std::string(tr(STR_NO)) + "]";
|
||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing, buttonY, text.c_str());
|
||||
} else {
|
||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing + 4, buttonY, "No");
|
||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing + 4, buttonY, tr(STR_NO));
|
||||
}
|
||||
|
||||
// Use centralized button hints
|
||||
const auto labels = mappedInput.mapLabels("« Skip", "Select", "Left", "Right");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_CANCEL), tr(STR_SELECT), tr(STR_DIR_LEFT), tr(STR_DIR_RIGHT));
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
}
|
||||
|
||||
@@ -676,11 +679,11 @@ void WifiSelectionActivity::renderConnectionFailed() const {
|
||||
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
||||
const auto top = (pageHeight - height * 2) / 2;
|
||||
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, top - 20, "Connection Failed", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, top - 20, tr(STR_CONNECTION_FAILED), true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top + 20, connectionError.c_str());
|
||||
|
||||
// Use centralized button hints
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Continue", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_DONE), "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
}
|
||||
|
||||
@@ -690,14 +693,15 @@ void WifiSelectionActivity::renderForgetPrompt() const {
|
||||
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
||||
const auto top = (pageHeight - height * 3) / 2;
|
||||
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, top - 40, "Forget Network", true, EpdFontFamily::BOLD);
|
||||
std::string ssidInfo = "Network: " + selectedSSID;
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, top - 40, tr(STR_FORGET_NETWORK), true, EpdFontFamily::BOLD);
|
||||
|
||||
std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + selectedSSID;
|
||||
if (ssidInfo.length() > 28) {
|
||||
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
||||
}
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top, ssidInfo.c_str());
|
||||
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top + 40, "Forget network and remove saved password?");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top + 40, tr(STR_FORGET_AND_REMOVE));
|
||||
|
||||
// Draw Cancel/Forget network buttons
|
||||
const int buttonY = top + 80;
|
||||
@@ -708,19 +712,21 @@ void WifiSelectionActivity::renderForgetPrompt() const {
|
||||
|
||||
// Draw "Cancel" button
|
||||
if (forgetPromptSelection == 0) {
|
||||
renderer.drawText(UI_10_FONT_ID, startX, buttonY, "[Cancel]");
|
||||
std::string text = "[" + std::string(tr(STR_CANCEL)) + "]";
|
||||
renderer.drawText(UI_10_FONT_ID, startX, buttonY, text.c_str());
|
||||
} else {
|
||||
renderer.drawText(UI_10_FONT_ID, startX + 4, buttonY, "Cancel");
|
||||
renderer.drawText(UI_10_FONT_ID, startX + 4, buttonY, tr(STR_CANCEL));
|
||||
}
|
||||
|
||||
// Draw "Forget network" button
|
||||
if (forgetPromptSelection == 1) {
|
||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing, buttonY, "[Forget network]");
|
||||
std::string text = "[" + std::string(tr(STR_FORGET_BUTTON)) + "]";
|
||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing, buttonY, text.c_str());
|
||||
} else {
|
||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing + 4, buttonY, "Forget network");
|
||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing + 4, buttonY, tr(STR_FORGET_BUTTON));
|
||||
}
|
||||
|
||||
// Use centralized button hints
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "Left", "Right");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_LEFT), tr(STR_DIR_RIGHT));
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <FsHelpers.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalStorage.h>
|
||||
#include <I18n.h>
|
||||
#include <Logging.h>
|
||||
|
||||
#include <PlaceholderCoverGenerator.h>
|
||||
@@ -477,7 +478,7 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
|
||||
BookmarkStore::addBookmark(epub->getCachePath(), currentSpineIndex, page, snippet);
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
GUI.drawPopup(renderer, "Bookmark added");
|
||||
GUI.drawPopup(renderer, tr(STR_BOOKMARK_ADDED));
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
xSemaphoreGive(renderingMutex);
|
||||
vTaskDelay(750 / portTICK_PERIOD_MS);
|
||||
@@ -492,7 +493,7 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
const int page = section ? section->currentPage : 0;
|
||||
BookmarkStore::removeBookmark(epub->getCachePath(), currentSpineIndex, page);
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
GUI.drawPopup(renderer, "Bookmark removed");
|
||||
GUI.drawPopup(renderer, tr(STR_BOOKMARK_REMOVED));
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
xSemaphoreGive(renderingMutex);
|
||||
vTaskDelay(750 / portTICK_PERIOD_MS);
|
||||
@@ -564,12 +565,12 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
if (Dictionary::cacheExists()) {
|
||||
Dictionary::deleteCache();
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
GUI.drawPopup(renderer, "Dictionary cache deleted");
|
||||
GUI.drawPopup(renderer, tr(STR_DICT_CACHE_DELETED));
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
xSemaphoreGive(renderingMutex);
|
||||
} else {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
GUI.drawPopup(renderer, "No cache to delete");
|
||||
GUI.drawPopup(renderer, tr(STR_NO_CACHE_TO_DELETE));
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
@@ -811,7 +812,7 @@ void EpubReaderActivity::render(Activity::RenderLock&& lock) {
|
||||
// Show end of book screen
|
||||
if (currentSpineIndex == epub->getSpineItemsCount()) {
|
||||
renderer.clearScreen();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "End of book", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_END_OF_BOOK), true, EpdFontFamily::BOLD);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
@@ -852,7 +853,7 @@ void EpubReaderActivity::render(Activity::RenderLock&& lock) {
|
||||
viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle)) {
|
||||
LOG_DBG("ERS", "Cache not found, building...");
|
||||
|
||||
const auto popupFn = [this]() { GUI.drawPopup(renderer, "Indexing..."); };
|
||||
const auto popupFn = [this]() { GUI.drawPopup(renderer, tr(STR_INDEXING)); };
|
||||
|
||||
if (!section->createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(),
|
||||
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
||||
@@ -900,7 +901,7 @@ void EpubReaderActivity::render(Activity::RenderLock&& lock) {
|
||||
|
||||
if (section->pageCount == 0) {
|
||||
LOG_DBG("ERS", "No pages to render");
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Empty chapter", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_EMPTY_CHAPTER), true, EpdFontFamily::BOLD);
|
||||
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
@@ -908,7 +909,7 @@ void EpubReaderActivity::render(Activity::RenderLock&& lock) {
|
||||
|
||||
if (section->currentPage < 0 || section->currentPage >= section->pageCount) {
|
||||
LOG_DBG("ERS", "Page out of bounds: %d (max %d)", section->currentPage, section->pageCount);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Out of bounds", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_OUT_OF_BOUNDS), true, EpdFontFamily::BOLD);
|
||||
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
@@ -1129,8 +1130,8 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in
|
||||
std::string title;
|
||||
int titleWidth;
|
||||
if (tocIndex == -1) {
|
||||
title = "Unnamed";
|
||||
titleWidth = renderer.getTextWidth(SMALL_FONT_ID, "Unnamed");
|
||||
title = tr(STR_UNNAMED);
|
||||
titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str());
|
||||
} else {
|
||||
const auto tocItem = epub->getTocItem(tocIndex);
|
||||
title = tocItem.title;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "EpubReaderChapterSelectionActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include "MappedInputManager.h"
|
||||
#include "components/UITheme.h"
|
||||
@@ -104,8 +105,8 @@ void EpubReaderChapterSelectionActivity::render(Activity::RenderLock&&) {
|
||||
|
||||
// Manual centering to honor content gutters.
|
||||
const int titleX =
|
||||
contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, "Go to Chapter", EpdFontFamily::BOLD)) / 2;
|
||||
renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentY, "Go to Chapter", true, EpdFontFamily::BOLD);
|
||||
contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, tr(STR_SELECT_CHAPTER), EpdFontFamily::BOLD)) / 2;
|
||||
renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentY, tr(STR_SELECT_CHAPTER), true, EpdFontFamily::BOLD);
|
||||
|
||||
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
|
||||
// Highlight only the content area, not the hint gutters.
|
||||
@@ -127,7 +128,7 @@ void EpubReaderChapterSelectionActivity::render(Activity::RenderLock&&) {
|
||||
renderer.drawText(UI_10_FONT_ID, indentSize, displayY, chapterName.c_str(), !isSelected);
|
||||
}
|
||||
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
renderer.displayBuffer();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "EpubReaderMenuActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include "MappedInputManager.h"
|
||||
#include "components/UITheme.h"
|
||||
@@ -92,9 +93,10 @@ void EpubReaderMenuActivity::render(Activity::RenderLock&&) {
|
||||
// Progress summary
|
||||
std::string progressLine;
|
||||
if (totalPages > 0) {
|
||||
progressLine = "Chapter: " + std::to_string(currentPage) + "/" + std::to_string(totalPages) + " pages | ";
|
||||
progressLine = std::string(tr(STR_CHAPTER_PREFIX)) + std::to_string(currentPage) + "/" +
|
||||
std::to_string(totalPages) + std::string(tr(STR_PAGES_SEPARATOR));
|
||||
}
|
||||
progressLine += "Book: " + std::to_string(bookProgressPercent) + "%";
|
||||
progressLine += std::string(tr(STR_BOOK_PREFIX)) + std::to_string(bookProgressPercent) + "%";
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 45, progressLine.c_str());
|
||||
|
||||
// Menu Items
|
||||
@@ -110,24 +112,24 @@ void EpubReaderMenuActivity::render(Activity::RenderLock&&) {
|
||||
renderer.fillRect(contentX, displayY, contentWidth - 1, lineHeight, true);
|
||||
}
|
||||
|
||||
renderer.drawText(UI_10_FONT_ID, contentX + 20, displayY, menuItems[i].label.c_str(), !isSelected);
|
||||
renderer.drawText(UI_10_FONT_ID, contentX + 20, displayY, I18N.get(menuItems[i].labelId), !isSelected);
|
||||
|
||||
if (menuItems[i].action == MenuAction::ROTATE_SCREEN) {
|
||||
// Render current orientation value on the right edge of the content area.
|
||||
const auto value = orientationLabels[pendingOrientation];
|
||||
const char* value = I18N.get(orientationLabels[pendingOrientation]);
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, value);
|
||||
renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected);
|
||||
}
|
||||
if (menuItems[i].action == MenuAction::LETTERBOX_FILL) {
|
||||
// Render current letterbox fill value on the right edge of the content area.
|
||||
const auto value = letterboxFillLabels[letterboxFillToIndex()];
|
||||
const char* value = I18N.get(letterboxFillLabels[letterboxFillToIndex()]);
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, value);
|
||||
renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected);
|
||||
}
|
||||
}
|
||||
|
||||
// Footer / Hints
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
renderer.displayBuffer();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <Epub.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
@@ -57,7 +58,7 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity {
|
||||
private:
|
||||
struct MenuItem {
|
||||
MenuAction action;
|
||||
std::string label;
|
||||
StrId labelId;
|
||||
};
|
||||
|
||||
std::vector<MenuItem> menuItems;
|
||||
@@ -67,12 +68,14 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity {
|
||||
ButtonNavigator buttonNavigator;
|
||||
std::string title = "Reader Menu";
|
||||
uint8_t pendingOrientation = 0;
|
||||
const std::vector<const char*> orientationLabels = {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"};
|
||||
const std::vector<StrId> orientationLabels = {StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED,
|
||||
StrId::STR_LANDSCAPE_CCW};
|
||||
std::string bookCachePath;
|
||||
// Letterbox fill override: 0xFF = Default (use global), 0 = Dithered, 1 = Solid, 2 = None
|
||||
uint8_t pendingLetterboxFill = BookSettings::USE_GLOBAL;
|
||||
static constexpr int LETTERBOX_FILL_OPTION_COUNT = 4; // Default + 3 modes
|
||||
const std::vector<const char*> letterboxFillLabels = {"Default", "Dithered", "Solid", "None"};
|
||||
const std::vector<StrId> letterboxFillLabels = {StrId::STR_DEFAULT_OPTION, StrId::STR_DITHERED, StrId::STR_SOLID,
|
||||
StrId::STR_NONE_OPT};
|
||||
int currentPage = 0;
|
||||
int totalPages = 0;
|
||||
int bookProgressPercent = 0;
|
||||
@@ -103,24 +106,24 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity {
|
||||
static std::vector<MenuItem> buildMenuItems(bool hasDictionary, bool isBookmarked) {
|
||||
std::vector<MenuItem> items;
|
||||
if (isBookmarked) {
|
||||
items.push_back({MenuAction::REMOVE_BOOKMARK, "Remove Bookmark"});
|
||||
items.push_back({MenuAction::REMOVE_BOOKMARK, StrId::STR_REMOVE_BOOKMARK});
|
||||
} else {
|
||||
items.push_back({MenuAction::ADD_BOOKMARK, "Add Bookmark"});
|
||||
items.push_back({MenuAction::ADD_BOOKMARK, StrId::STR_ADD_BOOKMARK});
|
||||
}
|
||||
if (hasDictionary) {
|
||||
items.push_back({MenuAction::LOOKUP, "Lookup Word"});
|
||||
items.push_back({MenuAction::LOOKED_UP_WORDS, "Lookup Word History"});
|
||||
items.push_back({MenuAction::LOOKUP, StrId::STR_LOOKUP_WORD});
|
||||
items.push_back({MenuAction::LOOKED_UP_WORDS, StrId::STR_LOOKUP_HISTORY});
|
||||
}
|
||||
items.push_back({MenuAction::ROTATE_SCREEN, "Reading Orientation"});
|
||||
items.push_back({MenuAction::LETTERBOX_FILL, "Letterbox Fill"});
|
||||
items.push_back({MenuAction::SELECT_CHAPTER, "Table of Contents"});
|
||||
items.push_back({MenuAction::GO_TO_BOOKMARK, "Go to Bookmark"});
|
||||
items.push_back({MenuAction::GO_TO_PERCENT, "Go to %"});
|
||||
items.push_back({MenuAction::GO_HOME, "Close Book"});
|
||||
items.push_back({MenuAction::SYNC, "Sync Progress"});
|
||||
items.push_back({MenuAction::DELETE_CACHE, "Delete Book Cache"});
|
||||
items.push_back({MenuAction::ROTATE_SCREEN, StrId::STR_ORIENTATION});
|
||||
items.push_back({MenuAction::LETTERBOX_FILL, StrId::STR_LETTERBOX_FILL});
|
||||
items.push_back({MenuAction::SELECT_CHAPTER, StrId::STR_TABLE_OF_CONTENTS});
|
||||
items.push_back({MenuAction::GO_TO_BOOKMARK, StrId::STR_GO_TO_BOOKMARK});
|
||||
items.push_back({MenuAction::GO_TO_PERCENT, StrId::STR_GO_TO_PERCENT});
|
||||
items.push_back({MenuAction::GO_HOME, StrId::STR_CLOSE_BOOK});
|
||||
items.push_back({MenuAction::SYNC, StrId::STR_SYNC_PROGRESS});
|
||||
items.push_back({MenuAction::DELETE_CACHE, StrId::STR_DELETE_CACHE});
|
||||
if (hasDictionary) {
|
||||
items.push_back({MenuAction::DELETE_DICT_CACHE, "Delete Dictionary Cache"});
|
||||
items.push_back({MenuAction::DELETE_DICT_CACHE, StrId::STR_DELETE_DICT_CACHE});
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "EpubReaderPercentSelectionActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include "MappedInputManager.h"
|
||||
#include "components/UITheme.h"
|
||||
@@ -59,7 +60,7 @@ void EpubReaderPercentSelectionActivity::render(Activity::RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
// Title and numeric percent value.
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Go to Position", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_GO_TO_PERCENT), true, EpdFontFamily::BOLD);
|
||||
|
||||
const std::string percentText = std::to_string(percent) + "%";
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 90, percentText.c_str(), true, EpdFontFamily::BOLD);
|
||||
@@ -84,10 +85,10 @@ void EpubReaderPercentSelectionActivity::render(Activity::RenderLock&&) {
|
||||
renderer.fillRect(knobX, barY - 4, 4, barHeight + 8, true);
|
||||
|
||||
// Hint text for step sizes.
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, barY + 30, "Left/Right: 1% Up/Down: 10%", true);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, barY + 30, tr(STR_PERCENT_STEP_HINT), true);
|
||||
|
||||
// Button hints follow the current front button layout.
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "-", "+");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "-", "+");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
renderer.displayBuffer();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "KOReaderSyncActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
#include <Logging.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_sntp.h>
|
||||
@@ -54,7 +55,7 @@ void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) {
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
state = SYNCING;
|
||||
statusMessage = "Syncing time...";
|
||||
statusMessage = tr(STR_SYNCING_TIME);
|
||||
}
|
||||
requestUpdate();
|
||||
|
||||
@@ -63,7 +64,7 @@ void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) {
|
||||
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
statusMessage = "Calculating document hash...";
|
||||
statusMessage = tr(STR_CALC_HASH);
|
||||
}
|
||||
requestUpdate();
|
||||
|
||||
@@ -81,7 +82,7 @@ void KOReaderSyncActivity::performSync() {
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
state = SYNC_FAILED;
|
||||
statusMessage = "Failed to calculate document hash";
|
||||
statusMessage = tr(STR_HASH_FAILED);
|
||||
}
|
||||
requestUpdate();
|
||||
return;
|
||||
@@ -91,7 +92,7 @@ void KOReaderSyncActivity::performSync() {
|
||||
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
statusMessage = "Fetching remote progress...";
|
||||
statusMessage = tr(STR_FETCH_PROGRESS);
|
||||
}
|
||||
requestUpdateAndWait();
|
||||
|
||||
@@ -140,7 +141,7 @@ void KOReaderSyncActivity::performUpload() {
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
state = UPLOADING;
|
||||
statusMessage = "Uploading progress...";
|
||||
statusMessage = tr(STR_UPLOAD_PROGRESS);
|
||||
}
|
||||
requestUpdate();
|
||||
requestUpdateAndWait();
|
||||
@@ -191,7 +192,7 @@ void KOReaderSyncActivity::onEnter() {
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
LOG_DBG("KOSync", "Already connected to WiFi");
|
||||
state = SYNCING;
|
||||
statusMessage = "Syncing time...";
|
||||
statusMessage = tr(STR_SYNCING_TIME);
|
||||
requestUpdate();
|
||||
|
||||
// Perform sync directly (will be handled in loop)
|
||||
@@ -202,7 +203,7 @@ void KOReaderSyncActivity::onEnter() {
|
||||
syncTimeWithNTP();
|
||||
{
|
||||
RenderLock lock(*self);
|
||||
self->statusMessage = "Calculating document hash...";
|
||||
self->statusMessage = tr(STR_CALC_HASH);
|
||||
}
|
||||
self->requestUpdate();
|
||||
self->performSync();
|
||||
@@ -236,13 +237,13 @@ void KOReaderSyncActivity::render(Activity::RenderLock&&) {
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
|
||||
renderer.clearScreen();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "KOReader Sync", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_KOREADER_SYNC), true, EpdFontFamily::BOLD);
|
||||
|
||||
if (state == NO_CREDENTIALS) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, "No credentials configured", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 320, "Set up KOReader account in Settings");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, tr(STR_NO_CREDENTIALS_MSG), true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 320, tr(STR_KOREADER_SETUP_HINT));
|
||||
|
||||
const auto labels = mappedInput.mapLabels("Back", "", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
@@ -256,40 +257,41 @@ void KOReaderSyncActivity::render(Activity::RenderLock&&) {
|
||||
|
||||
if (state == SHOWING_RESULT) {
|
||||
// Show comparison
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 120, "Progress found!", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 120, tr(STR_PROGRESS_FOUND), true, EpdFontFamily::BOLD);
|
||||
|
||||
// Get chapter names from TOC
|
||||
const int remoteTocIndex = epub->getTocIndexForSpineIndex(remotePosition.spineIndex);
|
||||
const int localTocIndex = epub->getTocIndexForSpineIndex(currentSpineIndex);
|
||||
const std::string remoteChapter = (remoteTocIndex >= 0)
|
||||
? epub->getTocItem(remoteTocIndex).title
|
||||
: ("Section " + std::to_string(remotePosition.spineIndex + 1));
|
||||
const std::string localChapter = (localTocIndex >= 0) ? epub->getTocItem(localTocIndex).title
|
||||
: ("Section " + std::to_string(currentSpineIndex + 1));
|
||||
const std::string remoteChapter =
|
||||
(remoteTocIndex >= 0) ? epub->getTocItem(remoteTocIndex).title
|
||||
: (std::string(tr(STR_SECTION_PREFIX)) + std::to_string(remotePosition.spineIndex + 1));
|
||||
const std::string localChapter =
|
||||
(localTocIndex >= 0) ? epub->getTocItem(localTocIndex).title
|
||||
: (std::string(tr(STR_SECTION_PREFIX)) + std::to_string(currentSpineIndex + 1));
|
||||
|
||||
// Remote progress - chapter and page
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 160, "Remote:", true);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 160, tr(STR_REMOTE_LABEL), true);
|
||||
char remoteChapterStr[128];
|
||||
snprintf(remoteChapterStr, sizeof(remoteChapterStr), " %s", remoteChapter.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 185, remoteChapterStr);
|
||||
char remotePageStr[64];
|
||||
snprintf(remotePageStr, sizeof(remotePageStr), " Page %d, %.2f%% overall", remotePosition.pageNumber + 1,
|
||||
snprintf(remotePageStr, sizeof(remotePageStr), tr(STR_PAGE_OVERALL_FORMAT), remotePosition.pageNumber + 1,
|
||||
remoteProgress.percentage * 100);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 210, remotePageStr);
|
||||
|
||||
if (!remoteProgress.device.empty()) {
|
||||
char deviceStr[64];
|
||||
snprintf(deviceStr, sizeof(deviceStr), " From: %s", remoteProgress.device.c_str());
|
||||
snprintf(deviceStr, sizeof(deviceStr), tr(STR_DEVICE_FROM_FORMAT), remoteProgress.device.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 235, deviceStr);
|
||||
}
|
||||
|
||||
// Local progress - chapter and page
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 270, "Local:", true);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 270, tr(STR_LOCAL_LABEL), true);
|
||||
char localChapterStr[128];
|
||||
snprintf(localChapterStr, sizeof(localChapterStr), " %s", localChapter.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 295, localChapterStr);
|
||||
char localPageStr[64];
|
||||
snprintf(localPageStr, sizeof(localPageStr), " Page %d/%d, %.2f%% overall", currentPage + 1, totalPagesInSpine,
|
||||
snprintf(localPageStr, sizeof(localPageStr), tr(STR_PAGE_TOTAL_OVERALL_FORMAT), currentPage + 1, totalPagesInSpine,
|
||||
localProgress.percentage * 100);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 320, localPageStr);
|
||||
|
||||
@@ -300,45 +302,45 @@ void KOReaderSyncActivity::render(Activity::RenderLock&&) {
|
||||
if (selectedOption == 0) {
|
||||
renderer.fillRect(0, optionY - 2, pageWidth - 1, optionHeight);
|
||||
}
|
||||
renderer.drawText(UI_10_FONT_ID, 20, optionY, "Apply remote progress", selectedOption != 0);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, optionY, tr(STR_APPLY_REMOTE), selectedOption != 0);
|
||||
|
||||
// Upload option
|
||||
if (selectedOption == 1) {
|
||||
renderer.fillRect(0, optionY + optionHeight - 2, pageWidth - 1, optionHeight);
|
||||
}
|
||||
renderer.drawText(UI_10_FONT_ID, 20, optionY + optionHeight, "Upload local progress", selectedOption != 1);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, optionY + optionHeight, tr(STR_UPLOAD_LOCAL), selectedOption != 1);
|
||||
|
||||
// Bottom button hints: show Back and Select
|
||||
const auto labels = mappedInput.mapLabels("Back", "Select", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == NO_REMOTE_PROGRESS) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, "No remote progress found", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 320, "Upload current position?");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, tr(STR_NO_REMOTE_MSG), true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 320, tr(STR_UPLOAD_PROMPT));
|
||||
|
||||
const auto labels = mappedInput.mapLabels("Back", "Upload", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_UPLOAD), "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == UPLOAD_COMPLETE) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, "Progress uploaded!", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, tr(STR_UPLOAD_SUCCESS), true, EpdFontFamily::BOLD);
|
||||
|
||||
const auto labels = mappedInput.mapLabels("Back", "", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == SYNC_FAILED) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, "Sync failed", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, tr(STR_SYNC_FAILED_MSG), true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 320, statusMessage.c_str());
|
||||
|
||||
const auto labels = mappedInput.mapLabels("Back", "", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalStorage.h>
|
||||
#include <I18n.h>
|
||||
#include <Serialization.h>
|
||||
#include <Utf8.h>
|
||||
|
||||
@@ -223,7 +224,7 @@ void TxtReaderActivity::buildPageIndex() {
|
||||
|
||||
LOG_DBG("TRS", "Building page index for %zu bytes...", fileSize);
|
||||
|
||||
GUI.drawPopup(renderer, "Indexing...");
|
||||
GUI.drawPopup(renderer, tr(STR_INDEXING));
|
||||
|
||||
while (offset < fileSize) {
|
||||
std::vector<std::string> tempLines;
|
||||
@@ -391,7 +392,7 @@ void TxtReaderActivity::render(Activity::RenderLock&&) {
|
||||
|
||||
if (pageOffsets.empty()) {
|
||||
renderer.clearScreen();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Empty file", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_EMPTY_FILE), true, EpdFontFamily::BOLD);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <FsHelpers.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalStorage.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include <PlaceholderCoverGenerator.h>
|
||||
|
||||
@@ -187,7 +188,7 @@ void XtcReaderActivity::render(Activity::RenderLock&&) {
|
||||
if (currentPage >= xtc->getPageCount()) {
|
||||
// Show end of book screen
|
||||
renderer.clearScreen();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "End of book", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_END_OF_BOOK), true, EpdFontFamily::BOLD);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
@@ -216,7 +217,7 @@ void XtcReaderActivity::renderPage() {
|
||||
if (!pageBuffer) {
|
||||
LOG_ERR("XTR", "Failed to allocate page buffer (%lu bytes)", pageBufferSize);
|
||||
renderer.clearScreen();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Memory error", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_MEMORY_ERROR), true, EpdFontFamily::BOLD);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
@@ -227,7 +228,7 @@ void XtcReaderActivity::renderPage() {
|
||||
LOG_ERR("XTR", "Failed to load page %lu", currentPage);
|
||||
free(pageBuffer);
|
||||
renderer.clearScreen();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, "Page load error", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_PAGE_LOAD_ERROR), true, EpdFontFamily::BOLD);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "XtcReaderChapterSelectionActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -104,14 +105,14 @@ void XtcReaderChapterSelectionActivity::render(Activity::RenderLock&&) {
|
||||
const int pageItems = getPageItems();
|
||||
// Manual centering to honor content gutters.
|
||||
const int titleX =
|
||||
contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, "Select Chapter", EpdFontFamily::BOLD)) / 2;
|
||||
renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentY, "Select Chapter", true, EpdFontFamily::BOLD);
|
||||
contentX + (contentWidth - renderer.getTextWidth(UI_12_FONT_ID, tr(STR_SELECT_CHAPTER), EpdFontFamily::BOLD)) / 2;
|
||||
renderer.drawText(UI_12_FONT_ID, titleX, 15 + contentY, tr(STR_SELECT_CHAPTER), true, EpdFontFamily::BOLD);
|
||||
|
||||
const auto& chapters = xtc->getChapters();
|
||||
if (chapters.empty()) {
|
||||
// Center the empty state within the gutter-safe content region.
|
||||
const int emptyX = contentX + (contentWidth - renderer.getTextWidth(UI_10_FONT_ID, "No chapters")) / 2;
|
||||
renderer.drawText(UI_10_FONT_ID, emptyX, 120 + contentY, "No chapters");
|
||||
const int emptyX = contentX + (contentWidth - renderer.getTextWidth(UI_10_FONT_ID, tr(STR_NO_CHAPTERS))) / 2;
|
||||
renderer.drawText(UI_10_FONT_ID, emptyX, 120 + contentY, tr(STR_NO_CHAPTERS));
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
@@ -121,13 +122,13 @@ void XtcReaderChapterSelectionActivity::render(Activity::RenderLock&&) {
|
||||
renderer.fillRect(contentX, 60 + contentY + (selectorIndex % pageItems) * 30 - 2, contentWidth - 1, 30);
|
||||
for (int i = pageStartIndex; i < static_cast<int>(chapters.size()) && i < pageStartIndex + pageItems; i++) {
|
||||
const auto& chapter = chapters[i];
|
||||
const char* title = chapter.name.empty() ? "Unnamed" : chapter.name.c_str();
|
||||
const char* title = chapter.name.empty() ? tr(STR_UNNAMED) : chapter.name.c_str();
|
||||
renderer.drawText(UI_10_FONT_ID, contentX + 20, 60 + contentY + (i % pageItems) * 30, title, i != selectorIndex);
|
||||
}
|
||||
|
||||
// Skip button hints in landscape CW mode (they overlap content)
|
||||
if (renderer.getOrientation() != GfxRenderer::LandscapeClockwise) {
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "ButtonRemapActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "MappedInputManager.h"
|
||||
@@ -106,8 +107,8 @@ void ButtonRemapActivity::render(Activity::RenderLock&&) {
|
||||
return "-";
|
||||
};
|
||||
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Remap Front Buttons", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 40, "Press a front button for each role");
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_REMAP_FRONT_BUTTONS), true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 40, tr(STR_REMAP_PROMPT));
|
||||
|
||||
for (uint8_t i = 0; i < kRoleCount; i++) {
|
||||
const int y = 70 + i * 30;
|
||||
@@ -122,7 +123,7 @@ void ButtonRemapActivity::render(Activity::RenderLock&&) {
|
||||
renderer.drawText(UI_10_FONT_ID, 20, y, roleName, !isSelected);
|
||||
|
||||
// Show currently assigned hardware button (or unassigned).
|
||||
const char* assigned = (tempMapping[i] == kUnassigned) ? "Unassigned" : getHardwareName(tempMapping[i]);
|
||||
const char* assigned = (tempMapping[i] == kUnassigned) ? tr(STR_UNASSIGNED) : getHardwareName(tempMapping[i]);
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, assigned);
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, y, assigned, !isSelected);
|
||||
}
|
||||
@@ -133,8 +134,8 @@ void ButtonRemapActivity::render(Activity::RenderLock&&) {
|
||||
}
|
||||
|
||||
// Provide side button actions at the bottom of the screen (split across two lines).
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, 250, "Side button Up: Reset to default layout", true);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, 280, "Side button Down: Cancel remapping", true);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, 250, tr(STR_REMAP_RESET_HINT), true);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, 280, tr(STR_REMAP_CANCEL_HINT), true);
|
||||
|
||||
// Live preview of logical labels under front buttons.
|
||||
// This mirrors the on-device front button order: Back, Confirm, Left, Right.
|
||||
@@ -157,7 +158,7 @@ bool ButtonRemapActivity::validateUnassigned(const uint8_t pressedButton) {
|
||||
// Block reusing a hardware button already assigned to another role.
|
||||
for (uint8_t i = 0; i < kRoleCount; i++) {
|
||||
if (tempMapping[i] == pressedButton && i != currentStep) {
|
||||
errorMessage = "Already assigned";
|
||||
errorMessage = tr(STR_ALREADY_ASSIGNED);
|
||||
errorUntil = millis() + kErrorDisplayMs;
|
||||
return false;
|
||||
}
|
||||
@@ -168,27 +169,27 @@ bool ButtonRemapActivity::validateUnassigned(const uint8_t pressedButton) {
|
||||
const char* ButtonRemapActivity::getRoleName(const uint8_t roleIndex) const {
|
||||
switch (roleIndex) {
|
||||
case 0:
|
||||
return "Back";
|
||||
return tr(STR_BACK);
|
||||
case 1:
|
||||
return "Confirm";
|
||||
return tr(STR_CONFIRM);
|
||||
case 2:
|
||||
return "Left";
|
||||
return tr(STR_DIR_LEFT);
|
||||
case 3:
|
||||
default:
|
||||
return "Right";
|
||||
return tr(STR_DIR_RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
const char* ButtonRemapActivity::getHardwareName(const uint8_t buttonIndex) const {
|
||||
switch (buttonIndex) {
|
||||
case CrossPointSettings::FRONT_HW_BACK:
|
||||
return "Back (1st button)";
|
||||
return tr(STR_HW_BACK_LABEL);
|
||||
case CrossPointSettings::FRONT_HW_CONFIRM:
|
||||
return "Confirm (2nd button)";
|
||||
return tr(STR_HW_CONFIRM_LABEL);
|
||||
case CrossPointSettings::FRONT_HW_LEFT:
|
||||
return "Left (3rd button)";
|
||||
return tr(STR_HW_LEFT_LABEL);
|
||||
case CrossPointSettings::FRONT_HW_RIGHT:
|
||||
return "Right (4th button)";
|
||||
return tr(STR_HW_RIGHT_LABEL);
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "CalibreSettingsActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -12,7 +13,7 @@
|
||||
|
||||
namespace {
|
||||
constexpr int MENU_ITEMS = 3;
|
||||
const char* menuNames[MENU_ITEMS] = {"OPDS Server URL", "Username", "Password"};
|
||||
const StrId menuNames[MENU_ITEMS] = {StrId::STR_CALIBRE_WEB_URL, StrId::STR_USERNAME, StrId::STR_PASSWORD};
|
||||
} // namespace
|
||||
|
||||
void CalibreSettingsActivity::onEnter() {
|
||||
@@ -57,7 +58,7 @@ void CalibreSettingsActivity::handleSelection() {
|
||||
// OPDS Server URL
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, "OPDS Server URL", SETTINGS.opdsServerUrl, 10,
|
||||
renderer, mappedInput, tr(STR_CALIBRE_WEB_URL), SETTINGS.opdsServerUrl, 10,
|
||||
127, // maxLength
|
||||
false, // not password
|
||||
[this](const std::string& url) {
|
||||
@@ -75,7 +76,7 @@ void CalibreSettingsActivity::handleSelection() {
|
||||
// Username
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, "Username", SETTINGS.opdsUsername, 10,
|
||||
renderer, mappedInput, tr(STR_USERNAME), SETTINGS.opdsUsername, 10,
|
||||
63, // maxLength
|
||||
false, // not password
|
||||
[this](const std::string& username) {
|
||||
@@ -93,7 +94,7 @@ void CalibreSettingsActivity::handleSelection() {
|
||||
// Password
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, "Password", SETTINGS.opdsPassword, 10,
|
||||
renderer, mappedInput, tr(STR_PASSWORD), SETTINGS.opdsPassword, 10,
|
||||
63, // maxLength
|
||||
false, // not password mode
|
||||
[this](const std::string& password) {
|
||||
@@ -116,10 +117,10 @@ void CalibreSettingsActivity::render(Activity::RenderLock&&) {
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
|
||||
// Draw header
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "OPDS Browser", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_OPDS_BROWSER), true, EpdFontFamily::BOLD);
|
||||
|
||||
// Draw info text about Calibre
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 40, "For Calibre, add /opds to your URL");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 40, tr(STR_CALIBRE_URL_HINT));
|
||||
|
||||
// Draw selection highlight
|
||||
renderer.fillRect(0, 70 + selectedIndex * 30 - 2, pageWidth - 1, 30);
|
||||
@@ -129,23 +130,26 @@ void CalibreSettingsActivity::render(Activity::RenderLock&&) {
|
||||
const int settingY = 70 + i * 30;
|
||||
const bool isSelected = (i == selectedIndex);
|
||||
|
||||
renderer.drawText(UI_10_FONT_ID, 20, settingY, menuNames[i], !isSelected);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, settingY, I18N.get(menuNames[i]), !isSelected);
|
||||
|
||||
// Draw status for each setting
|
||||
const char* status = "[Not Set]";
|
||||
std::string status = std::string("[") + tr(STR_NOT_SET) + "]";
|
||||
if (i == 0) {
|
||||
status = (strlen(SETTINGS.opdsServerUrl) > 0) ? "[Set]" : "[Not Set]";
|
||||
status = (strlen(SETTINGS.opdsServerUrl) > 0) ? std::string("[") + tr(STR_SET) + "]"
|
||||
: std::string("[") + tr(STR_NOT_SET) + "]";
|
||||
} else if (i == 1) {
|
||||
status = (strlen(SETTINGS.opdsUsername) > 0) ? "[Set]" : "[Not Set]";
|
||||
status = (strlen(SETTINGS.opdsUsername) > 0) ? std::string("[") + tr(STR_SET) + "]"
|
||||
: std::string("[") + tr(STR_NOT_SET) + "]";
|
||||
} else if (i == 2) {
|
||||
status = (strlen(SETTINGS.opdsPassword) > 0) ? "[Set]" : "[Not Set]";
|
||||
status = (strlen(SETTINGS.opdsPassword) > 0) ? std::string("[") + tr(STR_SET) + "]"
|
||||
: std::string("[") + tr(STR_NOT_SET) + "]";
|
||||
}
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, status);
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, status, !isSelected);
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, status.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, status.c_str(), !isSelected);
|
||||
}
|
||||
|
||||
// Draw button hints
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
renderer.displayBuffer();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalStorage.h>
|
||||
#include <I18n.h>
|
||||
#include <Logging.h>
|
||||
|
||||
#include "MappedInputManager.h"
|
||||
@@ -21,46 +22,47 @@ void ClearCacheActivity::render(Activity::RenderLock&&) {
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
renderer.clearScreen();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Clear Cache", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_CLEAR_READING_CACHE), true, EpdFontFamily::BOLD);
|
||||
|
||||
if (state == WARNING) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 60, "This will clear all cached book data.", true);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 30, "All reading progress will be lost!", true,
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 60, tr(STR_CLEAR_CACHE_WARNING_1), true);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 30, tr(STR_CLEAR_CACHE_WARNING_2), true,
|
||||
EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, "Books will need to be re-indexed", true);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 30, "when opened again.", true);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, tr(STR_CLEAR_CACHE_WARNING_3), true);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 30, tr(STR_CLEAR_CACHE_WARNING_4), true);
|
||||
|
||||
const auto labels = mappedInput.mapLabels("« Cancel", "Clear", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_CANCEL), tr(STR_CLEAR_BUTTON), "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == CLEARING) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, "Clearing cache...", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, tr(STR_CLEARING_CACHE), true, EpdFontFamily::BOLD);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == SUCCESS) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, "Cache Cleared", true, EpdFontFamily::BOLD);
|
||||
String resultText = String(clearedCount) + " items removed";
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, tr(STR_CACHE_CLEARED), true, EpdFontFamily::BOLD);
|
||||
std::string resultText = std::to_string(clearedCount) + " " + std::string(tr(STR_ITEMS_REMOVED));
|
||||
if (failedCount > 0) {
|
||||
resultText += ", " + String(failedCount) + " failed";
|
||||
resultText += ", " + std::to_string(failedCount) + " " + std::string(tr(STR_FAILED_LOWER));
|
||||
}
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, resultText.c_str());
|
||||
|
||||
const auto labels = mappedInput.mapLabels("« Back", "", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == FAILED) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, "Failed to clear cache", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, "Check serial output for details");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, tr(STR_CLEAR_CACHE_FAILED), true,
|
||||
EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, tr(STR_CHECK_SERIAL_OUTPUT));
|
||||
|
||||
const auto labels = mappedInput.mapLabels("« Back", "", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "KOReaderAuthActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include "KOReaderCredentialStore.h"
|
||||
@@ -17,7 +18,7 @@ void KOReaderAuthActivity::onWifiSelectionComplete(const bool success) {
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
state = FAILED;
|
||||
errorMessage = "WiFi connection failed";
|
||||
errorMessage = tr(STR_WIFI_CONN_FAILED);
|
||||
}
|
||||
requestUpdate();
|
||||
return;
|
||||
@@ -26,7 +27,7 @@ void KOReaderAuthActivity::onWifiSelectionComplete(const bool success) {
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
state = AUTHENTICATING;
|
||||
statusMessage = "Authenticating...";
|
||||
statusMessage = tr(STR_AUTHENTICATING);
|
||||
}
|
||||
requestUpdate();
|
||||
|
||||
@@ -40,7 +41,7 @@ void KOReaderAuthActivity::performAuthentication() {
|
||||
RenderLock lock(*this);
|
||||
if (result == KOReaderSyncClient::OK) {
|
||||
state = SUCCESS;
|
||||
statusMessage = "Successfully authenticated!";
|
||||
statusMessage = tr(STR_AUTH_SUCCESS);
|
||||
} else {
|
||||
state = FAILED;
|
||||
errorMessage = KOReaderSyncClient::errorString(result);
|
||||
@@ -58,7 +59,7 @@ void KOReaderAuthActivity::onEnter() {
|
||||
// Check if already connected
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
state = AUTHENTICATING;
|
||||
statusMessage = "Authenticating...";
|
||||
statusMessage = tr(STR_AUTHENTICATING);
|
||||
requestUpdate();
|
||||
|
||||
// Perform authentication in a separate task
|
||||
@@ -89,7 +90,7 @@ void KOReaderAuthActivity::onExit() {
|
||||
|
||||
void KOReaderAuthActivity::render(Activity::RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "KOReader Auth", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_KOREADER_AUTH), true, EpdFontFamily::BOLD);
|
||||
|
||||
if (state == AUTHENTICATING) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, statusMessage.c_str(), true, EpdFontFamily::BOLD);
|
||||
@@ -98,20 +99,20 @@ void KOReaderAuthActivity::render(Activity::RenderLock&&) {
|
||||
}
|
||||
|
||||
if (state == SUCCESS) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, "Success!", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 320, "KOReader sync is ready to use");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, tr(STR_AUTH_SUCCESS), true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 320, tr(STR_SYNC_READY));
|
||||
|
||||
const auto labels = mappedInput.mapLabels("Done", "", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_DONE), "", "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == FAILED) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, "Authentication Failed", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, tr(STR_AUTH_FAILED), true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 320, errorMessage.c_str());
|
||||
|
||||
const auto labels = mappedInput.mapLabels("Back", "", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "KOReaderSettingsActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -13,7 +14,8 @@
|
||||
|
||||
namespace {
|
||||
constexpr int MENU_ITEMS = 5;
|
||||
const char* menuNames[MENU_ITEMS] = {"Username", "Password", "Sync Server URL", "Document Matching", "Authenticate"};
|
||||
const StrId menuNames[MENU_ITEMS] = {StrId::STR_USERNAME, StrId::STR_PASSWORD, StrId::STR_SYNC_SERVER_URL,
|
||||
StrId::STR_DOCUMENT_MATCHING, StrId::STR_AUTHENTICATE};
|
||||
} // namespace
|
||||
|
||||
void KOReaderSettingsActivity::onEnter() {
|
||||
@@ -58,7 +60,7 @@ void KOReaderSettingsActivity::handleSelection() {
|
||||
// Username
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, "KOReader Username", KOREADER_STORE.getUsername(), 10,
|
||||
renderer, mappedInput, tr(STR_KOREADER_USERNAME), KOREADER_STORE.getUsername(), 10,
|
||||
64, // maxLength
|
||||
false, // not password
|
||||
[this](const std::string& username) {
|
||||
@@ -75,7 +77,7 @@ void KOReaderSettingsActivity::handleSelection() {
|
||||
// Password
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, "KOReader Password", KOREADER_STORE.getPassword(), 10,
|
||||
renderer, mappedInput, tr(STR_KOREADER_PASSWORD), KOREADER_STORE.getPassword(), 10,
|
||||
64, // maxLength
|
||||
false, // show characters
|
||||
[this](const std::string& password) {
|
||||
@@ -94,7 +96,7 @@ void KOReaderSettingsActivity::handleSelection() {
|
||||
const std::string prefillUrl = currentUrl.empty() ? "https://" : currentUrl;
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, "Sync Server URL", prefillUrl, 10,
|
||||
renderer, mappedInput, tr(STR_SYNC_SERVER_URL), prefillUrl, 10,
|
||||
128, // maxLength - URLs can be long
|
||||
false, // not password
|
||||
[this](const std::string& url) {
|
||||
@@ -137,7 +139,7 @@ void KOReaderSettingsActivity::render(Activity::RenderLock&&) {
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
|
||||
// Draw header
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "KOReader Sync", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_KOREADER_SYNC), true, EpdFontFamily::BOLD);
|
||||
|
||||
// Draw selection highlight
|
||||
renderer.fillRect(0, 60 + selectedIndex * 30 - 2, pageWidth - 1, 30);
|
||||
@@ -147,28 +149,31 @@ void KOReaderSettingsActivity::render(Activity::RenderLock&&) {
|
||||
const int settingY = 60 + i * 30;
|
||||
const bool isSelected = (i == selectedIndex);
|
||||
|
||||
renderer.drawText(UI_10_FONT_ID, 20, settingY, menuNames[i], !isSelected);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, settingY, I18N.get(menuNames[i]), !isSelected);
|
||||
|
||||
// Draw status for each item
|
||||
const char* status = "";
|
||||
std::string status = "";
|
||||
if (i == 0) {
|
||||
status = KOREADER_STORE.getUsername().empty() ? "[Not Set]" : "[Set]";
|
||||
status = std::string("[") + (KOREADER_STORE.getUsername().empty() ? tr(STR_NOT_SET) : tr(STR_SET)) + "]";
|
||||
} else if (i == 1) {
|
||||
status = KOREADER_STORE.getPassword().empty() ? "[Not Set]" : "[Set]";
|
||||
status = std::string("[") + (KOREADER_STORE.getPassword().empty() ? tr(STR_NOT_SET) : tr(STR_SET)) + "]";
|
||||
} else if (i == 2) {
|
||||
status = KOREADER_STORE.getServerUrl().empty() ? "[Default]" : "[Custom]";
|
||||
status =
|
||||
std::string("[") + (KOREADER_STORE.getServerUrl().empty() ? tr(STR_DEFAULT_VALUE) : tr(STR_CUSTOM)) + "]";
|
||||
} else if (i == 3) {
|
||||
status = KOREADER_STORE.getMatchMethod() == DocumentMatchMethod::FILENAME ? "[Filename]" : "[Binary]";
|
||||
status = std::string("[") +
|
||||
(KOREADER_STORE.getMatchMethod() == DocumentMatchMethod::FILENAME ? tr(STR_FILENAME) : tr(STR_BINARY)) +
|
||||
"]";
|
||||
} else if (i == 4) {
|
||||
status = KOREADER_STORE.hasCredentials() ? "" : "[Set credentials first]";
|
||||
status = KOREADER_STORE.hasCredentials() ? "" : std::string("[") + tr(STR_SET_CREDENTIALS_FIRST) + "]";
|
||||
}
|
||||
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, status);
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, status, !isSelected);
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, status.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, status.c_str(), !isSelected);
|
||||
}
|
||||
|
||||
// Draw button hints
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
renderer.displayBuffer();
|
||||
|
||||
94
src/activities/settings/LanguageSelectActivity.cpp
Normal file
94
src/activities/settings/LanguageSelectActivity.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "LanguageSelectActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include "MappedInputManager.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
void LanguageSelectActivity::onEnter() {
|
||||
Activity::onEnter();
|
||||
|
||||
totalItems = getLanguageCount();
|
||||
|
||||
// Set current selection based on current language
|
||||
selectedIndex = static_cast<int>(I18N.getLanguage());
|
||||
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void LanguageSelectActivity::onExit() { Activity::onExit(); }
|
||||
|
||||
void LanguageSelectActivity::loop() {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onBack();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
handleSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||
selectedIndex = (selectedIndex + totalItems - 1) % totalItems;
|
||||
requestUpdate();
|
||||
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||
selectedIndex = (selectedIndex + 1) % totalItems;
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void LanguageSelectActivity::handleSelection() {
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
I18N.setLanguage(static_cast<Language>(selectedIndex));
|
||||
}
|
||||
|
||||
// Return to previous page
|
||||
onBack();
|
||||
}
|
||||
|
||||
void LanguageSelectActivity::render(Activity::RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
constexpr int rowHeight = 30;
|
||||
|
||||
// Title
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_LANGUAGE), true, EpdFontFamily::BOLD);
|
||||
|
||||
// Current language marker
|
||||
const int currentLang = static_cast<int>(I18N.getLanguage());
|
||||
|
||||
// Draw options
|
||||
for (int i = 0; i < totalItems; i++) {
|
||||
const int itemY = 60 + i * rowHeight;
|
||||
const bool isSelected = (i == selectedIndex);
|
||||
const bool isCurrent = (i == currentLang);
|
||||
|
||||
// Draw selection highlight
|
||||
if (isSelected) {
|
||||
renderer.fillRect(0, itemY - 2, pageWidth - 1, rowHeight);
|
||||
}
|
||||
|
||||
// Draw language name - get it from i18n system
|
||||
const char* langName = I18N.getLanguageName(static_cast<Language>(i));
|
||||
renderer.drawText(UI_10_FONT_ID, 20, itemY, langName, !isSelected);
|
||||
|
||||
// Draw current selection marker
|
||||
if (isCurrent) {
|
||||
const char* marker = tr(STR_ON_MARKER);
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, marker);
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, itemY, marker, !isSelected);
|
||||
}
|
||||
}
|
||||
|
||||
// Button hints
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
33
src/activities/settings/LanguageSelectActivity.h
Normal file
33
src/activities/settings/LanguageSelectActivity.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "../ActivityWithSubactivity.h"
|
||||
#include "components/UITheme.h"
|
||||
|
||||
class MappedInputManager;
|
||||
|
||||
/**
|
||||
* Activity for selecting UI language
|
||||
*/
|
||||
class LanguageSelectActivity final : public Activity {
|
||||
public:
|
||||
explicit LanguageSelectActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onBack)
|
||||
: Activity("LanguageSelect", renderer, mappedInput), onBack(onBack) {}
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
|
||||
private:
|
||||
void handleSelection();
|
||||
|
||||
std::function<void()> onBack;
|
||||
int selectedIndex = 0;
|
||||
int totalItems = 0;
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "OtaUpdateActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include "MappedInputManager.h"
|
||||
@@ -97,27 +98,27 @@ void OtaUpdateActivity::render(Activity::RenderLock&&) {
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
|
||||
renderer.clearScreen();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Update", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_UPDATE), true, EpdFontFamily::BOLD);
|
||||
|
||||
if (state == CHECKING_FOR_UPDATE) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, "Checking for update...", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, tr(STR_CHECKING_UPDATE), true, EpdFontFamily::BOLD);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == WAITING_CONFIRMATION) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 200, "New update available!", true, EpdFontFamily::BOLD);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 250, "Current Version: " CROSSPOINT_VERSION);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 270, ("New Version: " + updater.getLatestVersion()).c_str());
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 200, tr(STR_NEW_UPDATE), true, EpdFontFamily::BOLD);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 250, (std::string(tr(STR_CURRENT_VERSION)) + CROSSPOINT_VERSION).c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 270, (std::string(tr(STR_NEW_VERSION)) + updater.getLatestVersion()).c_str());
|
||||
|
||||
const auto labels = mappedInput.mapLabels("Cancel", "Update", "", "");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_CANCEL), tr(STR_UPDATE), "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == UPDATE_IN_PROGRESS) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 310, "Updating...", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 310, tr(STR_UPDATING), true, EpdFontFamily::BOLD);
|
||||
renderer.drawRect(20, 350, pageWidth - 40, 50);
|
||||
renderer.fillRect(24, 354, static_cast<int>(updaterProgress * static_cast<float>(pageWidth - 44)), 42);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 420,
|
||||
@@ -130,20 +131,20 @@ void OtaUpdateActivity::render(Activity::RenderLock&&) {
|
||||
}
|
||||
|
||||
if (state == NO_UPDATE) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, "No update available", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, tr(STR_NO_UPDATE), true, EpdFontFamily::BOLD);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == FAILED) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, "Update failed", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, tr(STR_UPDATE_FAILED), true, EpdFontFamily::BOLD);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == FINISHED) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, "Update complete", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 350, "Press and hold power button to turn back on");
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, tr(STR_UPDATE_COMPLETE), true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 350, tr(STR_POWER_ON_HINT));
|
||||
renderer.displayBuffer();
|
||||
state = SHUTTING_DOWN;
|
||||
return;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "ClearCacheActivity.h"
|
||||
#include "CrossPointSettings.h"
|
||||
#include "KOReaderSettingsActivity.h"
|
||||
#include "LanguageSelectActivity.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "OtaUpdateActivity.h"
|
||||
#include "SettingsList.h"
|
||||
@@ -15,7 +16,8 @@
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"};
|
||||
const StrId SettingsActivity::categoryNames[categoryCount] = {StrId::STR_CAT_DISPLAY, StrId::STR_CAT_READER,
|
||||
StrId::STR_CAT_CONTROLS, StrId::STR_CAT_SYSTEM};
|
||||
|
||||
void SettingsActivity::onEnter() {
|
||||
Activity::onEnter();
|
||||
@@ -27,14 +29,14 @@ void SettingsActivity::onEnter() {
|
||||
systemSettings.clear();
|
||||
|
||||
for (auto& setting : getSettingsList()) {
|
||||
if (!setting.category) continue;
|
||||
if (strcmp(setting.category, "Display") == 0) {
|
||||
if (setting.category == StrId::STR_NONE_OPT) continue;
|
||||
if (setting.category == StrId::STR_CAT_DISPLAY) {
|
||||
displaySettings.push_back(std::move(setting));
|
||||
} else if (strcmp(setting.category, "Reader") == 0) {
|
||||
} else if (setting.category == StrId::STR_CAT_READER) {
|
||||
readerSettings.push_back(std::move(setting));
|
||||
} else if (strcmp(setting.category, "Controls") == 0) {
|
||||
} else if (setting.category == StrId::STR_CAT_CONTROLS) {
|
||||
controlsSettings.push_back(std::move(setting));
|
||||
} else if (strcmp(setting.category, "System") == 0) {
|
||||
} else if (setting.category == StrId::STR_CAT_SYSTEM) {
|
||||
systemSettings.push_back(std::move(setting));
|
||||
}
|
||||
// Web-only categories (KOReader Sync, OPDS Browser) are skipped for device UI
|
||||
@@ -42,12 +44,13 @@ void SettingsActivity::onEnter() {
|
||||
|
||||
// Append device-only ACTION items
|
||||
controlsSettings.insert(controlsSettings.begin(),
|
||||
SettingInfo::Action("Remap Front Buttons", SettingAction::RemapFrontButtons));
|
||||
systemSettings.push_back(SettingInfo::Action("Network", SettingAction::Network));
|
||||
systemSettings.push_back(SettingInfo::Action("KOReader Sync", SettingAction::KOReaderSync));
|
||||
systemSettings.push_back(SettingInfo::Action("OPDS Browser", SettingAction::OPDSBrowser));
|
||||
systemSettings.push_back(SettingInfo::Action("Clear Cache", SettingAction::ClearCache));
|
||||
systemSettings.push_back(SettingInfo::Action("Check for updates", SettingAction::CheckForUpdates));
|
||||
SettingInfo::Action(StrId::STR_REMAP_FRONT_BUTTONS, SettingAction::RemapFrontButtons));
|
||||
systemSettings.push_back(SettingInfo::Action(StrId::STR_WIFI_NETWORKS, SettingAction::Network));
|
||||
systemSettings.push_back(SettingInfo::Action(StrId::STR_KOREADER_SYNC, SettingAction::KOReaderSync));
|
||||
systemSettings.push_back(SettingInfo::Action(StrId::STR_OPDS_BROWSER, SettingAction::OPDSBrowser));
|
||||
systemSettings.push_back(SettingInfo::Action(StrId::STR_CLEAR_READING_CACHE, SettingAction::ClearCache));
|
||||
systemSettings.push_back(SettingInfo::Action(StrId::STR_CHECK_UPDATES, SettingAction::CheckForUpdates));
|
||||
systemSettings.push_back(SettingInfo::Action(StrId::STR_LANGUAGE, SettingAction::Language));
|
||||
|
||||
// Reset selection to first category
|
||||
selectedCategoryIndex = 0;
|
||||
@@ -196,6 +199,9 @@ void SettingsActivity::toggleCurrentSetting() {
|
||||
case SettingAction::CheckForUpdates:
|
||||
enterSubActivity(new OtaUpdateActivity(renderer, mappedInput, onComplete));
|
||||
break;
|
||||
case SettingAction::Language:
|
||||
enterSubActivity(new LanguageSelectActivity(renderer, mappedInput, onComplete));
|
||||
break;
|
||||
case SettingAction::None:
|
||||
// Do nothing
|
||||
break;
|
||||
@@ -215,12 +221,12 @@ void SettingsActivity::render(Activity::RenderLock&&) {
|
||||
|
||||
auto metrics = UITheme::getInstance().getMetrics();
|
||||
|
||||
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, "Settings");
|
||||
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_SETTINGS_TITLE));
|
||||
|
||||
std::vector<TabInfo> tabs;
|
||||
tabs.reserve(categoryCount);
|
||||
for (int i = 0; i < categoryCount; i++) {
|
||||
tabs.push_back({categoryNames[i], selectedCategoryIndex == i});
|
||||
tabs.push_back({I18N.get(categoryNames[i]), selectedCategoryIndex == i});
|
||||
}
|
||||
GUI.drawTabBar(renderer, Rect{0, metrics.topPadding + metrics.headerHeight, pageWidth, metrics.tabBarHeight}, tabs,
|
||||
selectedSettingIndex == 0);
|
||||
@@ -231,23 +237,24 @@ void SettingsActivity::render(Activity::RenderLock&&) {
|
||||
Rect{0, metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight + metrics.verticalSpacing, pageWidth,
|
||||
pageHeight - (metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight + metrics.buttonHintsHeight +
|
||||
metrics.verticalSpacing * 2)},
|
||||
settingsCount, selectedSettingIndex - 1, [&settings](int index) { return std::string(settings[index].name); },
|
||||
nullptr, nullptr,
|
||||
settingsCount, selectedSettingIndex - 1,
|
||||
[&settings](int index) { return std::string(I18N.get(settings[index].nameId)); }, nullptr, nullptr,
|
||||
[&settings](int i) {
|
||||
const auto& setting = settings[i];
|
||||
std::string valueText = "";
|
||||
if (settings[i].type == SettingType::TOGGLE && settings[i].valuePtr != nullptr) {
|
||||
const bool value = SETTINGS.*(settings[i].valuePtr);
|
||||
valueText = value ? "ON" : "OFF";
|
||||
} else if (settings[i].type == SettingType::ENUM && settings[i].valuePtr != nullptr) {
|
||||
const uint8_t value = SETTINGS.*(settings[i].valuePtr);
|
||||
valueText = settings[i].enumValues[value];
|
||||
} else if (settings[i].type == SettingType::ENUM && settings[i].valueGetter) {
|
||||
const uint8_t value = settings[i].valueGetter();
|
||||
if (value < settings[i].enumValues.size()) {
|
||||
valueText = settings[i].enumValues[value];
|
||||
if (setting.type == SettingType::TOGGLE && setting.valuePtr != nullptr) {
|
||||
const bool value = SETTINGS.*(setting.valuePtr);
|
||||
valueText = value ? tr(STR_STATE_ON) : tr(STR_STATE_OFF);
|
||||
} else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {
|
||||
const uint8_t value = SETTINGS.*(setting.valuePtr);
|
||||
valueText = I18N.get(setting.enumValues[value]);
|
||||
} else if (setting.type == SettingType::ENUM && setting.valueGetter) {
|
||||
const uint8_t value = setting.valueGetter();
|
||||
if (value < setting.enumValues.size()) {
|
||||
valueText = I18N.get(setting.enumValues[value]);
|
||||
}
|
||||
} else if (settings[i].type == SettingType::VALUE && settings[i].valuePtr != nullptr) {
|
||||
valueText = std::to_string(SETTINGS.*(settings[i].valuePtr));
|
||||
} else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) {
|
||||
valueText = std::to_string(SETTINGS.*(setting.valuePtr));
|
||||
}
|
||||
return valueText;
|
||||
});
|
||||
@@ -258,7 +265,7 @@ void SettingsActivity::render(Activity::RenderLock&&) {
|
||||
metrics.versionTextY, CROSSPOINT_VERSION);
|
||||
|
||||
// Draw help text
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Toggle", "Up", "Down");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_TOGGLE), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
// Always use standard refresh for settings screen
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include <I18n.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
@@ -19,13 +20,14 @@ enum class SettingAction {
|
||||
Network,
|
||||
ClearCache,
|
||||
CheckForUpdates,
|
||||
Language,
|
||||
};
|
||||
|
||||
struct SettingInfo {
|
||||
const char* name;
|
||||
StrId nameId;
|
||||
SettingType type;
|
||||
uint8_t CrossPointSettings::* valuePtr = nullptr;
|
||||
std::vector<std::string> enumValues;
|
||||
std::vector<StrId> enumValues;
|
||||
SettingAction action = SettingAction::None;
|
||||
|
||||
struct ValueRange {
|
||||
@@ -35,8 +37,8 @@ struct SettingInfo {
|
||||
};
|
||||
ValueRange valueRange = {};
|
||||
|
||||
const char* key = nullptr; // JSON API key (nullptr for ACTION types)
|
||||
const char* category = nullptr; // Category for web UI grouping
|
||||
const char* key = nullptr; // JSON API key (nullptr for ACTION types)
|
||||
StrId category = StrId::STR_NONE_OPT; // Category for web UI grouping
|
||||
|
||||
// Direct char[] string fields (for settings stored in CrossPointSettings)
|
||||
char* stringPtr = nullptr;
|
||||
@@ -48,10 +50,10 @@ struct SettingInfo {
|
||||
std::function<std::string()> stringGetter;
|
||||
std::function<void(const std::string&)> stringSetter;
|
||||
|
||||
static SettingInfo Toggle(const char* name, uint8_t CrossPointSettings::* ptr, const char* key = nullptr,
|
||||
const char* category = nullptr) {
|
||||
static SettingInfo Toggle(StrId nameId, uint8_t CrossPointSettings::* ptr, const char* key = nullptr,
|
||||
StrId category = StrId::STR_NONE_OPT) {
|
||||
SettingInfo s;
|
||||
s.name = name;
|
||||
s.nameId = nameId;
|
||||
s.type = SettingType::TOGGLE;
|
||||
s.valuePtr = ptr;
|
||||
s.key = key;
|
||||
@@ -59,10 +61,10 @@ struct SettingInfo {
|
||||
return s;
|
||||
}
|
||||
|
||||
static SettingInfo Enum(const char* name, uint8_t CrossPointSettings::* ptr, std::vector<std::string> values,
|
||||
const char* key = nullptr, const char* category = nullptr) {
|
||||
static SettingInfo Enum(StrId nameId, uint8_t CrossPointSettings::* ptr, std::vector<StrId> values,
|
||||
const char* key = nullptr, StrId category = StrId::STR_NONE_OPT) {
|
||||
SettingInfo s;
|
||||
s.name = name;
|
||||
s.nameId = nameId;
|
||||
s.type = SettingType::ENUM;
|
||||
s.valuePtr = ptr;
|
||||
s.enumValues = std::move(values);
|
||||
@@ -71,18 +73,18 @@ struct SettingInfo {
|
||||
return s;
|
||||
}
|
||||
|
||||
static SettingInfo Action(const char* name, SettingAction action) {
|
||||
static SettingInfo Action(StrId nameId, SettingAction action) {
|
||||
SettingInfo s;
|
||||
s.name = name;
|
||||
s.nameId = nameId;
|
||||
s.type = SettingType::ACTION;
|
||||
s.action = action;
|
||||
return s;
|
||||
}
|
||||
|
||||
static SettingInfo Value(const char* name, uint8_t CrossPointSettings::* ptr, const ValueRange valueRange,
|
||||
const char* key = nullptr, const char* category = nullptr) {
|
||||
static SettingInfo Value(StrId nameId, uint8_t CrossPointSettings::* ptr, const ValueRange valueRange,
|
||||
const char* key = nullptr, StrId category = StrId::STR_NONE_OPT) {
|
||||
SettingInfo s;
|
||||
s.name = name;
|
||||
s.nameId = nameId;
|
||||
s.type = SettingType::VALUE;
|
||||
s.valuePtr = ptr;
|
||||
s.valueRange = valueRange;
|
||||
@@ -91,10 +93,10 @@ struct SettingInfo {
|
||||
return s;
|
||||
}
|
||||
|
||||
static SettingInfo String(const char* name, char* ptr, size_t maxLen, const char* key = nullptr,
|
||||
const char* category = nullptr) {
|
||||
static SettingInfo String(StrId nameId, char* ptr, size_t maxLen, const char* key = nullptr,
|
||||
StrId category = StrId::STR_NONE_OPT) {
|
||||
SettingInfo s;
|
||||
s.name = name;
|
||||
s.nameId = nameId;
|
||||
s.type = SettingType::STRING;
|
||||
s.stringPtr = ptr;
|
||||
s.stringMaxLen = maxLen;
|
||||
@@ -103,11 +105,11 @@ struct SettingInfo {
|
||||
return s;
|
||||
}
|
||||
|
||||
static SettingInfo DynamicEnum(const char* name, std::vector<std::string> values, std::function<uint8_t()> getter,
|
||||
static SettingInfo DynamicEnum(StrId nameId, std::vector<StrId> values, std::function<uint8_t()> getter,
|
||||
std::function<void(uint8_t)> setter, const char* key = nullptr,
|
||||
const char* category = nullptr) {
|
||||
StrId category = StrId::STR_NONE_OPT) {
|
||||
SettingInfo s;
|
||||
s.name = name;
|
||||
s.nameId = nameId;
|
||||
s.type = SettingType::ENUM;
|
||||
s.enumValues = std::move(values);
|
||||
s.valueGetter = std::move(getter);
|
||||
@@ -117,11 +119,11 @@ struct SettingInfo {
|
||||
return s;
|
||||
}
|
||||
|
||||
static SettingInfo DynamicString(const char* name, std::function<std::string()> getter,
|
||||
static SettingInfo DynamicString(StrId nameId, std::function<std::string()> getter,
|
||||
std::function<void(const std::string&)> setter, const char* key = nullptr,
|
||||
const char* category = nullptr) {
|
||||
StrId category = StrId::STR_NONE_OPT) {
|
||||
SettingInfo s;
|
||||
s.name = name;
|
||||
s.nameId = nameId;
|
||||
s.type = SettingType::STRING;
|
||||
s.stringGetter = std::move(getter);
|
||||
s.stringSetter = std::move(setter);
|
||||
@@ -148,7 +150,7 @@ class SettingsActivity final : public ActivityWithSubactivity {
|
||||
const std::function<void()> onGoHome;
|
||||
|
||||
static constexpr int categoryCount = 4;
|
||||
static const char* categoryNames[categoryCount];
|
||||
static const StrId categoryNames[categoryCount];
|
||||
|
||||
void enterCategory(int categoryIndex);
|
||||
void toggleCurrentSetting();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "KeyboardEntryActivity.h"
|
||||
|
||||
#include <I18n.h>
|
||||
|
||||
#include "MappedInputManager.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
@@ -259,7 +261,8 @@ void KeyboardEntryActivity::render(Activity::RenderLock&&) {
|
||||
|
||||
// SHIFT key (logical col 0, spans 2 key widths)
|
||||
const bool shiftSelected = (selectedRow == 4 && selectedCol >= SHIFT_COL && selectedCol < SPACE_COL);
|
||||
renderItemWithSelector(currentX + 2, rowY, shiftString[shiftState], shiftSelected);
|
||||
static constexpr StrId shiftIds[3] = {StrId::STR_KBD_SHIFT, StrId::STR_KBD_SHIFT_CAPS, StrId::STR_KBD_LOCK};
|
||||
renderItemWithSelector(currentX + 2, rowY, I18N.get(shiftIds[shiftState]), shiftSelected);
|
||||
currentX += 2 * (keyWidth + keySpacing);
|
||||
|
||||
// Space bar (logical cols 2-6, spans 5 key widths)
|
||||
@@ -277,7 +280,7 @@ void KeyboardEntryActivity::render(Activity::RenderLock&&) {
|
||||
|
||||
// OK button (logical col 9, spans 2 key widths)
|
||||
const bool okSelected = (selectedRow == 4 && selectedCol >= DONE_COL);
|
||||
renderItemWithSelector(currentX + 2, rowY, "OK", okSelected);
|
||||
renderItemWithSelector(currentX + 2, rowY, tr(STR_OK_BUTTON), okSelected);
|
||||
} else {
|
||||
// Regular rows: render each key individually
|
||||
for (int col = 0; col < getRowLength(row); col++) {
|
||||
@@ -294,11 +297,11 @@ void KeyboardEntryActivity::render(Activity::RenderLock&&) {
|
||||
}
|
||||
|
||||
// Draw help text
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "Left", "Right");
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_LEFT), tr(STR_DIR_RIGHT));
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
// Draw side button hints for Up/Down navigation
|
||||
GUI.drawSideButtonHints(renderer, "Up", "Down");
|
||||
GUI.drawSideButtonHints(renderer, tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
||||
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "Battery.h"
|
||||
#include "I18n.h"
|
||||
#include "RecentBooksStore.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
@@ -554,7 +555,7 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std:
|
||||
const int continueY = bookY + bookHeight - renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2;
|
||||
if (coverRendered) {
|
||||
// Draw box behind "Continue Reading" text (inverted when selected: black box instead of white)
|
||||
const char* continueText = "Continue Reading";
|
||||
const char* continueText = tr(STR_CONTINUE_READING);
|
||||
const int continueTextWidth = renderer.getTextWidth(UI_10_FONT_ID, continueText);
|
||||
constexpr int continuePadding = 6;
|
||||
const int continueBoxWidth = continueTextWidth + continuePadding * 2;
|
||||
@@ -565,7 +566,7 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std:
|
||||
renderer.drawRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, !bookSelected);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, continueY, continueText, !bookSelected);
|
||||
} else {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, continueY, "Continue Reading", !bookSelected);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, continueY, tr(STR_CONTINUE_READING), !bookSelected);
|
||||
}
|
||||
} else {
|
||||
// No book to continue reading
|
||||
@@ -593,7 +594,8 @@ void BaseTheme::drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount
|
||||
rect.width - BaseMetrics::values.contentSidePadding * 2, BaseMetrics::values.menuRowHeight);
|
||||
}
|
||||
|
||||
const char* label = buttonLabel(i).c_str();
|
||||
std::string labelStr = buttonLabel(i);
|
||||
const char* label = labelStr.c_str();
|
||||
const int textWidth = renderer.getTextWidth(UI_10_FONT_ID, label);
|
||||
const int textX = rect.x + (rect.width - textWidth) / 2;
|
||||
const int lineHeight = renderer.getLineHeight(UI_10_FONT_ID);
|
||||
|
||||
@@ -343,7 +343,8 @@ void LyraTheme::drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount
|
||||
renderer.fillRoundedRect(tileRect.x, tileRect.y, tileRect.width, tileRect.height, cornerRadius, Color::LightGray);
|
||||
}
|
||||
|
||||
const char* label = buttonLabel(i).c_str();
|
||||
std::string labelStr = buttonLabel(i);
|
||||
const char* label = labelStr.c_str();
|
||||
const int textX = tileRect.x + 16;
|
||||
const int lineHeight = renderer.getLineHeight(UI_12_FONT_ID);
|
||||
const int textY = tileRect.y + (LyraMetrics::values.menuRowHeight - lineHeight) / 2;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <HalGPIO.h>
|
||||
#include <HalPowerManager.h>
|
||||
#include <HalStorage.h>
|
||||
#include <I18n.h>
|
||||
#include <Logging.h>
|
||||
#include <SPI.h>
|
||||
#include <builtinFonts/all.h>
|
||||
@@ -323,6 +324,7 @@ void setup() {
|
||||
}
|
||||
|
||||
SETTINGS.loadFromFile();
|
||||
I18N.loadSettings();
|
||||
KOREADER_STORE.loadFromFile();
|
||||
UITheme::getInstance().reload();
|
||||
ButtonNavigator::setMappedInputManager(mappedInputManager);
|
||||
|
||||
@@ -1017,8 +1017,8 @@ void CrossPointWebServer::handleGetSettings() const {
|
||||
|
||||
doc.clear();
|
||||
doc["key"] = s.key;
|
||||
doc["name"] = s.name;
|
||||
doc["category"] = s.category;
|
||||
doc["name"] = I18N.get(s.nameId);
|
||||
doc["category"] = I18N.get(s.category);
|
||||
|
||||
switch (s.type) {
|
||||
case SettingType::TOGGLE: {
|
||||
@@ -1037,7 +1037,7 @@ void CrossPointWebServer::handleGetSettings() const {
|
||||
}
|
||||
JsonArray options = doc["options"].to<JsonArray>();
|
||||
for (const auto& opt : s.enumValues) {
|
||||
options.add(opt);
|
||||
options.add(I18N.get(opt));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user