This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch main
in repository eradio.
View the commit online.
commit 9f2925c3f024d7511aad21ba547bf0be25bb22e2
Author: politebot <[email protected]>
AuthorDate: Sat Oct 11 04:31:15 2025 -0500
Add pagination for search results
---
src/appdata.h | 2 +
src/eradio.edc | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++
src/http.c | 40 +++++++++++----
src/http.h | 2 +-
src/station_list.c | 7 +--
src/station_list.h | 2 +-
src/ui.c | 34 +++++++++++--
src/ui.h | 1 +
8 files changed, 217 insertions(+), 18 deletions(-)
diff --git a/src/appdata.h b/src/appdata.h
index 37d2219..374c78e 100644
--- a/src/appdata.h
+++ b/src/appdata.h
@@ -29,6 +29,7 @@ typedef struct _AppData
Evas_Object *play_pause_btn;
Evas_Object *stop_btn;
Evas_Object *search_btn;
+ Evas_Object *load_more_btn;
Evas_Object *search_bar;
Eina_List *stations;
Eina_List *api_servers; // list of strings (hostnames)
@@ -37,4 +38,5 @@ typedef struct _AppData
Eina_Hash *favorites;
Eina_List *favorites_stations;
ViewMode view_mode;
+ int search_offset;
} AppData;
diff --git a/src/eradio.edc b/src/eradio.edc
new file mode 100644
index 0000000..518668a
--- /dev/null
+++ b/src/eradio.edc
@@ -0,0 +1,147 @@
+collections {
+ group {
+ name: "main";
+ parts {
+ part {
+ name: "background";
+ type: RECT;
+ description {
+ state: "default" 0.0;
+ color: 255 255 255 255;
+ }
+ }
+ part {
+ name: "main_box";
+ type: BOX;
+ description {
+ state: "default" 0.0;
+ align: 1.0 1.0;
+ rel1 {
+ relative: 0.0 0.0;
+ }
+ rel2 {
+ relative: 1.0 1.0;
+ }
+ padding: 10 10;
+ }
+ box {
+ layout: "vertical";
+ padding: 10 10;
+ align: 0.5 0.0;
+ }
+ }
+ part {
+ name: "search_box";
+ type: BOX;
+ description {
+ state: "default" 0.0;
+ align: 0.5 0.0;
+ min: 0 30;
+ }
+ box {
+ layout: "horizontal";
+ padding: 10 0;
+ align: 0.5 0.0;
+ }
+ }
+ part {
+ name: "search_entry";
+ type: SWALLOW;
+ description {
+ state: "default" 0.0;
+ rel1.to: "search_box";
+ rel2.to: "search_box";
+ align: 0.0 0.5;
+ }
+ }
+ part {
+ name: "search_hoversel";
+ type: SWALLOW;
+ description {
+ state: "default" 0.0;
+ rel1.to: "search_entry";
+ rel1.relative: 1.0 0.0;
+ rel2.to: "search_entry";
+ rel2.relative: 1.0 1.0;
+ align: 0.0 0.5;
+ min: 100 30;
+ }
+ }
+ part {
+ name: "search_btn";
+ type: SWALLOW;
+ description {
+ state: "default" 0.0;
+ rel1.to: "search_hoversel";
+ rel1.relative: 1.0 0.0;
+ rel2.to: "search_hoversel";
+ rel2.relative: 1.0 1.0;
+ align: 0.0 0.5;
+ min: 80 30;
+ }
+ }
+ part {
+ name: "station_list";
+ type: SWALLOW;
+ description {
+ state: "default" 0.0;
+ align: 0.5 1.0;
+ }
+ }
+ part {
+ name: "controls_box";
+ type: BOX;
+ description {
+ state: "default" 0.0;
+ align: 0.5 1.0;
+ min: 0 50;
+ }
+ box {
+ layout: "horizontal";
+ padding: 10 0;
+ align: 0.5 1.0;
+ }
+ }
+ part {
+ name: "play_pause_btn";
+ type: SWALLOW;
+ description {
+ state: "default" 0.0;
+ rel1.to: "controls_box";
+ rel2.to: "controls_box";
+ align: 0.5 0.5;
+ }
+ }
+ part {
+ name: "stop_btn";
+ type: SWALLOW;
+ description {
+ state: "default" 0.0;
+ rel1.to: "play_pause_btn";
+ rel1.relative: 1.0 0.0;
+ rel2.to: "play_pause_btn";
+ rel2.relative: 1.0 1.0;
+ align: 0.5 0.5;
+ }
+ }
+ }
+ programs {
+ program {
+ name: "load";
+ signal: "load";
+ source: "";
+ action: STATE_SET "default" 0.0;
+ target: "background";
+ target: "main_box";
+ target: "search_box";
+ target: "search_entry";
+ target: "search_hoversel";
+ target: "search_btn";
+ target: "station_list";
+ target: "controls_box";
+ target: "play_pause_btn";
+ target: "stop_btn";
+ }
+ }
+ }
+}
diff --git a/src/http.c b/src/http.c
index dbea4f7..c3a68fb 100644
--- a/src/http.c
+++ b/src/http.c
@@ -10,6 +10,7 @@
#include "http.h"
#include "station_list.h"
#include "favorites.h"
+#include "ui.h" // Include ui.h for ui_set_load_more_button_visibility
typedef enum _Download_Type
{
@@ -32,6 +33,9 @@ typedef struct _Station_Download_Context
Eina_List *current; // current server node
char search_type[64];
char search_term[512];
+ int offset;
+ int limit;
+ Eina_Bool new_search;
} Station_Download_Context;
typedef struct _Icon_Download_Context
@@ -81,7 +85,7 @@ http_shutdown(void)
}
void
-http_search_stations(AppData *ad, const char *search_term, const char *search_type)
+http_search_stations(AppData *ad, const char *search_term, const char *search_type, int offset, int limit, Eina_Bool new_search)
{
Ecore_Con_Url *url;
Station_Download_Context *d_ctx;
@@ -91,6 +95,9 @@ http_search_stations(AppData *ad, const char *search_term, const char *search_ty
d_ctx = calloc(1, sizeof(Station_Download_Context));
d_ctx->base.type = DOWNLOAD_TYPE_STATIONS;
d_ctx->base.ad = ad;
+ d_ctx->offset = offset;
+ d_ctx->limit = limit;
+ d_ctx->new_search = new_search;
_populate_station_request(d_ctx, ad, search_type, search_term);
_issue_station_request(&url, d_ctx);
ecore_con_url_additional_header_add(url, "User-Agent", "eradio/1.0");
@@ -128,16 +135,17 @@ http_download_icon(AppData *ad, Elm_Object_Item *list_item, const char *url_str)
}
void
-_search_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info)
+_search_btn_clicked_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
AppData *ad = data;
const char *search_term = elm_object_text_get(ad->search_entry);
const char *search_type = elm_object_text_get(ad->search_hoversel);
- http_search_stations(ad, search_term, search_type);
+ ad->search_offset = 0; // Reset offset for a new search
+ http_search_stations(ad, search_term, search_type, ad->search_offset, 100, EINA_TRUE);
}
void
-_search_entry_activated_cb(void *data, Evas_Object *obj, void *event_info)
+_search_entry_activated_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
_search_btn_clicked_cb(data, obj, event_info);
}
@@ -224,7 +232,6 @@ _handle_station_list_complete(Ecore_Con_Event_Url_Complete *ev)
xmlParseChunk(d_ctx->ctxt, "", 0, 1);
doc = d_ctx->ctxt->myDoc;
xmlFreeParserCtxt(d_ctx->ctxt);
- free(d_ctx);
if (doc == NULL)
{
@@ -238,6 +245,7 @@ _handle_station_list_complete(Ecore_Con_Event_Url_Complete *ev)
{
printf("Error: could not create new XPath context\n");
xmlFreeDoc(doc);
+ free(d_ctx);
return;
}
@@ -247,11 +255,15 @@ _handle_station_list_complete(Ecore_Con_Event_Url_Complete *ev)
printf("Error: could not evaluate xpath _expression_\n");
xmlXPathFreeContext(xpathCtx);
xmlFreeDoc(doc);
+ free(d_ctx);
return;
}
- eina_list_free(ad->stations);
- ad->stations = NULL;
+ if (d_ctx->new_search)
+ {
+ eina_list_free(ad->stations);
+ ad->stations = NULL;
+ }
for (int i = 0; i < xpathObj->nodesetval->nodeNr; i++)
{
@@ -289,9 +301,17 @@ _handle_station_list_complete(Ecore_Con_Event_Url_Complete *ev)
ad->stations = eina_list_append(ad->stations, st);
}
+ free(d_ctx);
+
favorites_apply_to_stations(ad);
if (ad->view_mode == VIEW_SEARCH)
- station_list_populate(ad, ad->stations);
+ station_list_populate(ad, ad->stations, d_ctx->new_search);
+
+ // Determine if "Load More" button should be visible
+ if (xpathObj->nodesetval->nodeNr == d_ctx->limit)
+ ui_set_load_more_button_visibility(ad, EINA_TRUE);
+ else
+ ui_set_load_more_button_visibility(ad, EINA_FALSE);
xmlXPathFreeObject(xpathObj);
xmlXPathFreeContext(xpathCtx);
@@ -464,9 +484,9 @@ static void _issue_station_request(Ecore_Con_Url **url_out, Station_Download_Con
const char *server = d_ctx->current ? (const char *)d_ctx->current->data : NULL;
char url_str[1024];
if (server)
- snprintf(url_str, sizeof(url_str), "http://%s/xml/stations/search?%s=%s", server, d_ctx->search_type, d_ctx->search_term);
+ snprintf(url_str, sizeof(url_str), "http://%s/xml/stations/search?%s=%s&offset=%d&limit=%d", server, d_ctx->search_type, d_ctx->search_term, d_ctx->offset, d_ctx->limit);
else
- snprintf(url_str, sizeof(url_str), "http://de2.api.radio-browser.info/xml/stations/search?%s=%s", d_ctx->search_type, d_ctx->search_term);
+ snprintf(url_str, sizeof(url_str), "http://de2.api.radio-browser.info/xml/stations/search?%s=%s&offset=%d&limit=%d", d_ctx->search_type, d_ctx->search_term, d_ctx->offset, d_ctx->limit);
*url_out = ecore_con_url_new(url_str);
}
diff --git a/src/http.h b/src/http.h
index 24e27d8..78bdfd3 100644
--- a/src/http.h
+++ b/src/http.h
@@ -4,7 +4,7 @@
void http_init(AppData *ad);
void http_shutdown(void);
-void http_search_stations(AppData *ad, const char *search_term, const char *search_type);
+void http_search_stations(AppData *ad, const char *search_term, const char *search_type, int offset, int limit, Eina_Bool new_search);
void http_download_icon(AppData *ad, Elm_Object_Item *list_item, const char *url);
void _search_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info);
void _search_entry_activated_cb(void *data, Evas_Object *obj, void *event_info);
diff --git a/src/station_list.c b/src/station_list.c
index 77de71b..4319b56 100644
--- a/src/station_list.c
+++ b/src/station_list.c
@@ -33,12 +33,13 @@ station_list_clear(AppData *ad)
}
void
-station_list_populate(AppData *ad, Eina_List *stations)
+station_list_populate(AppData *ad, Eina_List *stations, Eina_Bool new_search)
{
Eina_List *l;
Station *st;
- station_list_clear(ad);
+ if (new_search)
+ station_list_clear(ad);
EINA_LIST_FOREACH(stations, l, st)
{
@@ -139,7 +140,7 @@ _favorite_remove_btn_clicked_cb(void *data, Evas_Object *obj EINA_UNUSED, void *
// Rebuild favorites list to reflect removal
favorites_rebuild_station_list(ctx->ad);
- station_list_populate(ctx->ad, ctx->ad->favorites_stations);
+ station_list_populate(ctx->ad, ctx->ad->favorites_stations, EINA_TRUE);
free(ctx);
}
diff --git a/src/station_list.h b/src/station_list.h
index d544bcf..00ae1a9 100644
--- a/src/station_list.h
+++ b/src/station_list.h
@@ -2,6 +2,6 @@
#include "appdata.h"
-void station_list_populate(AppData *ad, Eina_List *stations);
+void station_list_populate(AppData *ad, Eina_List *stations, Eina_Bool new_search);
void station_list_clear(AppData *ad);
void _list_item_selected_cb(void *data, Evas_Object *obj, void *event_info);
diff --git a/src/ui.c b/src/ui.c
index 111f8af..97e20a6 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -2,6 +2,7 @@
#include "appdata.h"
#include "favorites.h"
#include "station_list.h"
+#include "http.h" // Include http.h for http_search_stations
static void _win_del_cb(void *data, Evas_Object *obj, void *event_info);
static void _app_exit_cb(void *data, Evas_Object *obj, void *event_info);
@@ -17,6 +18,7 @@ void _search_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info);
void _search_entry_activated_cb(void *data, Evas_Object *obj, void *event_info);
void _list_item_selected_cb(void *data, Evas_Object *obj, void *event_info);
static void _favorites_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info);
+static void _load_more_btn_clicked_cb(void *data, Evas_Object *obj, void *event_info);
static void
@@ -153,18 +155,26 @@ ui_create(AppData *ad)
elm_box_pack_end(controls_hbox, ad->stop_btn);
evas_object_show(ad->stop_btn);
+ ad->load_more_btn = elm_button_add(ad->win);
+ elm_object_text_set(ad->load_more_btn, "Load More");
+ elm_box_pack_end(controls_hbox, ad->load_more_btn);
+ evas_object_show(ad->load_more_btn);
+
evas_object_smart_callback_add(ad->play_pause_btn, "clicked", _play_pause_btn_clicked_cb, ad);
evas_object_smart_callback_add(ad->stop_btn, "clicked", _stop_btn_clicked_cb, ad);
evas_object_smart_callback_add(ad->search_btn, "clicked", _search_btn_clicked_cb, ad);
evas_object_smart_callback_add(ad->search_entry, "activated", _search_entry_activated_cb, ad);
evas_object_smart_callback_add(ad->list, "selected", _list_item_selected_cb, ad);
+ evas_object_smart_callback_add(ad->load_more_btn, "clicked", _load_more_btn_clicked_cb, ad);
/* Favorites view button removed; toolbar callbacks handle switching */
/* Default to Search view on startup */
ad->view_mode = VIEW_SEARCH;
+ ad->search_offset = 0; // Initialize offset
+ ui_set_load_more_button_visibility(ad, EINA_FALSE); // Initially hide load more button
evas_object_show(ad->search_bar);
if (ad->stations)
- station_list_populate(ad, ad->stations);
+ station_list_populate(ad, ad->stations, EINA_TRUE);
else
station_list_clear(ad);
@@ -205,7 +215,7 @@ _tb_favorites_clicked_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_i
ad->view_mode = VIEW_FAVORITES;
evas_object_hide(ad->search_bar);
favorites_rebuild_station_list(ad);
- station_list_populate(ad, ad->favorites_stations);
+ station_list_populate(ad, ad->favorites_stations, EINA_TRUE);
}
static void
@@ -215,7 +225,25 @@ _tb_search_clicked_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info
ad->view_mode = VIEW_SEARCH;
evas_object_show(ad->search_bar);
if (ad->stations)
- station_list_populate(ad, ad->stations);
+ station_list_populate(ad, ad->stations, EINA_TRUE);
else
station_list_clear(ad);
}
+
+static void
+_load_more_btn_clicked_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
+{
+ AppData *ad = data;
+ const char *search_term = elm_object_text_get(ad->search_entry);
+ const char *search_type = elm_object_text_get(ad->search_hoversel);
+ ad->search_offset += 100; // Increment offset for next page
+ http_search_stations(ad, search_term, search_type, ad->search_offset, 100, EINA_FALSE);
+}
+
+void ui_set_load_more_button_visibility(AppData *ad, Eina_Bool visible)
+{
+ if (visible)
+ evas_object_show(ad->load_more_btn);
+ else
+ evas_object_hide(ad->load_more_btn);
+}
diff --git a/src/ui.h b/src/ui.h
index 84e40d1..c1765c0 100644
--- a/src/ui.h
+++ b/src/ui.h
@@ -6,3 +6,4 @@ typedef struct _AppData AppData;
void ui_create(AppData *ad);
void ui_update_server_list(AppData *ad);
+void ui_set_load_more_button_visibility(AppData *ad, Eina_Bool visible);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.