feat(extensions): polish app install UX

This commit is contained in:
Daniel 2026-02-03 00:01:59 -08:00
parent e55fe71145
commit 4ffbc2a641
6 changed files with 56 additions and 37 deletions

View File

@ -86,9 +86,12 @@ void AppsActivity::launchApp() {
CrossPoint::AppLoader loader; CrossPoint::AppLoader loader;
bool success = loader.flashApp(binPath, [this](size_t written, size_t total) { bool success = loader.flashApp(binPath, [this](size_t written, size_t total) {
flashProgress_ = static_cast<int>((written * 100) / total); const int nextProgress = (total > 0) ? static_cast<int>((written * 100) / total) : 0;
needsUpdate_ = true; if (nextProgress != flashProgress_) {
renderProgress(); flashProgress_ = nextProgress;
needsUpdate_ = true;
renderProgress();
}
}); });
if (!success) { if (!success) {
@ -134,7 +137,8 @@ void AppsActivity::render() {
int textWidth = renderer_.getTextWidth(UI_12_FONT_ID, buf); int textWidth = renderer_.getTextWidth(UI_12_FONT_ID, buf);
int x = (pageWidth - textWidth) / 2 - 10; int x = (pageWidth - textWidth) / 2 - 10;
renderer_.fillRect(x, y - 5, textWidth + 20, lineHeight - 5); renderer_.fillRect(x, y - 5, textWidth + 20, lineHeight - 5);
renderer_.drawText(UI_12_FONT_ID, x + 10, y, buf, true); // inverted // Draw white text on black highlight.
renderer_.drawText(UI_12_FONT_ID, x + 10, y, buf, false);
} else { } else {
renderer_.drawCenteredText(UI_10_FONT_ID, y, buf); renderer_.drawCenteredText(UI_10_FONT_ID, y, buf);
} }

View File

@ -506,7 +506,8 @@ void HomeActivity::render() {
// --- Bottom menu tiles --- // --- Bottom menu tiles ---
// Build menu items dynamically // Build menu items dynamically
std::vector<const char*> menuItems = {"My Library", "File Transfer", "Settings"}; // Keep this list in sync with getMenuItemCount() and loop() index mapping.
std::vector<const char*> menuItems = {"My Library", "File Transfer", "Apps", "Settings"};
if (hasOpdsUrl) { if (hasOpdsUrl) {
// Insert OPDS Browser after My Library // Insert OPDS Browser after My Library
menuItems.insert(menuItems.begin() + 1, "OPDS Browser"); menuItems.insert(menuItems.begin() + 1, "OPDS Browser");

View File

@ -19,7 +19,7 @@ EpdFont ui12BoldFont(&ubuntu_12_bold);
EpdFontFamily ui12FontFamily(&ui12RegularFont, &ui12BoldFont); EpdFontFamily ui12FontFamily(&ui12RegularFont, &ui12BoldFont);
} }
HelloWorldActivity::HelloWorldActivity(EInkDisplay& display, InputManager& input) HelloWorldActivity::HelloWorldActivity(HalDisplay& display, HalGPIO& input)
: display_(display), input_(input), needsUpdate_(true) {} : display_(display), input_(input), needsUpdate_(true) {}
void HelloWorldActivity::onEnter() { void HelloWorldActivity::onEnter() {
@ -35,7 +35,7 @@ void HelloWorldActivity::onEnter() {
} }
void HelloWorldActivity::loop() { void HelloWorldActivity::loop() {
if (input_.wasPressed(InputManager::BTN_BACK)) { if (input_.wasPressed(HalGPIO::BTN_BACK)) {
returnToLauncher(); returnToLauncher();
return; return;
} }

View File

@ -1,19 +1,19 @@
#pragma once #pragma once
#include <EInkDisplay.h> #include <HalDisplay.h>
#include <InputManager.h> #include <HalGPIO.h>
class HelloWorldActivity { class HelloWorldActivity {
public: public:
HelloWorldActivity(EInkDisplay& display, InputManager& input); HelloWorldActivity(HalDisplay& display, HalGPIO& input);
void onEnter(); void onEnter();
void loop(); void loop();
void onExit(); void onExit();
private: private:
EInkDisplay& display_; HalDisplay& display_;
InputManager& input_; HalGPIO& input_;
bool needsUpdate_; bool needsUpdate_;
void render(); void render();

View File

@ -1,32 +1,31 @@
#include <Arduino.h> #include <Arduino.h>
#include <EInkDisplay.h> #include <HalDisplay.h>
#include <InputManager.h> #include <HalGPIO.h>
#include "HelloWorldActivity.h" #include "HelloWorldActivity.h"
// Display SPI pins for Xteink X4 HalDisplay display;
#define EPD_SCLK 8 HalGPIO gpio;
#define EPD_MOSI 10 HelloWorldActivity activity(display, gpio);
#define EPD_CS 21
#define EPD_DC 4
#define EPD_RST 5
#define EPD_BUSY 6
EInkDisplay display(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY);
InputManager input;
HelloWorldActivity activity(display, input);
void setup() { void setup() {
Serial.begin(115200); gpio.begin();
Serial.println("[HelloWorld] Starting...");
// Only start serial if USB connected
if (gpio.isUsbConnected()) {
Serial.begin(115200);
Serial.println("[HelloWorld] Starting...");
}
input.begin();
activity.onEnter(); activity.onEnter();
Serial.println("[HelloWorld] Activity started"); if (Serial) {
Serial.println("[HelloWorld] Activity started");
}
} }
void loop() { void loop() {
input.update(); gpio.update();
activity.loop(); activity.loop();
delay(10); delay(10);
} }

View File

@ -102,8 +102,15 @@ AppManifest AppLoader::parseManifest(const String& path) {
return manifest; return manifest;
} }
// Handle UTF-8 BOM if the manifest was created by an editor that writes it.
const char* json = buffer.get();
if (bytesRead >= 3 && static_cast<uint8_t>(json[0]) == 0xEF && static_cast<uint8_t>(json[1]) == 0xBB &&
static_cast<uint8_t>(json[2]) == 0xBF) {
json += 3;
}
JsonDocument doc; JsonDocument doc;
const DeserializationError error = deserializeJson(doc, buffer.get()); const DeserializationError error = deserializeJson(doc, json);
if (error) { if (error) {
Serial.printf("[%lu] [AppLoader] JSON parse error in %s: %s\n", Serial.printf("[%lu] [AppLoader] JSON parse error in %s: %s\n",
@ -230,12 +237,16 @@ bool AppLoader::flashApp(const String& binPath, ProgressCallback callback) {
} }
if (callback) { if (callback) {
callback(0, 100); callback(0, fileSize);
} }
size_t totalWritten = 0; size_t totalWritten = 0;
static constexpr size_t flashChunkSize = 1024; // Larger chunks reduce SD/OTA overhead significantly.
uint8_t buffer[flashChunkSize]; // 32KB is a good balance on ESP32-C3: faster writes without blowing RAM.
static constexpr size_t flashChunkSize = 32 * 1024;
static uint8_t buffer[flashChunkSize];
size_t lastNotifiedPercent = 0;
while (totalWritten < fileSize) { while (totalWritten < fileSize) {
const size_t remaining = fileSize - totalWritten; const size_t remaining = fileSize - totalWritten;
@ -260,7 +271,11 @@ bool AppLoader::flashApp(const String& binPath, ProgressCallback callback) {
if (callback) { if (callback) {
const size_t percent = (totalWritten * 100) / fileSize; const size_t percent = (totalWritten * 100) / fileSize;
callback(percent, 100); // Throttle UI updates; each screen refresh is ~400ms.
if (percent >= lastNotifiedPercent + 10 || percent == 100) {
lastNotifiedPercent = percent;
callback(totalWritten, fileSize);
}
} }
} }