Login
1 branch 0 tags
Ben (Desktop/Arch) Code cleanup 3637223 1 month ago 78 Commits
moon / src / screens / podcast_feed_screen.c
#include "podcast_feed_screen.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../podcast_xml.h"
#include "../storage.h"
#include "../topbar.h"

#define PODCASTS_OPML "/Podcasts/podcasts.xml"

static void cancel_event_cb(lv_event_t* e) {
	(void)e;
	navigate_back(SCREEN_PODCASTS);
}

// Look up feed URL from OPML by matching directory basename
static bool lookup_feed_url(const char* dir,
                            char* out_title,
                            size_t title_size,
                            char* out_url,
                            size_t url_size) {
	const char* basename = strrchr(dir, '/');
	basename = (basename && basename[1]) ? basename + 1 : dir;

	podcast_feed_t* feeds = malloc(PODCAST_MAX_FEEDS * sizeof(podcast_feed_t));
	if (!feeds) {
		return false;
	}

	int count = podcast_parse_opml(PODCASTS_OPML, feeds, PODCAST_MAX_FEEDS);

	// Match by sanitizing each feed title and comparing to basename
	for (int i = 0; i < count; i++) {
		char sanitized[65];
		podcast_sanitize_title(feeds[i].title, sanitized, sizeof(sanitized));

		if (strcmp(sanitized, basename) == 0) {
			snprintf(out_title, title_size, "%s", feeds[i].title);
			snprintf(out_url, url_size, "%s", feeds[i].url);
			free(feeds);
			return true;
		}
	}

	free(feeds);
	return false;
}

static void sync_click_cb(lv_event_t* e) {
	if (lv_event_get_code(e) != LV_EVENT_CLICKED) {
		return;
	}

	podcast_feed_screen_state_t* state = ui_state.podcast_feed;
	if (!state) {
		return;
	}

	// Look up feed URL from OPML
	if (!lookup_feed_url(state->dir, app_state.podcast_title,
	                     sizeof(app_state.podcast_title), app_state.podcast_url,
	                     sizeof(app_state.podcast_url))) {
		return;
	}

	navigate_to(SCREEN_PODCAST_SYNC);
}

static void back_event_cb(lv_event_t* e) {
	if (lv_event_get_code(e) == LV_EVENT_CLICKED) {
		cancel_event_cb(e);
	}
}

static void episode_click_cb(lv_event_t* e) {
	if (lv_event_get_code(e) != LV_EVENT_CLICKED) {
		return;
	}

	podcast_feed_screen_state_t* state = ui_state.podcast_feed;
	if (!state) {
		return;
	}

	int index = (int)(intptr_t)lv_event_get_user_data(e);

	// Get sorted entries to find episode dir by index
	storage_entry_t* entries =
	    malloc(STORAGE_MAX_DIR_ENTRIES * sizeof(storage_entry_t));
	if (!entries) {
		return;
	}

	int count =
	    get_sorted_entries(state->dir, entries, STORAGE_MAX_DIR_ENTRIES);
	if (index < 0 || index >= count) {
		free(entries);
		return;
	}

	// Build episode directory path and store in app_state
	int n = snprintf(app_state.podcast_episode_dir,
	                 sizeof(app_state.podcast_episode_dir), "%s/%s", state->dir,
	                 entries[index].name);
	free(entries);

	if (n <= 0 || (size_t)n >= sizeof(app_state.podcast_episode_dir)) {
		return;
	}

	navigate_to(SCREEN_PODCAST_EPISODE);
}

ui_state_t setup_podcast_feed_screen(lv_obj_t* parent,
                                     const char* dir,
                                     const char* focus_item) {
	podcast_feed_screen_state_t* state =
	    calloc(1, sizeof(podcast_feed_screen_state_t));

	if (dir && dir[0]) {
		strncpy(state->dir, dir, sizeof(state->dir) - 1);
		state->dir[sizeof(state->dir) - 1] = '\0';
	}

	state->list = bvs_list_create(parent);

	// Set topbar title to directory basename
	const char* basename = strrchr(state->dir, '/');
	basename = (basename && basename[1]) ? basename + 1 : "Episodes";
	topbar_set_title(basename);

	lv_obj_t* focus_btn = NULL;

	// Sync button for this feed
	lv_obj_t* sync_btn = bvs_list_add_nav_button(
	    state->list, NULL, "Sync now", sync_click_cb, NULL, cancel_event_cb);
	focus_btn = sync_btn;

	storage_entry_t* entries =
	    malloc(STORAGE_MAX_DIR_ENTRIES * sizeof(storage_entry_t));
	int count = entries ? get_sorted_entries(state->dir, entries,
	                                         STORAGE_MAX_DIR_ENTRIES)
	                    : 0;

	// Only show directories (episode dirs), skip regular files like feed.xml
	int btn_index = 0;
	for (int i = 0; i < count; i++) {
		if (entries[i].type != STORAGE_TYPE_DIR) {
			continue;
		}

		// Try to read title.txt for a human-readable label
		char title_path[STORAGE_MAX_PATH];
		char title[128];
		const char* label = entries[i].name;
		int tp = snprintf(title_path, sizeof(title_path), "%s/%s/title.txt",
		                  state->dir, entries[i].name);
		if (tp > 0 && (size_t)tp < sizeof(title_path)) {
			storage_file_t tf = storage_open(title_path, "r");
			if (tf) {
				size_t n = storage_read(tf, title, sizeof(title) - 1);
				storage_close(tf);
				title[n] = '\0';
				if (n > 0) {
					label = title;
				}
			}
		}

		lv_obj_t* btn =
		    bvs_list_add_nav_button(state->list, NULL, label, episode_click_cb,
		                            (void*)(intptr_t)i, cancel_event_cb);

		if (!focus_btn) {
			focus_btn = btn;
		}
		if (focus_item && focus_item[0] &&
		    strcmp(entries[i].name, focus_item) == 0) {
			focus_btn = btn;
		}
		btn_index++;
	}

	if (btn_index == 0) {
		bvs_list_add_button(state->list, NULL, "No episodes");
	}

	free(entries);

	// Back button
	lv_obj_t* back_btn = bvs_list_add_nav_button(
	    state->list, NULL, "Back", back_event_cb, NULL, cancel_event_cb);

	lv_group_focus_obj(focus_btn ? focus_btn : back_btn);

	return (ui_state_t){.type = SCREEN_PODCAST_FEED, .podcast_feed = state};
}

void free_podcast_feed_screen(podcast_feed_screen_state_t* state) {
	if (!state) {
		return;
	}
	free(state);
}

void update_podcast_feed_screen(podcast_feed_screen_state_t* state) {
	(void)state;
}