Merge main (file sharing, v0.18.0) into the VPN branch
CI / build (pull_request) Successful in 1m50s
Site / build (pull_request) Successful in 10s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
2026-10-08 00:40:12 +02:00
co-authored by Claude Opus 5.5
21 changed files with 1126 additions and 5 deletions
+56
View File
@@ -48,6 +48,7 @@ void StorageApp::onEnter() {
}
void StorageApp::onExit() {
share_.stop(); // sharing lasts as long as its screen
if (wait_ != Wait::None && wait_ != Wait::Work) {
ops_.cancel();
wait_ = Wait::None;
@@ -97,6 +98,13 @@ bool StorageApp::work(const std::string& why, const std::string& selectAfter) {
void StorageApp::update(uint32_t nowMs) {
bool present = storage_.state().present;
if (view_ == View::Share) {
if (!present || !share_.running()) {
share_.stop();
view_ = View::Browse;
}
if (nowMs - lastDrawMs_ >= 500) requestRedraw(); // what the browser is doing
}
if (present != cardPresent_) { // taken out, put in, or erased: back to the top
cardPresent_ = present;
cwd_ = want_ = "/";
@@ -281,6 +289,7 @@ void StorageApp::help(std::vector<KeyHelp>& out) const {
case View::Editor: return noteEditor_.help(out);
case View::Viewer: return viewer_.help(out);
case View::Details: return keys::add(out, keys::kStorageDetails);
case View::Share: return keys::add(out, keys::kStorageShare);
default: break;
}
if (dialog_) return keys::add(out, keys::kDialog);
@@ -295,6 +304,7 @@ const char* StorageApp::helpTitle() const {
case View::Editor: return "Storage: the editor";
case View::Viewer: return "Storage: a file";
case View::Details: return "Storage: details";
case View::Share: return "Storage: sharing";
case View::Name: return "Storage: a name";
default: return nullptr;
}
@@ -303,6 +313,14 @@ const char* StorageApp::helpTitle() const {
bool StorageApp::onKey(const KeyEvent& e) {
requestRedraw();
if (view_ == View::NoMemory) return false;
if (view_ == View::Share) {
if (e.key == Key::Back) {
share_.stop();
view_ = View::Browse;
open(cwd_); // what was uploaded or deleted meanwhile
}
return true;
}
if (view_ == View::Maintenance) {
if (!maintenance_.onKey(e)) {
view_ = View::Browse;
@@ -413,6 +431,12 @@ bool StorageApp::onBrowseKey(const KeyEvent& e) {
}
uint32_t ch = e.key == Key::Char ? (e.ch >= 'A' && e.ch <= 'Z' ? e.ch + 32 : e.ch) : 0;
if (ch == 'm') askMaintenance();
if (ch == 'w') {
std::string why = share_.start();
if (why.empty()) view_ = View::Share;
else say(why);
return true;
}
if (!cardPresent_ || !loaded_) return true;
Item item = selected();
@@ -507,6 +531,7 @@ void StorageApp::draw(Canvas& c) {
c.drawString("in Gemini) and open Storage again.", 4, area.y + 22 + theme::kLineHeight);
return;
case View::Maintenance: maintenance_.draw(c); return;
case View::Share: drawShare(c); return;
case View::Viewer: viewer_.draw(c); return;
case View::Editor: noteEditor_.draw(c); return;
case View::Details: {
@@ -539,6 +564,37 @@ void StorageApp::draw(Canvas& c) {
}
}
// The address as a QR code and in letters, the code to type, and what the browser has done.
void StorageApp::drawShare(Canvas& c) {
const auto& area = theme::kContent;
std::string url = share_.url(), code = share_.code();
const int qr = 104;
c.fillRect(2, area.y + 3, qr + 6, qr + 6, 0xFFFF); // a QR code wants a quiet border
c.qrcode((url + "#" + code).c_str(), 5, area.y + 6, qr);
int x = qr + 14, y = area.y + 4;
c.setFont(&fonts::small);
c.setTextColor(theme::kMuted);
c.drawString("In a browser, on", x, y);
c.drawString("this network:", x, y + 10);
c.setTextColor(theme::kText);
c.drawString(url.size() > 7 ? url.substr(7).c_str() : url.c_str(), x, y + 24); // without http://
c.setTextColor(theme::kMuted);
c.drawString("Code", x, y + 40);
c.setFont(&fonts::bold);
c.setTextColor(theme::kAccent);
std::string grouped = code.size() == 6 ? code.substr(0, 3) + " " + code.substr(3) : code;
c.drawString(grouped.c_str(), x, y + 50);
c.setFont(&fonts::small);
c.setTextColor(theme::kMuted);
std::string moved = formatBytes(share_.bytesIn()) + " in, " + formatBytes(share_.bytesOut()) + " out";
c.drawString(share_.requests() ? moved.c_str() : "Nothing asked yet", x, y + 72);
c.drawString("Not encrypted.", x, y + 84);
c.drawString("` stops sharing", x, y + 96);
c.setTextColor(theme::kText);
std::string last = share_.last();
if (!last.empty()) c.drawString(last.c_str(), 4, area.y + area.h - 9);
}
void StorageApp::drawBrowse(Canvas& c) {
const auto& area = theme::kContent;
StorageState card = storage_.state();
+6 -3
View File
@@ -16,6 +16,7 @@
#include "services/clock_service.h"
#include "services/file_ops.h"
#include "services/storage_service.h"
#include "services/web_share.h"
#include "ui/theme.h"
namespace roro {
@@ -26,8 +27,8 @@ namespace roro {
// usage, Storage Clean-up and erasing the card, behind a warning (Q128).
class StorageApp : public App {
public:
StorageApp(FileOps& ops, StorageService& storage, ClockService& clock, UpdateService& update, PowerService& power, EventBus& bus)
: ops_(ops), storage_(storage), bus_(bus), maintenance_(storage, clock, bus), viewer_(storage, update), noteEditor_(storage, clock, power) {}
StorageApp(FileOps& ops, StorageService& storage, ClockService& clock, UpdateService& update, PowerService& power, EventBus& bus, WebShare& share)
: ops_(ops), storage_(storage), bus_(bus), share_(share), maintenance_(storage, clock, bus), viewer_(storage, update), noteEditor_(storage, clock, power) {}
void onEnter() override;
void onExit() override;
bool onKey(const KeyEvent& e) override;
@@ -40,7 +41,7 @@ class StorageApp : public App {
const char* helpTitle() const override;
private:
enum class View { Browse, Details, Name, Viewer, Editor, Maintenance, NoMemory };
enum class View { Browse, Details, Name, Viewer, Editor, Maintenance, NoMemory, Share };
enum class Ask { None, Delete, Replace, Maintenance };
enum class Wait { None, List, CountToDelete, CountForDetails, Work }; // what the running operation is for
static constexpr int kRows = 8;
@@ -66,10 +67,12 @@ class StorageApp : public App {
void say(const std::string& text);
void drawBrowse(Canvas& c);
void drawProgress(Canvas& c);
void drawShare(Canvas& c); // `w`: the card in a browser on the same network (issue #88)
FileOps& ops_;
StorageService& storage_;
EventBus& bus_;
WebShare& share_;
MaintenancePage maintenance_;
FileViewer viewer_;
NoteEditor noteEditor_; // `e` in the text viewer (Q146)