Files
roro9stack/src/apps/storage_app.h
T
twislaandClaude Opus 5.5 2c18762614
CI / build (pull_request) Successful in 1m56s
Site / build (pull_request) Successful in 10s
Storage: view pictures, PNG, JPEG, BMP and GIF (#45)
Enter on a picture shows it: shrunk to fit the screen, or at its own size
with Enter again and the arrows to move. Dithered to the screen's 256
colours; a colour the screen has exactly is left alone, so screenshots are
shown as they are.

The picture is decoded once, straight into the screen's buffer, and kept
there (App::retainsContent): no copy in memory. Decoding runs on the
storage task, so the keys keep working and a 12 megapixel photograph
appears as it comes instead of tripping the watchdog.

PNG, BMP and GIF are read by decoders of our own, host-tested against files
made by Pillow; the PNG one needs 32 KB where the display library's needed
44 KB in one block, which the device often doesn't have. JPEG uses the
library's TJpgDec.

Also corrects two sentences that still gave 16 KB as the editing limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-07 21:04:39 +02:00

103 lines
4.1 KiB
C++

#pragma once
#include <memory>
#include <string>
#include <vector>
#include "app.h"
#include "apps/file_viewer.h"
#include "apps/maintenance_page.h"
#include "apps/note_editor.h"
#include "dialog_model.h"
#include "event_bus.h"
#include "file_list.h"
#include "line_editor.h"
#include "list_model.h"
#include "services/clock_service.h"
#include "services/file_ops.h"
#include "services/storage_service.h"
#include "ui/theme.h"
namespace roro {
// The Storage App (docs/milestones/F1.md): the SD card's folders and files, one item at a time
// with a clipboard (Q131). Enter opens a folder, or a file in its viewer (Q134); Back goes up;
// c copy, x cut, v paste here, r rename, d delete, n new folder, i details, s sort. At the top of the card, the last row is Maintenance:
// 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) {}
void onEnter() override;
void onExit() override;
bool onKey(const KeyEvent& e) override;
bool textEntryActive() const override { return view_ == View::Name || view_ == View::Editor; }
void update(uint32_t nowMs) override;
void draw(Canvas& c) override;
bool retainsContent() const override { return view_ == View::Viewer && viewer_.retains(); }
void contentLost() override { viewer_.lost(); }
void help(std::vector<KeyHelp>& out) const override;
const char* helpTitle() const override;
private:
enum class View { Browse, Details, Name, Viewer, Editor, Maintenance, NoMemory };
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;
static constexpr size_t kStartFloor = 55 * 1024; // Q86, Q136
struct Item {
std::string path;
bool folder = false;
explicit operator bool() const { return !path.empty(); }
};
void open(const std::string& folder, const std::string& select = "");
void onFinished(const FileOps::Status& s);
bool onBrowseKey(const KeyEvent& e);
bool onNameKey(const KeyEvent& e);
void onAnswer(int button);
Item selected() const; // empty on the Maintenance row or in an empty folder
bool onMaintenanceRow() const { return cwd_ == "/" && rows_.selected() == static_cast<int>(list_.count()); }
void paste(bool replace);
void askDelete(const Item& item, uint32_t files, uint32_t bytes);
void showDetails(const Item& item);
bool work(const std::string& why, const std::string& selectAfter); // an operation was asked for: did it start?
void say(const std::string& text);
void drawBrowse(Canvas& c);
void drawProgress(Canvas& c);
FileOps& ops_;
StorageService& storage_;
EventBus& bus_;
MaintenancePage maintenance_;
FileViewer viewer_;
NoteEditor noteEditor_; // `e` in the text viewer (Q146)
View view_ = View::Browse;
std::string cwd_ = "/", want_ = "/"; // the folder on screen; the one being read
files::FileList list_;
files::FileSort sort_ = files::FileSort::Name;
ListModel rows_{kRows};
bool wantList_ = false, loaded_ = false, cardPresent_ = false;
std::string selectAfter_; // after the next listing: the name to select, or else the row
int selectIndex_ = 0;
Wait wait_ = Wait::None;
uint32_t waitSinceMs_ = 0, lastDrawMs_ = 0;
Item clip_, target_; // the clipboard; what a dialog, the editor or the details are about
bool cut_ = false;
Ask ask_ = Ask::None;
std::unique_ptr<DialogModel> dialog_;
std::string question_;
LineEditor editor_{60};
bool renaming_ = false; // Name view: renaming target_, or else a new folder
bool pasting_ = false; // the operation running came from v: it may ask to replace
std::vector<std::string> details_;
int detailsTop_ = 0;
std::string message_;
uint32_t messageMs_ = 0;
};
} // namespace roro