Merge pull request #33 from jpirnay/fix-sleepimage
feat: Treat .sleep and sleep as equal
This commit is contained in:
@@ -425,6 +425,9 @@ STR_AUTHOR: "Author"
|
|||||||
STR_SERIES: "Series"
|
STR_SERIES: "Series"
|
||||||
STR_FILE_SIZE: "Size"
|
STR_FILE_SIZE: "Size"
|
||||||
STR_SLEEP_COVER_OVERLAY: "Sleep Screen Info Overlay"
|
STR_SLEEP_COVER_OVERLAY: "Sleep Screen Info Overlay"
|
||||||
|
STR_SLEEP_IMAGE_PICK_MODE: "Sleep Image Selection"
|
||||||
|
STR_RANDOM: "Random"
|
||||||
|
STR_SEQUENTIAL: "Sequential"
|
||||||
STR_OVERLAY_WHITE: "White"
|
STR_OVERLAY_WHITE: "White"
|
||||||
STR_OVERLAY_GRAY: "Gray"
|
STR_OVERLAY_GRAY: "Gray"
|
||||||
STR_OVERLAY_BLACK: "Black"
|
STR_OVERLAY_BLACK: "Black"
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class CrossPointSettings {
|
|||||||
SLEEP_SCREEN_MODE_COUNT
|
SLEEP_SCREEN_MODE_COUNT
|
||||||
};
|
};
|
||||||
enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1, SLEEP_SCREEN_COVER_MODE_COUNT };
|
enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1, SLEEP_SCREEN_COVER_MODE_COUNT };
|
||||||
|
enum SLEEP_IMAGE_PICK_MODE { PICK_RANDOM = 0, PICK_SEQUENTIAL = 1, SLEEP_IMAGE_PICK_MODE_COUNT };
|
||||||
enum SLEEP_SCREEN_COVER_FILTER {
|
enum SLEEP_SCREEN_COVER_FILTER {
|
||||||
NO_FILTER = 0,
|
NO_FILTER = 0,
|
||||||
BLACK_AND_WHITE = 1,
|
BLACK_AND_WHITE = 1,
|
||||||
@@ -177,6 +178,8 @@ class CrossPointSettings {
|
|||||||
uint8_t sleepScreenCoverFilter = NO_FILTER;
|
uint8_t sleepScreenCoverFilter = NO_FILTER;
|
||||||
// Apply information overlay with reading progress on sleep cover
|
// Apply information overlay with reading progress on sleep cover
|
||||||
uint8_t sleepCoverOverlay = 0;
|
uint8_t sleepCoverOverlay = 0;
|
||||||
|
// Sleep image pick mode (random vs sequential walk-through)
|
||||||
|
uint8_t sleepImagePickMode = PICK_RANDOM;
|
||||||
// Status bar settings (statusBar retained for migration only)
|
// Status bar settings (statusBar retained for migration only)
|
||||||
uint8_t statusBar = FULL;
|
uint8_t statusBar = FULL;
|
||||||
uint8_t statusBarChapterPageCount = 1;
|
uint8_t statusBarChapterPageCount = 1;
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ bool CrossPointState::loadFromBinaryFile() {
|
|||||||
if (version >= 2) {
|
if (version >= 2) {
|
||||||
serialization::readPod(inputFile, lastSleepImage);
|
serialization::readPod(inputFile, lastSleepImage);
|
||||||
} else {
|
} else {
|
||||||
lastSleepImage = UINT8_MAX;
|
lastSleepImage = SIZE_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version >= 3) {
|
if (version >= 3) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class CrossPointState {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
std::string openEpubPath;
|
std::string openEpubPath;
|
||||||
uint8_t lastSleepImage = UINT8_MAX; // UINT8_MAX = unset sentinel
|
size_t lastSleepImage = SIZE_MAX; // SIZE_MAX = unset sentinel
|
||||||
uint8_t readerActivityLoadCount = 0;
|
uint8_t readerActivityLoadCount = 0;
|
||||||
bool lastSleepFromReader = false;
|
bool lastSleepFromReader = false;
|
||||||
~CrossPointState() = default;
|
~CrossPointState() = default;
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ bool JsonSettingsIO::loadState(CrossPointState& s, const char* json) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.openEpubPath = doc["openEpubPath"] | std::string("");
|
s.openEpubPath = doc["openEpubPath"] | std::string("");
|
||||||
s.lastSleepImage = doc["lastSleepImage"] | (uint8_t)UINT8_MAX;
|
s.lastSleepImage = doc["lastSleepImage"] | SIZE_MAX;
|
||||||
s.readerActivityLoadCount = doc["readerActivityLoadCount"] | (uint8_t)0;
|
s.readerActivityLoadCount = doc["readerActivityLoadCount"] | (uint8_t)0;
|
||||||
s.lastSleepFromReader = doc["lastSleepFromReader"] | false;
|
s.lastSleepFromReader = doc["lastSleepFromReader"] | false;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ inline const std::vector<SettingInfo>& getSettingsList() {
|
|||||||
StrId::STR_SLEEP_COVER_OVERLAY, &CrossPointSettings::sleepCoverOverlay,
|
StrId::STR_SLEEP_COVER_OVERLAY, &CrossPointSettings::sleepCoverOverlay,
|
||||||
{StrId::STR_OVERLAY_OFF, StrId::STR_OVERLAY_WHITE, StrId::STR_OVERLAY_GRAY, StrId::STR_OVERLAY_BLACK},
|
{StrId::STR_OVERLAY_OFF, StrId::STR_OVERLAY_WHITE, StrId::STR_OVERLAY_GRAY, StrId::STR_OVERLAY_BLACK},
|
||||||
"sleepCoverOverlay", StrId::STR_CAT_DISPLAY),
|
"sleepCoverOverlay", StrId::STR_CAT_DISPLAY),
|
||||||
|
SettingInfo::Enum(StrId::STR_SLEEP_IMAGE_PICK_MODE, &CrossPointSettings::sleepImagePickMode,
|
||||||
|
{StrId::STR_RANDOM, StrId::STR_SEQUENTIAL}, "sleepImagePickMode", StrId::STR_CAT_DISPLAY),
|
||||||
SettingInfo::Enum(StrId::STR_HIDE_BATTERY, &CrossPointSettings::hideBatteryPercentage,
|
SettingInfo::Enum(StrId::STR_HIDE_BATTERY, &CrossPointSettings::hideBatteryPercentage,
|
||||||
{StrId::STR_NEVER, StrId::STR_IN_READER, StrId::STR_ALWAYS}, "hideBatteryPercentage",
|
{StrId::STR_NEVER, StrId::STR_IN_READER, StrId::STR_ALWAYS}, "hideBatteryPercentage",
|
||||||
StrId::STR_CAT_DISPLAY),
|
StrId::STR_CAT_DISPLAY),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <Txt.h>
|
#include <Txt.h>
|
||||||
#include <Xtc.h>
|
#include <Xtc.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <new>
|
#include <new>
|
||||||
|
|
||||||
#include "../reader/EpubReaderActivity.h"
|
#include "../reader/EpubReaderActivity.h"
|
||||||
@@ -158,6 +159,70 @@ int pngOverlayDraw(PNGDRAW* pDraw) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collects full paths of valid image files from /.sleep and /sleep, with no preference between
|
||||||
|
// the two directories. BMP files are validated by parsing their headers; invalid BMPs are skipped.
|
||||||
|
// When allowPng is true, .png files are also accepted (PNG validation happens later at decode time).
|
||||||
|
std::vector<std::string> collectSleepImages(bool allowPng) {
|
||||||
|
std::vector<std::string> files;
|
||||||
|
for (const char* sleepDir : {"/.sleep", "/sleep"}) {
|
||||||
|
auto dir = Storage.open(sleepDir);
|
||||||
|
if (!dir || !dir.isDirectory()) {
|
||||||
|
if (dir) dir.close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
char name[500];
|
||||||
|
for (auto file = dir.openNextFile(); file; file = dir.openNextFile()) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
file.close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
file.getName(name, sizeof(name));
|
||||||
|
auto filename = std::string(name);
|
||||||
|
if (filename[0] == '.') {
|
||||||
|
file.close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const bool isBmp = FsHelpers::hasBmpExtension(filename);
|
||||||
|
const bool isPng = allowPng && FsHelpers::hasPngExtension(filename);
|
||||||
|
if (!isBmp && !isPng) {
|
||||||
|
file.close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isBmp) {
|
||||||
|
Bitmap bmp(file);
|
||||||
|
if (bmp.parseHeaders() != BmpReaderError::Ok) {
|
||||||
|
LOG_DBG("SLP", "Skipping invalid BMP file: %s", name);
|
||||||
|
file.close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
files.emplace_back(std::string(sleepDir) + "/" + filename);
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
dir.close();
|
||||||
|
}
|
||||||
|
// Sort by full path so the order is deterministic across reboots — required for sequential
|
||||||
|
// pick mode, harmless for random pick mode.
|
||||||
|
std::sort(files.begin(), files.end());
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Picks the next file index based on the user's pick mode.
|
||||||
|
// RANDOM: uniform random with single reroll to avoid immediate repeats.
|
||||||
|
// SEQUENTIAL: advances from APP_STATE.lastSleepImage, wrapping at numFiles.
|
||||||
|
size_t pickSleepImageIndex(size_t numFiles) {
|
||||||
|
if (SETTINGS.sleepImagePickMode == CrossPointSettings::SLEEP_IMAGE_PICK_MODE::PICK_SEQUENTIAL) {
|
||||||
|
const size_t last = APP_STATE.lastSleepImage;
|
||||||
|
if (last == SIZE_MAX || last >= numFiles) return 0;
|
||||||
|
return (last + 1) % numFiles;
|
||||||
|
}
|
||||||
|
size_t idx = random(numFiles);
|
||||||
|
while (numFiles > 1 && APP_STATE.lastSleepImage != SIZE_MAX && idx == APP_STATE.lastSleepImage) {
|
||||||
|
idx = random(numFiles);
|
||||||
|
}
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void SleepActivity::onEnter() {
|
void SleepActivity::onEnter() {
|
||||||
@@ -202,78 +267,29 @@ void SleepActivity::renderCustomSleepScreen() const {
|
|||||||
explicitSleepFile.close();
|
explicitSleepFile.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have a /.sleep (preferred) or /sleep directory
|
// Collect valid BMP files from both /.sleep and /sleep directories (no preference between them)
|
||||||
const char* sleepDir = nullptr;
|
const auto files = collectSleepImages(/*allowPng=*/false);
|
||||||
auto dir = Storage.open("/.sleep");
|
const auto numFiles = files.size();
|
||||||
if (dir && dir.isDirectory()) {
|
if (numFiles > 0) {
|
||||||
sleepDir = "/.sleep";
|
const auto pickedIndex = pickSleepImageIndex(numFiles);
|
||||||
} else {
|
APP_STATE.lastSleepImage = pickedIndex;
|
||||||
if (dir) dir.close();
|
APP_STATE.saveToFile();
|
||||||
dir = Storage.open("/sleep");
|
const auto& filename = files[pickedIndex];
|
||||||
if (dir && dir.isDirectory()) {
|
FsFile file;
|
||||||
sleepDir = "/sleep";
|
if (Storage.openFileForRead("SLP", filename, file)) {
|
||||||
}
|
LOG_DBG("SLP", "Loading sleep image: %s", filename.c_str());
|
||||||
}
|
delay(100);
|
||||||
|
Bitmap bitmap(file, true);
|
||||||
if (sleepDir) {
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||||
std::vector<std::string> files;
|
const BookOverlayInfo resolvedOverlayInfo =
|
||||||
char name[500];
|
shouldLoadOverlayInfo ? getBookOverlayInfo(APP_STATE.openEpubPath) : overlayInfo;
|
||||||
// collect all valid BMP files
|
renderBitmapSleepScreen(bitmap, resolvedOverlayInfo);
|
||||||
for (auto file = dir.openNextFile(); file; file = dir.openNextFile()) {
|
|
||||||
if (file.isDirectory()) {
|
|
||||||
file.close();
|
file.close();
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
file.getName(name, sizeof(name));
|
|
||||||
auto filename = std::string(name);
|
|
||||||
if (filename[0] == '.') {
|
|
||||||
file.close();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!FsHelpers::hasBmpExtension(filename)) {
|
|
||||||
LOG_DBG("SLP", "Skipping non-.bmp file name: %s", name);
|
|
||||||
file.close();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Bitmap bitmap(file);
|
|
||||||
if (bitmap.parseHeaders() != BmpReaderError::Ok) {
|
|
||||||
LOG_DBG("SLP", "Skipping invalid BMP file: %s", name);
|
|
||||||
file.close();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
files.emplace_back(filename);
|
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
const auto numFiles = files.size();
|
|
||||||
if (numFiles > 0) {
|
|
||||||
// Generate a random number between 1 and numFiles
|
|
||||||
auto randomFileIndex = random(numFiles);
|
|
||||||
// If we picked the same image as last time, reroll
|
|
||||||
while (numFiles > 1 && APP_STATE.lastSleepImage != UINT8_MAX && randomFileIndex == APP_STATE.lastSleepImage) {
|
|
||||||
randomFileIndex = random(numFiles);
|
|
||||||
}
|
|
||||||
APP_STATE.lastSleepImage = randomFileIndex;
|
|
||||||
APP_STATE.saveToFile();
|
|
||||||
const auto filename = std::string(sleepDir) + "/" + files[randomFileIndex];
|
|
||||||
FsFile file;
|
|
||||||
if (Storage.openFileForRead("SLP", filename, file)) {
|
|
||||||
LOG_DBG("SLP", "Randomly loading: %s/%s", sleepDir, files[randomFileIndex].c_str());
|
|
||||||
delay(100);
|
|
||||||
Bitmap bitmap(file, true);
|
|
||||||
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
|
||||||
const BookOverlayInfo resolvedOverlayInfo =
|
|
||||||
shouldLoadOverlayInfo ? getBookOverlayInfo(APP_STATE.openEpubPath) : overlayInfo;
|
|
||||||
renderBitmapSleepScreen(bitmap, resolvedOverlayInfo);
|
|
||||||
file.close();
|
|
||||||
dir.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (dir) dir.close();
|
|
||||||
|
|
||||||
renderDefaultSleepScreen();
|
renderDefaultSleepScreen();
|
||||||
}
|
}
|
||||||
@@ -769,67 +785,22 @@ void SleepActivity::renderOverlaySleepScreen() const {
|
|||||||
return rc == PNG_SUCCESS;
|
return rc == PNG_SUCCESS;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try /.sleep/ (preferred) or /sleep/ directory (random selection, same as renderCustomSleepScreen).
|
// Collect images from both /.sleep and /sleep directories (no preference between them).
|
||||||
// Accepts both .bmp and .png files; .bmp headers are validated during the scan.
|
// Accepts both .bmp and .png files; .bmp headers are validated during the scan.
|
||||||
bool overlayDrawn = false;
|
bool overlayDrawn = false;
|
||||||
const char* sleepDir = nullptr;
|
const auto files = collectSleepImages(/*allowPng=*/true);
|
||||||
auto dir = Storage.open("/.sleep");
|
const auto numFiles = files.size();
|
||||||
if (dir && dir.isDirectory()) {
|
if (numFiles > 0) {
|
||||||
sleepDir = "/.sleep";
|
const auto pickedIndex = pickSleepImageIndex(numFiles);
|
||||||
} else {
|
APP_STATE.lastSleepImage = pickedIndex;
|
||||||
if (dir) dir.close();
|
APP_STATE.saveToFile();
|
||||||
dir = Storage.open("/sleep");
|
const std::string& selected = files[pickedIndex];
|
||||||
if (dir && dir.isDirectory()) {
|
if (FsHelpers::hasPngExtension(selected)) {
|
||||||
sleepDir = "/sleep";
|
overlayDrawn = tryDrawPngOverlay(selected);
|
||||||
|
} else {
|
||||||
|
overlayDrawn = tryDrawOverlay(selected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sleepDir) {
|
|
||||||
std::vector<std::string> files;
|
|
||||||
char name[500];
|
|
||||||
for (auto file = dir.openNextFile(); file; file = dir.openNextFile()) {
|
|
||||||
if (file.isDirectory()) {
|
|
||||||
file.close();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
file.getName(name, sizeof(name));
|
|
||||||
auto filename = std::string(name);
|
|
||||||
if (filename[0] == '.') {
|
|
||||||
file.close();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const bool isBmp = FsHelpers::checkFileExtension(filename, ".bmp");
|
|
||||||
const bool isPng = FsHelpers::checkFileExtension(filename, ".png");
|
|
||||||
if (!isBmp && !isPng) {
|
|
||||||
file.close();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (isBmp) {
|
|
||||||
Bitmap bmp(file);
|
|
||||||
if (bmp.parseHeaders() != BmpReaderError::Ok) {
|
|
||||||
file.close();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
files.emplace_back(filename);
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
const auto numFiles = files.size();
|
|
||||||
if (numFiles > 0) {
|
|
||||||
auto randomFileIndex = random(numFiles);
|
|
||||||
while (numFiles > 1 && randomFileIndex == APP_STATE.lastSleepImage) {
|
|
||||||
randomFileIndex = random(numFiles);
|
|
||||||
}
|
|
||||||
APP_STATE.lastSleepImage = randomFileIndex;
|
|
||||||
APP_STATE.saveToFile();
|
|
||||||
const std::string selected = std::string(sleepDir) + "/" + files[randomFileIndex];
|
|
||||||
if (FsHelpers::checkFileExtension(selected, ".png")) {
|
|
||||||
overlayDrawn = tryDrawPngOverlay(selected);
|
|
||||||
} else {
|
|
||||||
overlayDrawn = tryDrawOverlay(selected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dir) dir.close();
|
|
||||||
|
|
||||||
if (!overlayDrawn) {
|
if (!overlayDrawn) {
|
||||||
overlayDrawn = tryDrawOverlay("/sleep.bmp");
|
overlayDrawn = tryDrawOverlay("/sleep.bmp");
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ void Lyra3CoversTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, con
|
|||||||
cornerRadius, false, false, true, true, Color::LightGray);
|
cornerRadius, false, false, true, true, Color::LightGray);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawProgressBadge(renderer,
|
drawProgressBadge(static_cast<const GfxRenderer&>(renderer),
|
||||||
Rect{tileX + hPaddingInSelection, tileY + hPaddingInSelection,
|
Rect{tileX + hPaddingInSelection, tileY + hPaddingInSelection,
|
||||||
tileWidth - 2 * hPaddingInSelection, coverHeight},
|
tileWidth - 2 * hPaddingInSelection, coverHeight},
|
||||||
progressPercent);
|
progressPercent);
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ int LyraTheme::getRecentBookProgressPercent(const RecentBook& book) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LyraTheme::drawProgressBadge(GfxRenderer& renderer, Rect anchorRect, int progressPercent) {
|
void LyraTheme::drawProgressBadge(const GfxRenderer& renderer, Rect anchorRect, int progressPercent) {
|
||||||
if (progressPercent < 0) {
|
if (progressPercent < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -640,7 +640,7 @@ void LyraTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std:
|
|||||||
}
|
}
|
||||||
|
|
||||||
drawProgressBadge(
|
drawProgressBadge(
|
||||||
renderer,
|
static_cast<const GfxRenderer&>(renderer),
|
||||||
Rect{tileX + hPaddingInSelection + coverWidth + LyraMetrics::values.verticalSpacing, tileY,
|
Rect{tileX + hPaddingInSelection + coverWidth + LyraMetrics::values.verticalSpacing, tileY,
|
||||||
tileWidth - 2 * hPaddingInSelection - coverWidth - LyraMetrics::values.verticalSpacing, tileHeight},
|
tileWidth - 2 * hPaddingInSelection - coverWidth - LyraMetrics::values.verticalSpacing, tileHeight},
|
||||||
progressPercent);
|
progressPercent);
|
||||||
|
|||||||
@@ -72,5 +72,5 @@ class LyraTheme : public BaseTheme {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
static int getRecentBookProgressPercent(const RecentBook& book);
|
static int getRecentBookProgressPercent(const RecentBook& book);
|
||||||
static void drawProgressBadge(GfxRenderer& renderer, Rect anchorRect, int progressPercent);
|
static void drawProgressBadge(const GfxRenderer& renderer, Rect anchorRect, int progressPercent);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user