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;
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;
if (nextProgress != flashProgress_) {
flashProgress_ = nextProgress;
needsUpdate_ = true;
renderProgress();
}
});
if (!success) {
@ -134,7 +137,8 @@ void AppsActivity::render() {
int textWidth = renderer_.getTextWidth(UI_12_FONT_ID, buf);
int x = (pageWidth - textWidth) / 2 - 10;
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 {
renderer_.drawCenteredText(UI_10_FONT_ID, y, buf);
}

View File

@ -506,7 +506,8 @@ void HomeActivity::render() {
// --- Bottom menu tiles ---
// 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) {
// Insert OPDS Browser after My Library
menuItems.insert(menuItems.begin() + 1, "OPDS Browser");

View File

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

View File

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

View File

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

View File

@ -102,8 +102,15 @@ AppManifest AppLoader::parseManifest(const String& path) {
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;
const DeserializationError error = deserializeJson(doc, buffer.get());
const DeserializationError error = deserializeJson(doc, json);
if (error) {
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) {
callback(0, 100);
callback(0, fileSize);
}
size_t totalWritten = 0;
static constexpr size_t flashChunkSize = 1024;
uint8_t buffer[flashChunkSize];
// Larger chunks reduce SD/OTA overhead significantly.
// 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) {
const size_t remaining = fileSize - totalWritten;
@ -260,7 +271,11 @@ bool AppLoader::flashApp(const String& binPath, ProgressCallback callback) {
if (callback) {
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);
}
}
}