crosspoint-reader/src/ScreenComponents.cpp
Justin Mitchell b792b792bf
Calibre Web Epub Downloading + Calibre Wireless Device Syncing (#219)
## Summary

Adds support for browsing and downloading books from a Calibre-web
server via OPDS.
How it works
1. Configure server URL in Settings → Calibre Web URL (e.g.,
https://myserver.com:port I use Cloudflare tunnel to make my server
accessible anywhere fwiw)
2. "Calibre Library" will now show on the the home screen
3. Browse the catalog - navigate through categories like "By Newest",
"By Author", "By Series", etc.
4. Download books - select a book and press Confirm to download the EPUB
to your device
Navigation
- Up/Down - Move through entries
- Confirm - Open folder or download book
- Back - Go to parent catalog, or exit to home if at root
- Navigation entries show with > prefix, books show title and author
- Button hints update dynamically ("Open" for folders, "Download" for
books)
Technical details
- Fetches OPDS catalog from {server_url}/opds
- Parses both navigation feeds (catalog links) and acquisition feeds
(downloadable books)
- Maintains navigation history stack for back navigation
- Handles absolute paths in OPDS links correctly (e.g.,
/books/opds/navcatalog/...)
- Downloads EPUBs directly to the SD card root
Note
The server URL should be typed to include https:// if the server
requires it - HTTP→HTTPS redirects may cause SSL errors on ESP32.

## Additional Context

* I also changed the home titles to use uppercase for each word and
added a setting to change the size of the side margins

---------

Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-07 19:58:37 +11:00

66 lines
2.3 KiB
C++

#include "ScreenComponents.h"
#include <GfxRenderer.h>
#include <cstdint>
#include <string>
#include "Battery.h"
#include "fontIds.h"
void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, const int top) {
// Left aligned battery icon and percentage
const uint16_t percentage = battery.readPercentage();
const auto percentageText = std::to_string(percentage) + "%";
renderer.drawText(SMALL_FONT_ID, left + 20, top, percentageText.c_str());
// 1 column on left, 2 columns on right, 5 columns of battery body
constexpr int batteryWidth = 15;
constexpr int batteryHeight = 12;
const int x = left;
const int y = top + 6;
// Top line
renderer.drawLine(x + 1, y, x + batteryWidth - 3, y);
// Bottom line
renderer.drawLine(x + 1, y + batteryHeight - 1, x + batteryWidth - 3, y + batteryHeight - 1);
// Left line
renderer.drawLine(x, y + 1, x, y + batteryHeight - 2);
// Battery end
renderer.drawLine(x + batteryWidth - 2, y + 1, x + batteryWidth - 2, y + batteryHeight - 2);
renderer.drawPixel(x + batteryWidth - 1, y + 3);
renderer.drawPixel(x + batteryWidth - 1, y + batteryHeight - 4);
renderer.drawLine(x + batteryWidth - 0, y + 4, x + batteryWidth - 0, y + batteryHeight - 5);
// The +1 is to round up, so that we always fill at least one pixel
int filledWidth = percentage * (batteryWidth - 5) / 100 + 1;
if (filledWidth > batteryWidth - 5) {
filledWidth = batteryWidth - 5; // Ensure we don't overflow
}
renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4);
}
void ScreenComponents::drawProgressBar(const GfxRenderer& renderer, const int x, const int y, const int width,
const int height, const size_t current, const size_t total) {
if (total == 0) {
return;
}
// Use 64-bit arithmetic to avoid overflow for large files
const int percent = static_cast<int>((static_cast<uint64_t>(current) * 100) / total);
// Draw outline
renderer.drawRect(x, y, width, height);
// Draw filled portion
const int fillWidth = (width - 4) * percent / 100;
if (fillWidth > 0) {
renderer.fillRect(x + 2, y + 2, fillWidth, height - 4);
}
// Draw percentage text centered below bar
const std::string percentText = std::to_string(percent) + "%";
renderer.drawCenteredText(UI_10_FONT_ID, y + height + 15, percentText.c_str());
}