feat(ncm): 网易云客户端 v1 UI+播放链路 (agent-205) — os.meow.ncm: 登录页(真实unikey二维码+轮询状态机)/我的歌单(listview)/歌单详情/播放页(封面+seek+滚动歌词+竖横屏切换)/设置(音质默认Hi-res+缓存上限默认10G); 播放线程(缓存LRU+minimp3→ALSA UAPI直开hw:0,0); 媒体会话MSG_META上报+PLAYCTL接收(首真实媒体源)

ncm 1e331964 (text 764KB); 0 warning; 宿主离屏6页截图目检+真实QR可扫+qemu-mipsel干净启动+解码446xRT
真机待联调: ALSA时序/803扫码闭环/媒体会话联动/gbk大字库渲染
This commit is contained in:
MeowOS committed 2026-08-31 07:24:59 +08:00
1 parent 32543ac616
commit 21519ce2e4
23 files changed
+3255

No files matched your search

+104
View File
@@ -0,0 +1,104 @@
/* ui/common/chrome.c — os.meow.ncm 共享 chrome(TopAppBar 建置 / 几何 / 空态&页脚 / 底部提示条)。
*
* 与 files_app/ui/common/chrome.c 同理:把「页面 chrome 固定不随内容重建」分出去。绘制全部读
* meow_theme_get() 语义色(§8.1),不硬编码配色。topbar 由 meow_topappbar 建,自动注册进绘制物
* 注册表(§6.2),owner=创建时 active activity。
*/
#include "../ncm_app_internal.h"
#include <string.h>
int ncm_statusbar(void)
{
int sb = meow_ui_statusbar_height();
return sb < 0 ? 0 : sb;
}
ncm_rect_t ncm_topbar_rect(void)
{
int sb = ncm_statusbar();
return NR(0, sb, meow_ui_width(), APP_TOPBAR_H);
}
ncm_rect_t ncm_content_rect(void)
{
int sb = ncm_statusbar();
int y = sb + APP_TOPBAR_H;
int h = meow_ui_height() - y;
if (h < 0) h = 0;
return NR(0, y, meow_ui_width(), h);
}
meow_widget *ncm_topbar_create(const char *title, int back, const char *action_label)
{
ncm_rect_t r = ncm_topbar_rect();
meow_widget *tb = meow_topappbar_create(r.x, r.y, r.w, r.h, title);
meow_topappbar_set_back(tb, back);
if (action_label) meow_topappbar_add_action(tb, action_label);
meow_widget_set_z(tb, 999); /* chrome 置顶(§6.2:> 一切自动 z) */
return tb;
}
/* 空态/错误:居中两行(kind 大字 + what 小字)。 */
void ncm_draw_empty(ncm_rect_t r, const char *kind, const char *what)
{
const meow_theme *th = meow_theme_get();
int cy = r.y + r.h / 2;
meow_ui_text_center(r.x + r.w / 2, cy - 34, kind ? kind : "", 28, th->primary);
if (what && what[0])
meow_ui_text_center(r.x + r.w / 2, cy + 4, what, 20, th->dim);
}
/* 底部信息(右对齐 dim)。 */
void ncm_draw_footer(ncm_rect_t r, const char *text)
{
const meow_theme *th = meow_theme_get();
if (!text || !text[0]) return;
meow_ui_text_right(r.x + r.w - APP_PAD, r.y + r.h - 26, text, 20, th->dim);
}
/* 底部提示条(非阻塞):画在内容区底部之上,2s 后由 tick 清除。
* 只画一次并标脏;g_snack_until 为过期时间(0=无)。 */
static char s_snack[192];
static uint64_t s_snack_until; /* monotonic ms 过期时刻 */
static ncm_rect_t s_snack_r;
static int s_snack_id; /* <0=未注册 */
static void snack_draw(void *ctx)
{
(void)ctx;
if (meow_ui_time_ms() >= s_snack_until && s_snack_until != 0) {
meow_ui_invalidate(s_snack_r.x, s_snack_r.y, s_snack_r.w, s_snack_r.h);
s_snack_until = 0;
return;
}
const meow_theme *th = meow_theme_get();
/* 深色底 + 白字 */
meow_ui_fill_rect(s_snack_r.x, s_snack_r.y, s_snack_r.w, s_snack_r.h, th->inverseSurface);
meow_ui_text_center(s_snack_r.x + s_snack_r.w / 2, s_snack_r.y + (s_snack_r.h - 26) / 2 + 4,
s_snack, 20, th->inverseOnSurface);
}
void ncm_snack(const char *text)
{
if (!text) return;
strncpy(s_snack, text, sizeof s_snack - 1);
s_snack[sizeof s_snack - 1] = 0;
s_snack_until = meow_ui_time_ms() + 2000;
int w = meow_ui_width() - 2 * APP_PAD;
s_snack_r = NR(APP_PAD, meow_ui_height() - 56, w, 40);
if (s_snack_id < 0)
s_snack_id = meow_draw_custom_register(999, s_snack_r.x, s_snack_r.y, s_snack_r.w,
s_snack_r.h, snack_draw, NULL);
else
meow_draw_set_z(s_snack_id, 999);
meow_ui_invalidate(s_snack_r.x, s_snack_r.y, s_snack_r.w, s_snack_r.h);
}
/* 周期检查:snack 到期清除。可由各页 tick 调。 */
void chrome_tick(void)
{
if (s_snack_until && meow_ui_time_ms() >= s_snack_until) {
meow_ui_invalidate(s_snack_r.x, s_snack_r.y, s_snack_r.w, s_snack_r.h);
s_snack_until = 0;
}
}
+220
View File
@@ -0,0 +1,220 @@
/* 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
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);
}
+215
View File
@@ -0,0 +1,215 @@
/* ui/login.c — 扫码登录页(ACT_LOGIN,根页)。
*
* 流程:进入即起后台 worker(login_worker):
* unikey(eapi) → 本地二维码矩阵(qrcodegen) → 轮询(801/802/803) → 803 捕获 set-cookie 加密落盘
* → login_status 拿 uid/nickname → 置 g_login_done。
* UI 线程只在 tick 读 g_st.qr 状态与 login_state 重绘并检查 g_login_done → 切入 home。
* 800(过期)自动刷新二维码。UI 不卡:网络全在 worker 线程。
*/
#include "../ncm_app_internal.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
static pthread_t g_thread;
static volatile int g_polling; /* worker 运行中 */
static volatile int g_done; /* 登录成功(UI tick 切入 home) */
static volatile int g_err; /* 最后一次错误(网络不可达等) */
static int g_active;
static int g_custom_id; /* 绘制物 */
static ncm_rect_t r_cont;
/* ---- 绘制 ---- */
static void login_draw(void *ctx)
{
(void)ctx;
if (meow_ui_activity_active() != ACT_LOGIN) return;
const meow_theme *th = meow_theme_get();
/* 标题 */
meow_ui_text_center(r_cont.x + r_cont.w/2, r_cont.y + 24, "NetEase Cloud Music", 28, th->fg);
meow_ui_text_center(r_cont.x + r_cont.w/2, r_cont.y + 64, "scan to sign in", 20, th->dim);
/* QR 位图 */
int sz = g_st.qr_size;
if (sz > 0) {
int qs = sz * 6; /* 每模块 6px → 41x41=246,33x33=198 */
if (qs > 320) qs = sz * 4;
int ox = r_cont.x + (r_cont.w - qs) / 2;
int oy = r_cont.y + 108;
/* 白色底 + 边框 */
meow_ui_fill_rect(ox - 8, oy - 8, qs + 16, qs + 16, 0xFFFFFF);
meow_ui_draw_rect(ox - 8, oy - 8, qs + 16, qs + 16, th->outline);
for (int y = 0; y < sz; y++)
for (int x = 0; x < sz; x++)
if (g_st.qr_mods[y*sz + x])
meow_ui_fill_rect(ox + x*6, oy + y*6, 6, 6, 0x000000);
} else {
meow_ui_text_center(r_cont.x + r_cont.w/2, r_cont.y + 180,
g_err ? "no network - tap to retry" : "fetching QR key...", 20, th->dim);
}
/* 状态文字 */
const char *st = login_status_str(g_st.login_state);
if (g_st.login_state == NCM_LOGIN_OK && g_done) st = "success";
meow_ui_text_center(r_cont.x + r_cont.w/2, r_cont.y + 108 + 320 + 44,
st, 20, (g_st.login_state == NCM_LOGIN_OK) ? th->primary : th->dim);
}
/* 重绘 login 内容(QR+状态变化时)。 */
static void login_repaint(void)
{
meow_ui_invalidate(r_cont.x, r_cont.y, r_cont.w, r_cont.h);
}
const char *login_status_str(int state)
{
switch (state) {
case NCM_LOGIN_PENDING: return "waiting for scan...";
case NCM_LOGIN_SCANNED: return "scanned - confirm on phone";
case NCM_LOGIN_OK: return "signed in!";
case NCM_LOGIN_EXPIRED: return "QR expired - refreshing";
default: return "error, tap to retry";
}
}
static int get_unikey_and_qr(char *unikey, size_t usz)
{
int rc = ncm_login_unikey(g_st.ctx.device_id, unikey, usz);
if (rc != NCM_OK) return rc;
ncm_strlcpy(g_st.unikey, unikey, sizeof g_st.unikey);
ncm_login_qr_url(unikey, g_st.qr_url, sizeof g_st.qr_url);
int size = 0;
if (ncm_login_qr_matrix(g_st.qr_url, g_st.qr_mods, sizeof g_st.qr_mods / 177, &size) != NCM_OK)
g_st.qr_size = 0;
else
g_st.qr_size = size;
g_st.login_state = NCM_LOGIN_PENDING;
g_err = 0;
return NCM_OK;
}
static void *login_worker(void *arg)
{
(void)arg;
char unikey[64];
if (get_unikey_and_qr(unikey, sizeof unikey) != NCM_OK) { g_err = 1; return NULL; }
char cookie[NCM_COOKIE_MAX];
ncm_strlcpy(cookie, g_st.ctx.cookie, sizeof cookie);
while (!g_app_quit) {
ncm_login_poll_t r;
char out_cookie[NCM_COOKIE_MAX];
int rc = ncm_login_poll(g_st.ctx.device_id, unikey, cookie, &r,
out_cookie, sizeof out_cookie);
if (rc != NCM_OK) { g_err = 1; usleep(2 * 1000 * 1000); continue; }
g_st.login_state = r.state;
if (r.state == NCM_LOGIN_OK) {
ncm_strlcpy(cookie, out_cookie, sizeof cookie);
ncm_ctx_set_cookie(&g_st.ctx, cookie);
/* 拿 uid/nickname */
char uid[32], nick[128];
if (ncm_login_status_check(g_st.ctx.device_id, cookie, uid, sizeof uid,
nick, sizeof nick) == NCM_OK)
appstate_set_logged_in(uid, nick);
else
appstate_set_logged_in("", "");
g_st.logged_in = 1;
g_done = 1;
break;
}
if (r.state == NCM_LOGIN_EXPIRED) {
/* 刷新二维码 */
usleep(500 * 1000);
get_unikey_and_qr(unikey, sizeof unikey);
continue;
}
usleep(1500 * 1000);
}
return NULL;
}
int login_start(void)
{
if (g_polling) return 0;
g_polling = 1;
g_done = 0;
g_err = 0;
if (pthread_create(&g_thread, NULL, login_worker, NULL) != 0) {
g_polling = 0;
return -1;
}
pthread_detach(g_thread);
return 0;
}
int login_is_active(void) { return g_active; }
void login_on_tap(int x, int y)
{
(void)x; (void)y;
if (g_st.login_state == NCM_LOGIN_OK && g_done) return;
if (g_err) { login_start(); login_repaint(); } /* 网络失败重试 */
}
/* UI tick:重绘 QR/状态变化;登录成功 → 切 home。 */
void login_tick(uint64_t now)
{
(void)now;
chrome_tick();
static int last_state = -1, last_qr = 0;
if (g_st.login_state != last_state || g_st.qr_size != last_qr) {
last_state = g_st.login_state;
last_qr = g_st.qr_size;
login_repaint();
}
if (g_done)
meow_view_page_replace(ACT_HOME);
}
/* ---- 生命周期 ---- */
static void login_page_create(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
r_cont = ncm_content_rect();
g_custom_id = meow_draw_custom_register(1, r_cont.x, r_cont.y, r_cont.w, r_cont.h,
login_draw, NULL);
g_active = 0;
}
static void login_page_enter(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
g_active = 1;
const meow_theme *th = meow_theme_get();
meow_ui_set_page_bg(th->bg);
meow_ui_paint_full();
if (!g_host_mock) login_start(); /* 进入即获取二维码 + 轮询 */
}
static void login_page_leave(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
g_active = 0;
}
static void login_page_destroy(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
if (g_custom_id) { meow_draw_unregister(g_custom_id); g_custom_id = 0; }
}
meow_view *login_view_create(void)
{
static const meow_view_hooks h = {
.on_create = login_page_create,
.on_enter = login_page_enter,
.on_leave = login_page_leave,
.on_destroy = login_page_destroy,
};
return meow_view_create(ACT_LOGIN, MEOW_VIEW_ACTIVITY, &h, NULL);
}
+300
View File
@@ -0,0 +1,300 @@
/* ui/player.c — 播放页(ACT_PLAYER)。
*
* 布局:封面(后台 cover 线程解码) + 标题/歌手 + 进度条(可拖 seek) + 播放/暂停/上下曲 + 滚动歌词
* (时间轴解析,当前行高亮居中)。播放页横竖屏可切(device LCD 为竖屏 480x720,故为**布局**切换:
* 竖屏=封面在上、歌词在下;横屏=封面在左、信息+歌词在右,仍在 480x720 画布内)。
* 进度/状态每 tick 读 play_manager(跨线程 mu 保护),歌词行由 pos_ms 推出。
* 全部内容画进一个 custom 绘制物;控件钮/进度条/歌词命中用 player_draw 计算的共享几何。
*/
#include "../ncm_app_internal.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
typedef struct { meow_view *view; int active; } player_ctrl;
static player_ctrl g_plc;
static meow_widget *g_tb;
static ncm_rect_t r_cont;
static int g_custom_id;
static int g_landscape; /* 0=竖屏 1=横屏(布局) */
static int g_last_pos_ms, g_last_state, g_last_line;
/* 共享几何(player_draw 计算 → player_on_tap 命中用) */
static ncm_rect_t g_cov; /* 封面 */
static int g_bar_x, g_bar_y, g_bar_w;
static int g_cprev_x, g_cplay_x, g_cnext_x, g_cby, g_cr;
static int g_lyr_x, g_lyr_y, g_lyr_w, g_lyr_h; /* 歌词区 */
#define CTL_RR 28 /* 侧钮命中半径 */
static int cur_song(void)
{
int c = player_manager_playing_song();
return (c >= 0 && c < g_st.song_count) ? c : -1;
}
static void fmt_ms(int64_t ms, char *out, size_t sz)
{
if (ms < 0) ms = 0;
int sec = (int)(ms / 1000);
snprintf(out, sz, "%d:%02d", sec / 60, sec % 60);
}
/* 实心三角近似:right=向右指(顶点在右),left=向左指(顶点在左)。 */
static void tri(int x0, int y0, int h, int w, int dir, uint32_t color)
{
for (int i = 0; i < h; i++) {
int wi = 1 + i * w / h;
if (dir == 1) meow_ui_fill_rect(x0, y0 + i, wi, 1, color);
else meow_ui_fill_rect(x0 - wi + 1, y0 + i, wi, 1, color);
}
}
/* 圆形按钮 + 图标。kind: 0=|<<(prev) 1=>play 2=||pause 3=>>|(next)。 */
static void draw_ctl(int cx, int cy, int r, int kind, uint32_t base, uint32_t fg)
{
for (int i = 0; i < 2 * r; i++) {
int dy = i - r;
int half = (int)sqrt((double)(r * r - dy * dy));
if (half <= 0) continue;
meow_ui_fill_rect(cx - half, cy - r + i, 2 * half, 1, base);
}
int t = r / 2;
if (kind == 0) { /* |<< */
meow_ui_fill_rect(cx - r - 2, cy - t, 4, 2*t, fg);
tri(cx + r - 4, cy - t, 2*t, 2*t, -1, fg);
} else if (kind == 1) { /* > */
tri(cx - t, cy - t, 2*t, 2*t, 1, fg);
} else if (kind == 2) { /* || */
meow_ui_fill_rect(cx - t - 2, cy - t, 4, 2*t, fg);
meow_ui_fill_rect(cx + t - 2, cy - t, 4, 2*t, fg);
} else { /* >>| */
tri(cx - r + 4, cy - t, 2*t, 2*t, 1, fg);
meow_ui_fill_rect(cx + r - 2, cy - t, 4, 2*t, fg);
}
}
static void player_draw(void *ctx)
{
(void)ctx;
if (meow_ui_activity_active() != ACT_PLAYER) return;
const meow_theme *th = meow_theme_get();
int cx = r_cont.x + r_cont.w / 2;
int sc = cur_song();
const ncm_song_t *s = (sc >= 0) ? &g_st.songs[sc] : NULL;
/* 封面区域 */
int covsz = 220;
if (g_landscape) g_cov = NR(r_cont.x + 24, r_cont.y + 60, covsz, covsz);
else g_cov = NR(cx - covsz/2, r_cont.y + 10, covsz, covsz);
meow_ui_fill_rect(g_cov.x, g_cov.y, g_cov.w, g_cov.h, th->surfaceContainerHigh);
meow_ui_draw_rect(g_cov.x, g_cov.y, g_cov.w, g_cov.h, th->outlineVariant);
meow_img *cov = cover_get();
if (cov) meow_ui_img_cover(g_cov.x, g_cov.y, covsz, cov);
else meow_ui_text_center(g_cov.x + g_cov.w/2, g_cov.y + g_cov.h/2 - 10, "no artwork", 20, th->dim);
int info_x = (g_landscape) ? g_cov.x + g_cov.w + 28 : cx;
int info_w = (g_landscape) ? (r_cont.x + r_cont.w - 16 - info_x) : r_cont.w;
int ty = (g_landscape) ? g_cov.y + 8 : g_cov.y + g_cov.h + 8;
meow_ui_text_center(info_x, ty, s ? s->name : "nothing playing", 28, th->fg);
meow_ui_text_center(info_x, ty + 34, s ? s->art_name : "", 20, th->dim);
/* 进度条 + 时间 */
g_bar_x = (g_landscape) ? info_x - info_w/2 : 32;
g_bar_w = (g_landscape) ? info_w - 24 : r_cont.w - 64;
g_bar_y = ty + 78;
int pos = (int)player_manager_pos_ms();
int dur = (int)player_manager_dur_ms();
if (dur <= 0 && s) dur = (int)(s->duration_ms);
int frac = dur > 0 ? (int)((int64_t)pos * 1000 / dur) : 0;
if (frac < 0) frac = 0; if (frac > 1000) frac = 1000;
char lp[16], rp[16];
fmt_ms(pos, lp, sizeof lp);
fmt_ms(dur, rp, sizeof rp);
meow_ui_text(g_bar_x, g_bar_y - 20, lp, 20, th->dim);
meow_ui_text_right(g_bar_x + g_bar_w, g_bar_y - 20, rp, 20, th->dim);
meow_ui_fill_rect(g_bar_x, g_bar_y, g_bar_w, 4, th->surfaceVariant);
meow_ui_fill_rect(g_bar_x, g_bar_y, (int)((int64_t)g_bar_w * frac / 1000), 4, th->primary);
/* 控制钮 */
g_cby = g_bar_y + 40;
int cw = 56, gap = (g_landscape) ? 34 : 26;
g_cr = cw/2 + 4;
g_cprev_x = info_x - cw - gap/2 - 4;
g_cplay_x = info_x;
g_cnext_x = info_x + cw + gap/2 + 4;
draw_ctl(g_cprev_x, g_cby, cw/2, 0, th->secondaryContainer, th->onSecondaryContainer);
int st = player_manager_state();
draw_ctl(g_cplay_x, g_cby, g_cr, (st == NCM_PC_PAUSED) ? 1 : 2, th->primary, th->onPrimary);
draw_ctl(g_cnext_x, g_cby, cw/2, 3, th->secondaryContainer, th->onSecondaryContainer);
/* 歌词区 */
if (g_landscape) {
g_lyr_x = r_cont.x + 24;
g_lyr_y = g_cov.y + g_cov.h + 12;
g_lyr_w = r_cont.w - 48;
g_lyr_h = r_cont.y + r_cont.h - g_lyr_y;
} else {
g_lyr_x = r_cont.x;
g_lyr_y = g_cby + g_cr + 10;
g_lyr_w = r_cont.w;
g_lyr_h = r_cont.y + r_cont.h - g_lyr_y;
}
if (g_lyr_h < 0) g_lyr_h = 0;
/* 背景(歌词区隔断) */
meow_ui_fill_rect(g_lyr_x, g_lyr_y, g_lyr_w, 1, th->surfaceVariant);
int n = lyric_count();
int line_h = 28;
int center_y = g_lyr_y + (g_lyr_h > line_h * 11 ? g_lyr_h / 2 : 140);
int lcx = g_lyr_x + g_lyr_w / 2;
if (n == 0) {
meow_ui_text_center(lcx, center_y - 10, "no lyric", 20, th->dim);
return;
}
int64_t ps = player_manager_pos_ms();
int cur2 = lyric_line_at(ps);
for (int i = cur2 - 6; i <= cur2 + 5; i++) {
if (i < 0 || i >= n) continue;
int yy = center_y + (i - cur2) * line_h;
int is_cur = (i == cur2);
meow_ui_text_center(lcx, yy, lyric_line_text(i), is_cur ? 24 : 20,
is_cur ? th->primary : th->dim);
const char *tl = lyric_line_tl(i);
if (is_cur && tl && tl[0])
meow_ui_text_center(lcx, yy + 24, tl, 20, th->secondary);
}
}
int player_is_active(void) { return g_plc.active; }
int player_screen(void) { return g_landscape; }
int player_set_screen(int landscape)
{
int old = g_landscape;
g_landscape = landscape ? 1 : 0;
meow_ui_invalidate(r_cont.x, r_cont.y, r_cont.w, r_cont.h);
return old;
}
void player_on_tap(int x, int y)
{
if (g_tb) {
int ah = meow_topappbar_hit(g_tb, x, y);
if (ah == MEOW_TOPBAR_BACK) { meow_view_page_pop(); return; }
if (ah >= 0) { player_set_screen(!g_landscape); return; } /* ⟳ 布局切换 */
}
if (g_bar_w <= 0) return;
/* 控制钮 */
int r = g_cr;
if (x >= g_cplay_x - r && x <= g_cplay_x + r && y >= g_cby - r && y <= g_cby + r) {
if (player_manager_state() == NCM_PC_PAUSED) player_manager_pause(0);
else player_manager_pause(1);
return;
}
if (x >= g_cprev_x - CTL_RR && x <= g_cprev_x + CTL_RR &&
y >= g_cby - CTL_RR && y <= g_cby + CTL_RR) { player_manager_next(-1); return; }
if (x >= g_cnext_x - CTL_RR && x <= g_cnext_x + CTL_RR &&
y >= g_cby - CTL_RR && y <= g_cby + CTL_RR) { player_manager_next(1); return; }
/* 进度条 seek */
if (y >= g_bar_y - 8 && y <= g_bar_y + 12 && x >= g_bar_x && x <= g_bar_x + g_bar_w) {
int64_t dur = player_manager_dur_ms();
if (dur > 0) player_seek((int64_t)(x - g_bar_x) * dur / g_bar_w);
return;
}
}
void player_play_song(int idx)
{
if (idx < 0 || idx >= g_st.song_count) return;
player_manager_play(idx);
appstate_set_playing_song(idx, g_st.cover_url);
cover_request(g_st.cover_url);
meow_view_page_push(ACT_PLAYER);
}
void player_pause_toggle(int on)
{
if (player_manager_state() == NCM_PC_PAUSED) player_manager_pause(0);
else player_manager_pause(1);
(void)on;
}
void player_next(int dir) { player_manager_next(dir); }
void player_seek(int64_t ms) { player_manager_seek(ms); }
void player_on_swipe(int dir, int dist) { (void)dir; (void)dist; }
void player_tick(uint64_t now)
{
(void)now;
chrome_tick();
if (!g_plc.active) return;
int pos = (int)player_manager_pos_ms();
int st = player_manager_state();
int line = (lyric_count() > 0) ? lyric_line_at(pos) : -1;
if (pos != g_last_pos_ms || st != g_last_state) {
g_last_pos_ms = pos;
g_last_state = st;
meow_ui_invalidate(r_cont.x, r_cont.y, r_cont.w, r_cont.h);
} else if (line != g_last_line) {
g_last_line = line;
meow_ui_invalidate(g_lyr_x, g_lyr_y, g_lyr_w, g_lyr_h);
}
if (cover_changed()) {
cover_ack();
meow_ui_invalidate(g_cov.x, g_cov.y, g_cov.w, g_cov.h);
}
}
static void plr_page_create(meow_view *v, void *ctx)
{
(void)v;
((player_ctrl *)ctx)->view = v;
r_cont = ncm_content_rect();
meow_ui_activity_push(ACT_PLAYER); /* owner=ACT_PLAYER */
g_tb = ncm_topbar_create("Now Playing", 1, "\xe2\x86\xbb"); /* 返回 + ⟳ 布局 */
meow_ui_activity_pop();
g_custom_id = meow_draw_custom_register(1, r_cont.x, r_cont.y, r_cont.w, r_cont.h,
player_draw, NULL);
g_last_pos_ms = -1; g_last_state = -1; g_last_line = -1;
}
static void plr_page_enter(meow_view *v, void *ctx)
{
(void)v;
((player_ctrl *)ctx)->active = 1;
const meow_theme *th = meow_theme_get();
meow_ui_set_page_bg(th->bg);
meow_ui_paint_full();
if (!g_host_mock) player_manager_load_lyric();
}
static void plr_page_leave(meow_view *v, void *ctx)
{
(void)v;
((player_ctrl *)ctx)->active = 0;
}
static void plr_page_destroy(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
if (g_custom_id) { meow_draw_unregister(g_custom_id); g_custom_id = 0; }
if (g_tb) { meow_widget_free(g_tb); g_tb = NULL; }
}
meow_view *player_view_create(void)
{
static const meow_view_hooks h = {
.on_create = plr_page_create,
.on_enter = plr_page_enter,
.on_leave = plr_page_leave,
.on_destroy = plr_page_destroy,
};
return meow_view_create(ACT_PLAYER, MEOW_VIEW_ACTIVITY, &h, &g_plc);
}
+216
View File
@@ -0,0 +1,216 @@
/* ui/playlist.c — 歌单详情页(ACT_PLAYLIST,曲目列表)。
*
* 曲目用 SDK 结构化列表(meow_listview)虚拟化渲染「歌名 + 歌手 副标题」。点击行 → 起播并推播放页。
* 进入时拉歌单详情(trackIds)+ 分批 song_detail(有界 NCM_SONG_MAX)。空态/错误用绘制物。
* 左缘返回 = 弹回主页(app.c)。
*/
#include "../ncm_app_internal.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct { meow_view *view; int active; } playlist_ctrl;
static playlist_ctrl g_pc;
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[256];
static int g_load_err;
static int g_press_row, g_press_delta, g_dragging, g_drag_last_y;
static void pl_drag_reset(void)
{
g_press_row = -1; g_press_delta = 0; g_dragging = 0; g_drag_last_y = 0;
}
static int pl_src2(int idx, meow_list_item *out, void *ctx)
{
(void)ctx;
if (idx < 0 || idx >= g_st.song_count) return 0;
ncm_song_t *s = &g_st.songs[idx];
out->icon_kind = MEOW_LIST_ICON_NONE;
out->title = s->name;
out->subtitle = (s->art_name[0] || s->album_name[0])
? (s->art_name[0] ? s->art_name : s->album_name) : "-";
out->right = MEOW_LIST_RIGHT_ARROW;
out->on = 0;
out->enabled = 1;
return 1;
}
static void pl_draw_empty(void *ctx)
{
(void)ctx;
if (meow_ui_activity_active() != ACT_PLAYLIST) return;
if (g_load_err == 0 && g_st.song_count != 0) return;
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, "LOAD FAILED", "tap back and retry");
else
ncm_draw_empty(r_list, "NO TRACKS", "this playlist is empty");
}
static void pl_draw_footer(void *ctx)
{
(void)ctx;
if (meow_ui_activity_active() != ACT_PLAYLIST) return;
if (g_load_err != 0) return;
char foot[96];
snprintf(foot, sizeof foot, "%d/%d tracks",
g_st.song_count,
(g_st.playlist_count > 0 && g_st.active_playlist >= 0)
? g_st.playlists[g_st.active_playlist].track_count : g_st.song_count);
ncm_draw_footer(r_cont, foot);
}
void playlist_refresh(void)
{
g_load_err = 0;
int rc = NCM_ERR_ARG;
if (!g_host_mock && g_st.active_playlist >= 0)
rc = appstate_load_playlist_songs(g_st.active_playlist);
else if (g_host_mock)
rc = 0;
g_load_err = rc;
if (g_load_err != 0) g_st.song_count = 0;
if (g_list) {
meow_listview_set_count(g_list, g_st.song_count);
meow_listview_set_scroll(g_list, 0);
meow_listview_set_sel(g_list, -1);
}
if (g_tb && g_st.active_playlist >= 0 && g_st.active_playlist < g_st.playlist_count) {
snprintf(g_title, sizeof g_title, "%s", g_st.playlists[g_st.active_playlist].name);
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();
}
void playlist_open(int idx)
{
if (idx < 0 || idx >= g_st.playlist_count) return;
g_st.active_playlist = idx;
g_st.playing_song = -1;
cover_cache_reset();
meow_view_page_push(ACT_PLAYLIST);
playlist_refresh(); /* page_enter 也会调用(幂等);这里先拉避免空帧 */
}
int playlist_is_active(void) { return g_pc.active; }
int playlist_on_tap(int x, int y)
{
if (g_tb) {
int ah = meow_topappbar_hit(g_tb, x, y);
if (ah == MEOW_TOPBAR_BACK) { pl_drag_reset(); meow_view_page_pop(); return 1; }
if (ah >= 0) { pl_drag_reset(); playlist_refresh(); return 1; }
}
if (g_load_err != 0 || g_st.song_count == 0) return 0;
if (meow_widget_hit(g_list, x, y)) {
int row = meow_listview_hit(g_list, x, y);
if (row < 0) { pl_drag_reset(); return 1; }
pl_drag_reset();
player_play_song(row); /* 起播并推播放页 */
return 1;
}
return 0;
}
void playlist_touch_down(int x, int y)
{
pl_drag_reset();
if (g_load_err != 0 || g_st.song_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 playlist_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 playlist_swipe(int dir, int dist)
{
if (dir != MEOW_SWIPE_UP && dir != MEOW_SWIPE_DOWN) return;
if (g_load_err != 0 || g_st.song_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 pl_page_create(meow_view *v, void *ctx)
{
(void)v;
((playlist_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_PLAYLIST); /* owner=ACT_PLAYLIST */
g_tb = ncm_topbar_create("Playlist", 1, NULL);
g_list = meow_listview_create(r_list.x, r_list.y, r_list.w, r_list.h, 0,
pl_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,
pl_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, pl_draw_footer, NULL);
pl_drag_reset();
}
static void pl_page_enter(meow_view *v, void *ctx)
{
(void)v;
((playlist_ctrl *)ctx)->active = 1;
const meow_theme *th = meow_theme_get();
meow_ui_set_page_bg(th->bg);
playlist_refresh();
}
static void pl_page_leave(meow_view *v, void *ctx)
{
(void)v;
((playlist_ctrl *)ctx)->active = 0;
}
static void pl_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 *playlist_view_create(void)
{
static const meow_view_hooks h = {
.on_create = pl_page_create,
.on_enter = pl_page_enter,
.on_leave = pl_page_leave,
.on_destroy = pl_page_destroy,
};
return meow_view_create(ACT_PLAYLIST, MEOW_VIEW_ACTIVITY, &h, &g_pc);
}
+193
View File
@@ -0,0 +1,193 @@
/* ui/settings.c — 设置页(ACT_SETTINGS)。
*
* 行项:音质(Hi-res/较高/标准,默认 Hi-res)+ 缓存上限(1~100GB,默认 10GB)+ 退出登录。
* 设置持久化到 MeowData/os.meow.ncm/data(meow_config_*;缓存上限键 ncm.cache_limit_gb 与核心库
* ncm_cache_load_setting 对齐)。音质/上限经 BottomSheet 选择;退出登录经 confirm 弹窗。
* 空态/绘制用 custom 绘制物 + 手动行命中(仅 3 行,无需列表滚动)。
*/
#include "../ncm_app_internal.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct { meow_view *view; int active; } settings_ctrl;
static settings_ctrl g_sc;
static meow_widget *g_tb;
static ncm_rect_t r_cont;
static int g_custom_id;
static int g_row_h; /* 行高 */
static const char *level_name(int lv)
{
switch (lv) {
case NCM_LEVEL_HIRES: return "Hi-Res";
case NCM_LEVEL_LOSSLESS:return "Lossless";
case NCM_LEVEL_EXHIGH: return "Exhigh";
case NCM_LEVEL_HIGHER: return "Higher";
default: return "Standard";
}
}
static void settings_draw(void *ctx)
{
(void)ctx;
if (meow_ui_activity_active() != ACT_SETTINGS) return;
const meow_theme *th = meow_theme_get();
g_row_h = 60;
int y = r_cont.y + 16;
int w = r_cont.w - 2 * APP_PAD;
int x = APP_PAD;
/* 行:音质 / 缓存上限 / 退出登录 */
struct { const char *title; char sub[48]; } rows[3];
rows[0].title = "Music Quality";
rows[1].title = "Cache Limit";
rows[2].title = "Sign Out";
snprintf(rows[0].sub, sizeof rows[0].sub, "current: %s", level_name(g_st.level));
snprintf(rows[1].sub, sizeof rows[1].sub, "current: %d GB", g_st.cache_limit_gb);
snprintf(rows[2].sub, sizeof rows[2].sub, "%s", g_st.nickname[0] ? g_st.nickname : "sign out");
for (int i = 0; i < 3; i++) {
int yy = y + i * (g_row_h + 8);
meow_ui_fill_rect(x, yy, w, g_row_h, th->surfaceContainer);
uint32_t tcol = (i == 2) ? th->error : th->fg;
uint32_t scol = (i == 2) ? th->error : th->dim;
meow_ui_text(x + APP_PAD, yy + 12, rows[i].title, 20, tcol);
meow_ui_text(x + APP_PAD, yy + 34, rows[i].sub, 20, scol);
/* 右侧箭头 (draw) */
meow_ui_text_right(x + w - APP_PAD, yy + 18, ">", 28, th->dim);
}
}
static int row_at(int x, int y)
{
(void)x;
int r = r_cont.y + 16;
for (int i = 0; i < 3; i++) {
int yy = r + i * (g_row_h + 8);
if (y >= yy && y <= yy + g_row_h) return i;
}
return -1;
}
int settings_is_active(void) { return g_sc.active; }
int settings_on_tap(int x, int y)
{
if (g_tb) {
int ah = meow_topappbar_hit(g_tb, x, y);
if (ah == MEOW_TOPBAR_BACK) { meow_view_page_pop(); return 1; }
if (ah >= 0) { return 1; }
}
if (x < APP_PAD || x > r_cont.w - APP_PAD) return 0;
int row = row_at(x, y);
if (row < 0) return 0;
if (row == 0) { /* 音质 */
meow_sheet *s = meow_sheet_create("Music Quality", 0);
meow_sheet_add_item(s, "Hi-Res");
meow_sheet_add_item(s, "Higher");
meow_sheet_add_item(s, "Standard");
int op = meow_sheet_open(s);
meow_sheet_free(s);
if (op >= 0) {
g_st.level = (op == 0) ? NCM_LEVEL_HIRES : (op == 1) ? NCM_LEVEL_HIGHER : NCM_LEVEL_STANDARD;
player_manager_set_quality(g_st.level, g_st.cache_limit_gb);
appstate_save_settings();
meow_ui_invalidate(r_cont.x, r_cont.y, r_cont.w, r_cont.h);
}
return 1;
}
if (row == 1) { /* 缓存上限 */
static const int gbs[] = { 1, 2, 5, 10, 20, 30, 50, 100 };
static const char *labels[] = { "1 GB", "2 GB", "5 GB", "10 GB",
"20 GB", "30 GB", "50 GB", "100 GB" };
meow_sheet *s = meow_sheet_create("Cache Limit", 0);
for (int i = 0; i < 8; i++) meow_sheet_add_item(s, labels[i]);
int op = meow_sheet_open(s);
meow_sheet_free(s);
if (op >= 0 && op < 8) {
g_st.cache_limit_gb = gbs[op];
player_manager_set_quality(g_st.level, g_st.cache_limit_gb);
appstate_save_settings();
meow_ui_invalidate(r_cont.x, r_cont.y, r_cont.w, r_cont.h);
}
return 1;
}
if (row == 2) { /* 退出登录 */
meow_dialog *d = meow_dialog_create(MEOW_DLG_CONFIRM, "Sign out?");
const char *msgs[1];
msgs[0] = "Playback will stop.\nYou'll need to scan a QR code again.";
meow_dialog_set_items(d, msgs, 1, -1);
int yes = meow_dialog_confirm(d);
meow_dialog_free(d);
if (yes) {
appstate_logout();
meow_view_page_replace(ACT_LOGIN);
}
return 1;
}
return 0;
}
void settings_swipe(int dir, int dist) { (void)dir; (void)dist; }
void settings_touch_down(int x, int y) { (void)x; (void)y; }
void settings_touch_move(int x, int y) { (void)x; (void)y; }
void settings_open(void)
{
g_st.playing_song = -1;
meow_view_page_push(ACT_SETTINGS);
}
void settings_reload(void)
{
const meow_theme *th = meow_theme_get();
meow_ui_set_page_bg(th->bg);
meow_ui_paint_full();
}
static void st_page_create(meow_view *v, void *ctx)
{
(void)v;
((settings_ctrl *)ctx)->view = v;
r_cont = ncm_content_rect();
meow_ui_activity_push(ACT_SETTINGS); /* owner=ACT_SETTINGS */
g_tb = ncm_topbar_create("Settings", 1, NULL);
meow_ui_activity_pop();
g_row_h = 60;
g_custom_id = meow_draw_custom_register(1, r_cont.x, r_cont.y, r_cont.w, r_cont.h,
settings_draw, NULL);
}
static void st_page_enter(meow_view *v, void *ctx)
{
(void)v;
((settings_ctrl *)ctx)->active = 1;
settings_reload();
}
static void st_page_leave(meow_view *v, void *ctx)
{
(void)v;
((settings_ctrl *)ctx)->active = 0;
}
static void st_page_destroy(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
if (g_custom_id) { meow_draw_unregister(g_custom_id); g_custom_id = 0; }
if (g_tb) { meow_widget_free(g_tb); g_tb = NULL; }
}
meow_view *settings_view_create(void)
{
static const meow_view_hooks h = {
.on_create = st_page_create,
.on_enter = st_page_enter,
.on_leave = st_page_leave,
.on_destroy = st_page_destroy,
};
return meow_view_create(ACT_SETTINGS, MEOW_VIEW_ACTIVITY, &h, &g_sc);
}