[ai] improved look&feel

This commit is contained in:
2025-08-08 23:59:26 +02:00
parent 00048face4
commit ce863f4d02
6 changed files with 745 additions and 310 deletions

View File

@@ -96,12 +96,12 @@ void DirectoryNode::buildDirectoryTree(const char *currentPath)
subdirectories.clear();
mp3Files.clear();
ids.clear();
// Reserve memory to reduce heap fragmentation (optimization 3)
subdirectories.reserve(8); // Reserve space for 8 subdirectories
mp3Files.reserve(16); // Reserve space for 16 MP3 files
ids.reserve(16); // Reserve space for 16 IDs
subdirectories.reserve(8); // Reserve space for 8 subdirectories
mp3Files.reserve(16); // Reserve space for 16 MP3 files
ids.reserve(16); // Reserve space for 16 IDs
File rootDir = SD.open(currentPath);
while (true)
{
@@ -117,10 +117,11 @@ void DirectoryNode::buildDirectoryTree(const char *currentPath)
subdirectories.push_back(newNode);
newNode->buildDirectoryTree((String(currentPath) + entry.name()).c_str());
}
else if (String(entry.name()).endsWith(".mp3")||String(entry.name()).endsWith(".MP3"))
else if (String(entry.name()).endsWith(".mp3") || String(entry.name()).endsWith(".MP3"))
{
String fullPath = String(currentPath);
if (!fullPath.endsWith("/")) fullPath += "/";
if (!fullPath.endsWith("/"))
fullPath += "/";
fullPath += entry.name();
mp3Files.push_back(fullPath);
ids.push_back(getNextId());
@@ -130,8 +131,6 @@ void DirectoryNode::buildDirectoryTree(const char *currentPath)
rootDir.close();
}
void DirectoryNode::printDirectoryTree(int level) const
{
for (int i = 0; i < level; i++)
@@ -210,7 +209,7 @@ DirectoryNode *DirectoryNode::advanceToMP3(const uint16_t id)
}
// Recursively search in subdirectory
DirectoryNode* result = subdir->advanceToMP3(id);
DirectoryNode *result = subdir->advanceToMP3(id);
if (result != nullptr && result->getCurrentPlaying() != nullptr)
{
return result;
@@ -222,10 +221,10 @@ DirectoryNode *DirectoryNode::advanceToMP3(const uint16_t id)
return nullptr;
}
DirectoryNode *DirectoryNode::advanceToMP3(const String *songName)
{
if (songName == nullptr) {
if (songName == nullptr)
{
Serial.println("advanceToMP3: songName is null");
return nullptr;
}
@@ -236,12 +235,15 @@ DirectoryNode *DirectoryNode::advanceToMP3(const String *songName)
// First, search in the current directory's MP3 files
for (size_t i = 0; i < mp3Files.size(); i++)
{
if (isAbsolutePath) {
if (*songName == mp3Files[i]) {
if (isAbsolutePath)
{
if (*songName == mp3Files[i])
{
setCurrentPlaying(&mp3Files[i]);
return this;
}
} else if (mp3Files[i].endsWith(*songName))
}
else if (mp3Files[i].endsWith(*songName))
{
setCurrentPlaying(&mp3Files[i]);
return this;
@@ -259,14 +261,17 @@ DirectoryNode *DirectoryNode::advanceToMP3(const String *songName)
// Search all files within subdir:
for (size_t i = 0; i < subdir->mp3Files.size(); i++)
{
if (isAbsolutePath) {
if (*songName == subdir->mp3Files[i]) {
subdir->setCurrentPlaying(&subdir->mp3Files[i]);
return subdir;
{
if (isAbsolutePath)
{
if (*songName == subdir->mp3Files[i])
{
subdir->setCurrentPlaying(&subdir->mp3Files[i]);
return subdir;
}
}
} else if (subdir->mp3Files[i].endsWith(*songName))
else if (subdir->mp3Files[i].endsWith(*songName))
{
subdir->setCurrentPlaying(&subdir->mp3Files[i]);
return subdir;
@@ -322,7 +327,7 @@ DirectoryNode *DirectoryNode::goToPreviousMP3(uint32_t thresholdSeconds)
setCurrentPlaying(&mp3Files[currentIndex - 1]);
return this;
}
// If we're at the first song or song not found in current directory,
// we need to find the previous song globally
Serial.println("goToPreviousMP3: At first song or song not found, looking for previous globally");
@@ -338,14 +343,14 @@ DirectoryNode *DirectoryNode::findPreviousMP3Globally(const String *currentGloba
}
// Build a flat list of all MP3 files in order
std::vector<std::pair<DirectoryNode*, int>> allMP3s;
std::vector<std::pair<DirectoryNode *, int>> allMP3s;
buildFlatMP3List(allMP3s);
// Find current song in the flat list
int currentGlobalIndex = -1;
for (size_t i = 0; i < allMP3s.size(); i++)
{
DirectoryNode* node = allMP3s[i].first;
DirectoryNode *node = allMP3s[i].first;
int fileIndex = allMP3s[i].second;
if (node->mp3Files[fileIndex] == *currentGlobal)
{
@@ -357,12 +362,12 @@ DirectoryNode *DirectoryNode::findPreviousMP3Globally(const String *currentGloba
// If current song found and not the first globally, move to previous
if (currentGlobalIndex > 0)
{
DirectoryNode* prevNode = allMP3s[currentGlobalIndex - 1].first;
DirectoryNode *prevNode = allMP3s[currentGlobalIndex - 1].first;
int prevFileIndex = allMP3s[currentGlobalIndex - 1].second;
Serial.print("findPreviousMP3Globally: Moving to previous song globally: ");
Serial.println(prevNode->mp3Files[prevFileIndex]);
prevNode->setCurrentPlaying(&prevNode->mp3Files[prevFileIndex]);
return prevNode;
}
@@ -371,7 +376,7 @@ DirectoryNode *DirectoryNode::findPreviousMP3Globally(const String *currentGloba
return nullptr;
}
void DirectoryNode::buildFlatMP3List(std::vector<std::pair<DirectoryNode*, int>>& allMP3s)
void DirectoryNode::buildFlatMP3List(std::vector<std::pair<DirectoryNode *, int>> &allMP3s)
{
// Add all MP3 files from this directory
for (size_t i = 0; i < mp3Files.size(); i++)
@@ -380,7 +385,7 @@ void DirectoryNode::buildFlatMP3List(std::vector<std::pair<DirectoryNode*, int>>
}
// Recursively add MP3 files from subdirectories
for (DirectoryNode* subdir : subdirectories)
for (DirectoryNode *subdir : subdirectories)
{
subdir->buildFlatMP3List(allMP3s);
}
@@ -449,41 +454,79 @@ DirectoryNode *DirectoryNode::advanceToNextMP3(const String *currentGlobal)
return this;
}
String DirectoryNode::getDirectoryStructureHTML() const
{
String DirectoryNode::getDirectoryStructureHTML() const {
// Calculate required size first (prevents reallocations)
size_t htmlSize = calculateHTMLSize();
String html;
html.reserve(512);
if (name == "/")
{
html += "<ul>\n";
html.reserve(htmlSize); // Precise allocation - no wasted RAM
// Helper lambda to append without temporary Strings
auto append = [&html](const __FlashStringHelper* fstr) {
html += fstr;
};
auto appendId = [&html](uint32_t id) {
html += id; // Direct numeric append (NO temporary String)
};
if (name == "/") {
append(F("<ul>\n"));
}
if (name != "/")
{
html += "<li data-id=\"" + String(id) + "\"><b>" + name + "</b></li>\n";
if (name != "/") {
append(F("<li data-id=\""));
appendId(id);
append(F("\"><b>"));
html += name; // Still uses String, but unavoidable for dynamic content
append(F("</b></li>\n"));
}
for (int i = 0; i < mp3Files.size(); i++)
{
html += "<li data-id=\"" + String(ids[i]) + "\">" + mp3Files[i] + "</li>\n";
for (size_t i = 0; i < mp3Files.size(); i++) {
append(F("<li data-id=\""));
appendId(ids[i]);
append(F("\">"));
html += mp3Files[i]; // Dynamic file name
append(F("</li>\n"));
}
for (DirectoryNode *childNode : subdirectories)
{
html += childNode->getDirectoryStructureHTML();
for (DirectoryNode* child : subdirectories) {
html += child->getDirectoryStructureHTML();
}
if (name == "/")
{
html += "</ul>\n";
if (name == "/") {
append(F("</ul>\n"));
}
return html;
}
// NEW: Calculate exact required size first
size_t DirectoryNode::calculateHTMLSize() const {
size_t size = 0;
// Opening/closing tags
if (name == "/") size += 6; // <ul>\n
// Current directory entry
if (name != "/") {
size += 22 + name.length() + 10; // <li...><b></b></li>\n + ID digits (est)
}
// MP3 files
for (size_t i = 0; i < mp3Files.size(); i++) {
size += 16 + mp3Files[i].length() + 10; // <li...></li>\n + ID digits
}
// Subdirectories
for (DirectoryNode* child : subdirectories) {
size += child->calculateHTMLSize();
}
// Closing tag
if (name == "/") size += 7; // </ul>\n
return size;
}
void DirectoryNode::appendIndentation(String &html, int level) const
{
for (int i = 0; i < level; i++)

View File

@@ -55,6 +55,7 @@ public:
DirectoryNode* advanceToMP3(const uint16_t id);
void advanceToFirstMP3InThisNode();
String getDirectoryStructureHTML() const;
size_t calculateHTMLSize() const;
void appendIndentation(String& html, int level) const;
DirectoryNode* findFirstDirectoryWithMP3s();
String getCurrentPlayingFilePath() const;