text/plain
•
5.21 KB
•
199 lines
#include "files_screen.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../audio_player.h"
#include "../playlist.h"
#include "../storage.h"
#include "../topbar.h"
// Case-insensitive extension check helper
static bool ext_match(const char* ext, const char* pattern) {
while (*pattern) {
if ((tolower(*ext++) != tolower(*pattern++))) {
return false;
}
}
return !(*ext);
}
// Check if filename is a supported audio file
bool is_audio_file(const char* name) {
size_t len = strlen(name);
if (len < 4) {
return false;
}
// Find last dot
const char* dot = strrchr(name, '.');
if (!dot || dot == name) {
return false;
}
// Check supported extensions
return ext_match(dot, ".mp3") || ext_match(dot, ".wav") ||
ext_match(dot, ".aac") || ext_match(dot, ".m4a") ||
ext_match(dot, ".m4b") || ext_match(dot, ".flac") ||
ext_match(dot, ".ogg") ||
ext_match(dot, ".opu") || // 8.3 Filenames...
ext_match(dot, ".fla") || ext_match(dot, ".opus");
}
// Cancel goes up one directory or to home
static void cancel_event_cb(lv_event_t* e) {
(void)e;
files_screen_state_t* state = ui_state.files;
if (!state) {
return;
}
if (strcmp(state->cwd, "/") != 0) {
char parent[STORAGE_MAX_PATH];
char current_dir[STORAGE_MAX_NAME];
path_parent(state->cwd, parent, sizeof(parent), current_dir,
sizeof(current_dir));
lv_obj_t* scr = navigate_prepare(ANIM_BACK);
ui_state = setup_files_screen(scr, parent, current_dir);
navigate_commit();
} else {
navigate_back(SCREEN_HOME);
}
}
// Back button click - same as cancel
static void back_event_cb(lv_event_t* e) {
if (lv_event_get_code(e) == LV_EVENT_CLICKED) {
cancel_event_cb(e);
}
}
// Entry clicked - index stored in userdata
static void entry_click_cb(lv_event_t* e) {
if (lv_event_get_code(e) != LV_EVENT_CLICKED) {
return;
}
files_screen_state_t* state = ui_state.files;
if (!state) {
return;
}
int index = (int)(intptr_t)lv_event_get_user_data(e);
storage_entry_t entry;
if (!get_entry_at_index(state->cwd, index, &entry)) {
return;
}
// Build full path
char path[STORAGE_MAX_PATH];
bool at_root = (strcmp(state->cwd, "/") == 0);
int n = snprintf(path, sizeof(path), "%s%s%s", state->cwd,
at_root ? "" : "/", entry.name);
if (n <= 0 || (size_t)n >= sizeof(path)) {
return;
}
if (entry.type == STORAGE_TYPE_DIR) {
// Navigate into directory
lv_obj_t* scr = navigate_prepare(ANIM_FORWARD);
ui_state = setup_files_screen(scr, path, "");
navigate_commit();
} else if (is_audio_file(entry.name)) {
if (playlist_is_empty()) {
playlist_add(path, PLAYLIST_MUSIC);
playlist_set_index(0);
playlist_save();
navigate_to(SCREEN_NOW_PLAYING);
} else {
// Playlist has entries: show queue dialog
show_queue_dialog(path, PLAYLIST_MUSIC, SCREEN_FILES, state->cwd,
entry.name);
}
}
}
const void* storage_entry_get_icon(const storage_entry_t* entry) {
if (entry->type == STORAGE_TYPE_DIR) {
return LV_SYMBOL_DIRECTORY;
}
if (is_audio_file(entry->name)) {
return LV_SYMBOL_AUDIO;
}
return LV_SYMBOL_FILE;
}
ui_state_t setup_files_screen(lv_obj_t* parent,
const char* cwd,
const char* focus_item) {
files_screen_state_t* state = calloc(1, sizeof(files_screen_state_t));
// Store current directory (default to "/" if NULL or empty)
if (cwd && cwd[0] != '\0') {
strncpy(state->cwd, cwd, sizeof(state->cwd));
state->cwd[sizeof(state->cwd) - 1] = '\0';
} else {
strcpy(state->cwd, "/");
}
state->list = bvs_list_create(parent);
// Set topbar title to directory basename
const char* basename = strrchr(state->cwd, '/');
basename = (basename && basename[1]) ? basename + 1 : "Files";
topbar_set_title(basename);
lv_obj_t* focus_btn = NULL;
if (!storage_is_mounted()) {
bvs_list_add_button(state->list, LV_SYMBOL_WARNING,
"Error reading sd card");
} else {
storage_entry_t* entries =
malloc(STORAGE_MAX_DIR_ENTRIES * sizeof(storage_entry_t));
int count = entries ? get_sorted_entries(state->cwd, entries,
STORAGE_MAX_DIR_ENTRIES)
: 0;
if (count == 0) {
bvs_list_add_button(state->list, NULL, "Empty Directory");
} else {
for (int i = 0; i < count; i++) {
lv_obj_t* btn = bvs_list_add_nav_button(
state->list, NULL, entries[i].name, entry_click_cb,
(void*)(intptr_t)i, cancel_event_cb);
if (!focus_btn) {
focus_btn = btn;
}
// Check if this is the item to focus
if (focus_item && focus_item[0] != '\0' &&
strcmp(entries[i].name, focus_item) == 0) {
focus_btn = btn;
}
}
}
free(entries);
}
// Add Back button
lv_obj_t* back_btn = bvs_list_add_nav_button(
state->list, NULL, "Back", back_event_cb, NULL, cancel_event_cb);
// Focus: requested item > first entry > back button
lv_group_focus_obj(focus_btn ? focus_btn : back_btn);
return (ui_state_t){.type = SCREEN_FILES, .files = state};
}
void free_files_screen(files_screen_state_t* state) {
if (!state) {
return;
}
free(state);
}
void update_files_screen(files_screen_state_t* state) {
(void)state; // LVGL handles updates via timer
}