/* ui/home.c — 我的歌单页(ACT_HOME,根页)。 * * 内容区用 SDK 结构化列表(meow_listview,§5.5 W_LISTVIEW)虚拟化渲染 * 「图标(folder 占位) + 歌单名 + 曲目数副标题 + 右侧箭头」。行数据源逐行填 meow_list_item; * 滚动/选中/命中由 SDK 收口。空态/错误/底部信息行用 meow_draw_custom_register 注册非控件绘制物。 * 左缘返回 = 退出 App(app.c 处理)。下拉刷新可选:用 topbar 「↻」 动作项。 */ #include "../ncm_app_internal.h" #include #include #include typedef struct { meow_view *view; int active; } home_ctrl; static home_ctrl g_hc; static meow_widget *g_tb; static meow_widget *g_list; static ncm_rect_t r_cont, r_list; static int g_empty_id, g_footer_id; static char g_title[128]; static int g_load_err; /* 0=ok;负=错误码 */ static int g_press_row, g_press_delta, g_dragging, g_drag_last_y; static void home_drag_reset(void) { g_press_row = -1; g_press_delta = 0; g_dragging = 0; g_drag_last_y = 0; } static int home_src2(int idx, meow_list_item *out, void *ctx) { (void)ctx; if (idx < 0 || idx >= g_st.playlist_count) return 0; ncm_playlist_t *pl = &g_st.playlists[idx]; static char title[128]; /* 歌单名截断 */ static char sub[64]; snprintf(title, sizeof title, "%s", pl->name); snprintf(sub, sizeof sub, "%d tracks", pl->track_count); out->icon_kind = MEOW_LIST_ICON_FOLDER; out->title = title; out->subtitle = sub; out->right = MEOW_LIST_RIGHT_ARROW; out->on = 0; out->enabled = 1; return 1; } static void home_draw_empty(void *ctx) { (void)ctx; if (meow_ui_activity_active() != ACT_HOME) return; if (g_load_err == 0 && g_st.playlist_count != 0) return; if (g_st.logged_in == 0) ncm_draw_empty(r_list, "NOT SIGNED IN", "sign in first"); else if (g_load_err == NCM_ERR_AUTH) ncm_draw_empty(r_list, "SESSION EXPIRED", "please sign in again"); else if (g_load_err) ncm_draw_empty(r_list, "NETWORK ERROR", "pull to retry"); else ncm_draw_empty(r_list, "NO PLAYLISTS", "nothing here yet"); } static void home_draw_footer(void *ctx) { (void)ctx; if (meow_ui_activity_active() != ACT_HOME) return; if (g_load_err != 0) return; char foot[96]; if (g_st.playlist_count == 0) snprintf(foot, sizeof foot, "0 playlists"); else snprintf(foot, sizeof foot, "%d playlists %s", g_st.playlist_count, g_st.nickname[0] ? g_st.nickname : ""); ncm_draw_footer(r_cont, foot); } void home_reload(void) { g_load_err = 0; int rc = 0; if (g_host_mock) { /* 宿主/测试:跳过网络,用预置数据 */ rc = 0; } else if (!g_st.logged_in) rc = NCM_ERR_AUTH; else rc = appstate_load_playlists(); g_load_err = rc; if (g_load_err != 0) g_st.playlist_count = 0; if (g_list) { meow_listview_set_count(g_list, g_st.playlist_count); meow_listview_set_scroll(g_list, 0); meow_listview_set_sel(g_list, -1); } const char *name = g_st.nickname[0] ? g_st.nickname : "My Playlists"; snprintf(g_title, sizeof g_title, "%s", name); if (g_tb) meow_topappbar_set_title(g_tb, g_title); const meow_theme *th = meow_theme_get(); meow_ui_set_page_bg(th->bg); meow_ui_paint_full(); } int home_is_active(void) { return g_hc.active; } int home_on_tap(int x, int y) { if (g_tb) { int ah = meow_topappbar_hit(g_tb, x, y); if (ah == MEOW_TOPBAR_BACK) { home_drag_reset(); return 1; } if (ah >= 0) { home_drag_reset(); home_reload(); return 1; } /* ↻ 刷新 */ } if (g_load_err != 0 || g_st.playlist_count == 0) return 0; if (meow_widget_hit(g_list, x, y)) { int row = meow_listview_hit(g_list, x, y); if (row < 0) { home_drag_reset(); return 1; } home_drag_reset(); playlist_open(row); return 1; } return 0; } void home_touch_down(int x, int y) { home_drag_reset(); if (g_load_err != 0 || g_st.playlist_count == 0) return; if (meow_widget_hit(g_list, x, y)) { int row = meow_listview_hit(g_list, x, y); if (row >= 0) { g_press_row = row; meow_listview_set_sel(g_list, row); } g_dragging = 1; g_drag_last_y = y; meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h); } } void home_touch_move(int x, int y) { (void)x; if (!g_dragging) return; if (g_press_row >= 0) { int d = y - g_drag_last_y; if (d < 0) d = -d; g_press_delta += d; if (g_press_delta > 10) { g_press_row = -1; meow_listview_set_sel(g_list, -1); } } int dy = y - g_drag_last_y; g_drag_last_y = y; if (dy == 0) return; int before = meow_listview_scroll(g_list); meow_listview_scroll_by(g_list, -dy); if (meow_listview_scroll(g_list) != before) meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h); } void home_swipe(int dir, int dist) { if (dir != MEOW_SWIPE_UP && dir != MEOW_SWIPE_DOWN) return; if (g_load_err != 0 || g_st.playlist_count == 0) return; int del = (dir == MEOW_SWIPE_UP) ? dist : -dist; int before = meow_listview_scroll(g_list); meow_listview_scroll_by(g_list, del); if (meow_listview_scroll(g_list) != before) meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h); } /* 生命周期 */ static void home_page_create(meow_view *v, void *ctx) { (void)v; ((home_ctrl *)ctx)->view = v; r_cont = ncm_content_rect(); r_list = NR(r_cont.x, r_cont.y, r_cont.w, r_cont.h - APP_FOOTER_H); if (r_list.h < 0) r_list.h = 0; meow_ui_activity_push(ACT_HOME); /* 让下面控件 owner=ACT_HOME(§6.1 ①) */ g_tb = ncm_topbar_create("My Playlists", 0, "\xe2\x86\xbb"); /* 无返回;右侧 ↻ */ g_list = meow_listview_create(r_list.x, r_list.y, r_list.w, r_list.h, 0, home_src2, NULL, MEOW_LIST_SEL_SINGLE); meow_listview_set_item_h(g_list, APP_ROW_H); meow_ui_activity_pop(); g_empty_id = meow_draw_custom_register(1, r_list.x, r_list.y, r_list.w, r_list.h, home_draw_empty, NULL); g_footer_id = meow_draw_custom_register(1, r_cont.x, r_cont.y + r_cont.h - APP_FOOTER_H, r_cont.w, APP_FOOTER_H, home_draw_footer, NULL); home_drag_reset(); } static void home_page_enter(meow_view *v, void *ctx) { (void)v; ((home_ctrl *)ctx)->active = 1; const meow_theme *th = meow_theme_get(); meow_ui_set_page_bg(th->bg); home_reload(); } static void home_page_leave(meow_view *v, void *ctx) { (void)v; ((home_ctrl *)ctx)->active = 0; } static void home_page_destroy(meow_view *v, void *ctx) { (void)v; (void)ctx; if (g_empty_id) { meow_draw_unregister(g_empty_id); g_empty_id = 0; } if (g_footer_id) { meow_draw_unregister(g_footer_id); g_footer_id = 0; } if (g_list) { meow_widget_free(g_list); g_list = NULL; } if (g_tb) { meow_widget_free(g_tb); g_tb = NULL; } } meow_view *home_view_create(void) { static const meow_view_hooks h = { .on_create = home_page_create, .on_enter = home_page_enter, .on_leave = home_page_leave, .on_destroy = home_page_destroy, }; return meow_view_create(ACT_HOME, MEOW_VIEW_ACTIVITY, &h, &g_hc); }