ncm search: 历史+单曲/歌单分类+分页+误触修复+不覆写歌单上下文 (§23)
- 搜索历史:进页自动弹键盘;meow_config 持久化 ≤12 去重最新在前;空态=历史列表(点搜/左滑删/清空) - 结果分类单曲/歌单页签:独立结果集+独立滚动;歌单行进详情(playlist_open_pl);单曲行播放 - 触底分页:offset 累加 limit=30/页 cloudsearch 同参数;每类各记 offset - 误触抑制:g_press_row + g_press_delta>10(对齐 home/playlist §13.3) - 结果改独立数组(不写 g_st.songs[]);播放搜索单曲建搜索队列(queue_src=SEARCH),不弄丢歌单窗口 - core cloudsearch 扩展 type/offset/limit/total + 歌单解析 - bench/search_sim.c 离线回归(历史/独立集/翻页 offset/队列上下文);host searchtap 误触自检 验证:mipsel -Wall -Wextra 0 warning;core host/mipsel PASS;search_sim PASS; 宿主离屏 search(单曲)/searchpl(歌单)/searchhist(空态历史)/searchtap(误触) 均正常;0 真实 NCM 请求。
This commit is contained in:
1 parent
d4928224a5
commit
1e97dbf54a
15 files changed
+1152
-121
No files matched your search
@@ -71,7 +71,7 @@ static int g_lyr_meta_y; /* 顶部元信息 y */
|
||||
static int cur_song(void)
|
||||
{
|
||||
int c = player_manager_playing_song();
|
||||
return (c >= 0 && c < g_st.track_ids_count) ? c : -1;
|
||||
return (c >= 0 && c < appstate_queue_count()) ? c : -1; /* §23:按队列来源界 */
|
||||
}
|
||||
|
||||
/* 当前播放曲元数据(§17):优先播放快照(窗口无关);快照未就绪时退窗口拷贝。 */
|
||||
@@ -529,6 +529,7 @@ void player_on_swipe(int dir, int dist)
|
||||
void player_play_song(int idx)
|
||||
{
|
||||
if (idx < 0 || idx >= g_st.track_ids_count) return;
|
||||
g_st.queue_src = NCM_QUEUE_PLAYLIST; /* §23:歌单窗口队列 */
|
||||
/* 黑胶封面用真实专辑图(song_detail 的 al.picUrl,经 ncm_cache 缓存/限幅);
|
||||
* 滑窗未物化(占位行直点)时回落歌单封面,元数据由播放线程物化后快照接管(§17)。
|
||||
* §18:g_st.cover_url 只作「歌单封面兜底」。 */
|
||||
@@ -542,6 +543,22 @@ void player_play_song(int idx)
|
||||
meow_view_page_push(ACT_PLAYER);
|
||||
}
|
||||
|
||||
/* 搜索单曲起播(§23):走搜索队列(search_songs[]),不覆写歌单窗口;
|
||||
* 上/下一首在搜索结果集内循环,回退原歌单时窗口上下文不丢。 */
|
||||
void player_play_search_song(int i)
|
||||
{
|
||||
if (i < 0 || i >= g_st.search_song_count) return;
|
||||
g_st.queue_src = NCM_QUEUE_SEARCH;
|
||||
ncm_song_t s;
|
||||
if (appstate_search_song(i, &s) != 0) return;
|
||||
const char *cover = s.al_pic[0] ? s.al_pic : g_st.cover_url;
|
||||
player_manager_play(i);
|
||||
appstate_set_playing_song(i, NULL);
|
||||
cover_request(cover);
|
||||
g_ppage = 0;
|
||||
meow_view_page_push(ACT_PLAYER);
|
||||
}
|
||||
|
||||
void player_pause_toggle(int on)
|
||||
{
|
||||
if (player_manager_state() == NCM_PC_PAUSED) player_manager_pause(0);
|
||||
|
||||
@@ -44,6 +44,15 @@ static void pl_drag_reset(void)
|
||||
g_press_row = -1; g_press_delta = 0; g_dragging = 0; g_drag_last_y = 0;
|
||||
}
|
||||
|
||||
/* 当前打开的「歌单」:真实歌单(active_playlist 下标)或搜索来的歌单(NCM_PL_SEARCH 哨兵,§23)。 */
|
||||
static const ncm_playlist_t *pl_cur(void)
|
||||
{
|
||||
if (g_st.active_playlist == NCM_PL_SEARCH) return &g_st.search_pl_open;
|
||||
if (g_st.active_playlist >= 0 && g_st.active_playlist < g_st.playlist_count)
|
||||
return &g_st.playlists[g_st.active_playlist];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int pl_src2(int idx, meow_list_item *out, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
@@ -107,8 +116,7 @@ static void pl_draw(void *ctx)
|
||||
"\u266a", th->font_body, pa->red); /* ♪ */
|
||||
}
|
||||
cover_put();
|
||||
const ncm_playlist_t *pl = (g_st.active_playlist >= 0 && g_st.active_playlist < g_st.playlist_count)
|
||||
? &g_st.playlists[g_st.active_playlist] : NULL;
|
||||
const ncm_playlist_t *pl = pl_cur();
|
||||
const char *nm = pl ? pl->name : "歌单名";
|
||||
int nmx = r_cov.x + r_cov.w + 12;
|
||||
char meta[256];
|
||||
@@ -197,8 +205,9 @@ static void pl_apply(int reset_scroll)
|
||||
meow_listview_set_count(g_list, g_st.track_ids_count); /* 全量行数(占位行兜底未物化区) */
|
||||
if (reset_scroll) { 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);
|
||||
const ncm_playlist_t *pcur = pl_cur();
|
||||
if (g_tb && pcur) {
|
||||
snprintf(g_title, sizeof g_title, "%s", pcur->name);
|
||||
meow_topappbar_set_title(g_tb, g_title);
|
||||
}
|
||||
const meow_theme *th = meow_theme_get();
|
||||
@@ -292,6 +301,21 @@ void playlist_open(int idx)
|
||||
if (idx < 0 || idx >= g_st.playlist_count) return;
|
||||
g_st.active_playlist = idx;
|
||||
g_st.playing_song = -1;
|
||||
g_st.queue_src = NCM_QUEUE_PLAYLIST; /* §23:打开歌单 → 队列=歌单窗口 */
|
||||
cover_cache_reset();
|
||||
meow_view_page_push(ACT_PLAYLIST);
|
||||
playlist_refresh();
|
||||
}
|
||||
|
||||
/* 打开「搜索来的歌单」详情(§23):不污染真实歌单列表,复用本页/滑窗链路
|
||||
* (active_playlist=NCM_PL_SEARCH 哨兵,appstate_load_playlist_songs_page 读 search_pl_open)。 */
|
||||
void playlist_open_pl(const ncm_playlist_t *pl)
|
||||
{
|
||||
if (!pl || !pl->id[0]) return;
|
||||
g_st.search_pl_open = *pl;
|
||||
g_st.active_playlist = NCM_PL_SEARCH;
|
||||
g_st.playing_song = -1;
|
||||
g_st.queue_src = NCM_QUEUE_PLAYLIST;
|
||||
cover_cache_reset();
|
||||
meow_view_page_push(ACT_PLAYLIST);
|
||||
playlist_refresh();
|
||||
@@ -421,9 +445,9 @@ static void pl_page_enter(meow_view *v, void *ctx)
|
||||
meow_ui_set_page_bg(th->bg);
|
||||
/* §18:头块封面回填——封面槽全局单张(与播放页黑胶共享),进歌单页重新请求歌单封面
|
||||
* (旧版只在 playlist_open 里 cover_cache_reset 清掉,无人再请求 → 头块永远占位 ♪)。 */
|
||||
if (g_st.active_playlist >= 0 && g_st.active_playlist < g_st.playlist_count &&
|
||||
g_st.playlists[g_st.active_playlist].cover[0])
|
||||
cover_request(g_st.playlists[g_st.active_playlist].cover);
|
||||
const ncm_playlist_t *pcur = pl_cur();
|
||||
if (pcur && pcur->cover[0])
|
||||
cover_request(pcur->cover);
|
||||
playlist_refresh();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
/* ui/search.c — 搜索页(ACT_SEARCH,顶栏搜索按钮进入)。
|
||||
*
|
||||
* 独立页面:搜索输入框(SDK TextField,接 IME §5.1,仅英文/数字)+ 红色「搜索」按钮 +
|
||||
* 结果列表(点击可播,复用 g_st.songs[] 起点:播放/上下一首均对结果集生效)。
|
||||
* 搜索结果网络走后台 worker(search_worker),UI 不卡。
|
||||
* §23(REPORT):
|
||||
* 1. 搜索历史:进页自动弹键盘(meow_textfield_activate);历史本地持久化(meow_config,
|
||||
* 最近 ≤12 条、去重、最新在前);未输入时显示历史列表(点=直接搜、左滑删单条、清空全部)。
|
||||
* 2. 结果分类「单曲/歌单」:顶部小页签(单曲 type=1 / 歌单 type=1000);两页签各自独立
|
||||
* 结果集与滚动位置(g_scroll_song/g_scroll_pl);歌单行点击=playlist_open_pl 进详情。
|
||||
* 3. 加载更多:触底分页(offset 累加,NCM_SEARCH_PAGE=30/页,cloudsearch 同参数);
|
||||
* 每类各记各的 offset(appstate_search_offset 回填)。
|
||||
* 4. 修 search_on_tap 误触(NCM_UI_REVIEW §3-2):套用 g_press_row + g_press_delta>10 抑制
|
||||
* (对齐 home/playlist §13.3)。
|
||||
* 5. 结果不再覆写歌单上下文:单曲结果写 g_st.search_songs[](独立数组,appstate 持有),
|
||||
* 播放搜索单曲走 player_play_search_song(建立「搜索队列」上下文,queue_src=SEARCH),
|
||||
* 原歌单窗口(g_st.songs[]/track_ids_count)不被破坏。
|
||||
*
|
||||
* 风控纪律(用户指定):**搜索请求只在用户确认时发**——用户点「搜索」按钮才发起 cloudsearch,
|
||||
* 进入页面不自动搜索;IME 输入仅本地编辑,不逐键发请求。
|
||||
* 风控纪律(用户指定):**真实请求只在用户点「搜索」按钮/切页签/翻页时发**——进页不自动搜索,
|
||||
* IME 输入仅本地编辑,不逐键发请求。调试/测试全部离线 stub(g_host_mock)。
|
||||
*/
|
||||
#include "../ncm_app_internal.h"
|
||||
#include <stdio.h>
|
||||
@@ -16,10 +25,14 @@
|
||||
typedef struct { meow_view *view; int active; } search_ctrl;
|
||||
static search_ctrl g_sc;
|
||||
|
||||
/* 视图模式 */
|
||||
enum { SR_HIST = 0, SR_RES, SR_EMPTY };
|
||||
|
||||
static meow_widget *g_tb, *g_field, *g_list;
|
||||
static ncm_rect_t r_cont, r_list, r_btn;
|
||||
static int g_empty_id, g_footer_id, g_btn_id;
|
||||
static ncm_rect_t r_cont, r_list, r_btn, r_tabs[2];
|
||||
static int g_empty_id, g_footer_id, g_btn_id, g_tabbar_id;
|
||||
static int g_press_row, g_press_delta, g_dragging, g_drag_last_y;
|
||||
static int g_scroll_song, g_scroll_pl; /* 两页签各自滚动位置(§23)*/
|
||||
|
||||
static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_t g_thr;
|
||||
@@ -27,53 +40,146 @@ static int g_thr_on;
|
||||
static volatile int g_gen;
|
||||
static int g_acked;
|
||||
static volatile int g_loading;
|
||||
static char g_query[256];
|
||||
static char g_query[256]; /* worker 所搜关键词(不可含引用问题,见 core 转义) */
|
||||
static int g_tab; /* 0=单曲 1=歌单 */
|
||||
static int g_searched; /* 1=已提交过搜索(区分「输入后未搜」空态文案)*/
|
||||
static int g_req_tab, g_req_offset, g_req_replace;
|
||||
static volatile int g_more_err; /* 翻页失败(snack 一次)*/
|
||||
|
||||
static void *search_worker(void *arg); /* 前向声明(sr_maybe_load_more 先引用) */
|
||||
|
||||
static void sr_drag_reset(void)
|
||||
{
|
||||
g_press_row = -1; g_press_delta = 0; g_dragging = 0; g_drag_last_y = 0;
|
||||
}
|
||||
|
||||
/* 当前视图(§23 历史/结果/空态)。由 g_st.search_query 是否为空 + 各页签结果数推导。 */
|
||||
static int sr_view(void)
|
||||
{
|
||||
if (g_st.search_query[0] == 0)
|
||||
return (appstate_search_history_count() > 0) ? SR_HIST : SR_EMPTY;
|
||||
if (appstate_search_count(g_tab) > 0) return SR_RES;
|
||||
return SR_EMPTY;
|
||||
}
|
||||
|
||||
static int sr_count(void)
|
||||
{
|
||||
int v = sr_view();
|
||||
if (v == SR_HIST) return appstate_search_history_count();
|
||||
if (v == SR_RES) return appstate_search_count(g_tab);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sr_save_scroll(void)
|
||||
{
|
||||
if (g_tab == 0) g_scroll_song = meow_listview_scroll(g_list);
|
||||
else g_scroll_pl = meow_listview_scroll(g_list);
|
||||
}
|
||||
|
||||
static void sr_restore_scroll(void)
|
||||
{
|
||||
meow_listview_set_scroll(g_list, (g_tab == 0) ? g_scroll_song : g_scroll_pl);
|
||||
}
|
||||
|
||||
static int sr_src2(int idx, meow_list_item *out, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (idx < 0 || idx >= g_st.search_count) return 0;
|
||||
ncm_song_t *s = &g_st.songs[idx];
|
||||
static char sub[256];
|
||||
if (s->art_name[0] && s->album_name[0]) snprintf(sub, sizeof sub, "%s · %s", s->art_name, s->album_name);
|
||||
else if (s->art_name[0]) snprintf(sub, sizeof sub, "%s", s->art_name);
|
||||
else snprintf(sub, sizeof sub, "%s", s->album_name);
|
||||
out->icon_kind = MEOW_LIST_ICON_NONE;
|
||||
out->title = s->name;
|
||||
out->subtitle = sub;
|
||||
out->right = MEOW_LIST_RIGHT_ARROW;
|
||||
out->on = 0; out->enabled = 1;
|
||||
return 1;
|
||||
int v = sr_view();
|
||||
if (v == SR_HIST) {
|
||||
char q[NCM_SEARCH_HIST_LEN];
|
||||
if (!appstate_search_history_get(idx, q, sizeof q)) return 0;
|
||||
out->icon_kind = MEOW_LIST_ICON_NONE;
|
||||
out->title = q;
|
||||
out->subtitle = "";
|
||||
out->right = MEOW_LIST_RIGHT_NONE;
|
||||
out->on = 0; out->enabled = 1;
|
||||
return 1;
|
||||
}
|
||||
if (v == SR_RES) {
|
||||
if (g_tab == 0) {
|
||||
static ncm_song_t s;
|
||||
if (appstate_search_song(idx, &s) != 0) return 0;
|
||||
static char sub[256];
|
||||
if (s.art_name[0] && s.album_name[0]) snprintf(sub, sizeof sub, "%s · %s", s.art_name, s.album_name);
|
||||
else if (s.art_name[0]) snprintf(sub, sizeof sub, "%s", s.art_name);
|
||||
else snprintf(sub, sizeof sub, "%s", s.album_name);
|
||||
out->icon_kind = MEOW_LIST_ICON_NONE;
|
||||
out->title = s.name;
|
||||
out->subtitle = sub;
|
||||
} else {
|
||||
static ncm_playlist_t p;
|
||||
if (appstate_search_pl(idx, &p) != 0) return 0;
|
||||
static char sub[64];
|
||||
snprintf(sub, sizeof sub, "%d 首", p.track_count);
|
||||
out->icon_kind = MEOW_LIST_ICON_NONE;
|
||||
out->title = p.name;
|
||||
out->subtitle = sub;
|
||||
}
|
||||
out->right = MEOW_LIST_RIGHT_ARROW;
|
||||
out->on = 0; out->enabled = 1;
|
||||
return 1;
|
||||
}
|
||||
return 0; /* SR_EMPTY:无行 */
|
||||
}
|
||||
|
||||
static void sr_draw_empty(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (meow_ui_activity_active() != ACT_SEARCH) return;
|
||||
if (g_st.search_count != 0 && g_st.search_err == 0) return;
|
||||
if (sr_view() != SR_EMPTY) return; /* 历史/结果态有行,空态不画 */
|
||||
if (g_loading) { ncm_draw_empty(r_list, "搜索中", "正在搜索..."); return; }
|
||||
if (g_st.search_err == NCM_ERR_AUTH) ncm_draw_empty(r_list, "登录已过期", "请重新登录");
|
||||
else if (g_st.search_err) ncm_draw_empty(r_list, "搜索失败", "点「搜索」重试");
|
||||
else if (!g_query[0]) ncm_draw_empty(r_list, "输入关键词", "输入后点「搜索」");
|
||||
else ncm_draw_empty(r_list, "没有结果", "未找到相关歌曲");
|
||||
else if (!g_st.search_query[0]) ncm_draw_empty(r_list, "输入关键词", "输入后点「搜索」");
|
||||
else if (!g_searched) ncm_draw_empty(r_list, "输入后点「搜索」", "或点历史记录");
|
||||
else ncm_draw_empty(r_list, "没有结果",
|
||||
g_tab == 0 ? "未找到相关歌曲" : "未找到相关歌单");
|
||||
}
|
||||
|
||||
static void sr_draw_footer(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (meow_ui_activity_active() != ACT_SEARCH) return;
|
||||
if (g_st.search_err != 0 || g_st.search_count == 0) return;
|
||||
char foot[64];
|
||||
snprintf(foot, sizeof foot, "找到 %d 个结果", g_st.search_count);
|
||||
ncm_draw_footer(r_cont, foot);
|
||||
int v = sr_view();
|
||||
if (v == SR_HIST) { /* 历史:右缘「清空全部」按钮 */
|
||||
const meow_theme *th = meow_theme_get();
|
||||
const ncm_pal_t *pa = ncm_palette(g_dark);
|
||||
int cw = meow_ui_text_width("清空全部", th->font_body);
|
||||
int fx = r_cont.x + r_cont.w - 16 - cw;
|
||||
int fy = r_cont.y + r_cont.h - APP_FOOTER_H + (APP_FOOTER_H - th->font_body) / 2;
|
||||
meow_ui_text(fx, fy, "清空全部", th->font_body, pa->red);
|
||||
return;
|
||||
}
|
||||
if (v == SR_RES) {
|
||||
int n = appstate_search_count(g_tab);
|
||||
if (n > 0) {
|
||||
char foot[96];
|
||||
snprintf(foot, sizeof foot, "%s %d %s%s",
|
||||
g_tab == 0 ? "单曲" : "歌单", n,
|
||||
g_tab == 0 ? "个结果" : "个歌单",
|
||||
appstate_search_has_more(g_tab) ? " · 加载更多" : "");
|
||||
ncm_draw_footer(r_cont, foot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sr_draw_tabs(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (meow_ui_activity_active() != ACT_SEARCH) return;
|
||||
if (sr_view() != SR_RES) return; /* 页签只在结果态显示 */
|
||||
const meow_theme *th = meow_theme_get();
|
||||
const ncm_pal_t *pa = ncm_palette(g_dark);
|
||||
static const char *lab[2] = { "单曲", "歌单" };
|
||||
for (int i = 0; i < 2; i++) {
|
||||
int on = (g_tab == i);
|
||||
uint32_t col = on ? pa->red : th->onSurfaceVariant;
|
||||
meow_ui_text_center(r_tabs[i].x + r_tabs[i].w / 2, r_tabs[i].y + (r_tabs[i].h - th->font_body) / 2,
|
||||
lab[i], th->font_body, col);
|
||||
if (on) meow_ui_fill_rect(r_tabs[i].x, r_tabs[i].y + r_tabs[i].h - 3, r_tabs[i].w, 3, pa->red);
|
||||
}
|
||||
}
|
||||
|
||||
/* 「搜索」红色确认钮(SDK meow_btn FILLED 是 MD3 蓝;App 自绘品牌红)。 */
|
||||
static void sr_draw_btn(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
@@ -84,35 +190,61 @@ static void sr_draw_btn(void *ctx)
|
||||
|
||||
static void sr_apply(void)
|
||||
{
|
||||
if (g_list) {
|
||||
meow_listview_set_count(g_list, g_st.search_count);
|
||||
meow_listview_set_scroll(g_list, 0);
|
||||
meow_listview_set_sel(g_list, -1);
|
||||
}
|
||||
if (g_list) meow_listview_set_count(g_list, sr_count());
|
||||
const meow_theme *th = meow_theme_get();
|
||||
meow_ui_set_page_bg(th->bg);
|
||||
meow_ui_paint_full();
|
||||
}
|
||||
|
||||
/* 后台 worker:唯一网络调用(cloudsearch),UI 不卡。
|
||||
* §17:结果集(≤NCM_SEARCH_MAX,远小于窗口)经 appstate_adopt_songs 收编为窗口 base=0
|
||||
* 播放上下文(song_ids/track_ids_count 同步),播放/上下一首对结果集生效。 */
|
||||
/* 触底翻页(§23):RES 态 + 还有下一页 + 未在载 → 按当前页签的 offset 拉下一页。
|
||||
* 仅由用户滚动动作触发(风控:翻页=用户动作)。 */
|
||||
static void sr_maybe_load_more(void)
|
||||
{
|
||||
if (sr_view() != SR_RES) return;
|
||||
if (g_thr_on || g_loading || g_st.search_err != 0) return;
|
||||
int n = appstate_search_count(g_tab);
|
||||
int cap = (g_tab == 0) ? NCM_SEARCH_SONG_MAX : NCM_SEARCH_PL_MAX;
|
||||
if (n <= 0 || n >= cap || !appstate_search_has_more(g_tab)) return;
|
||||
int item = APP_ROW_H;
|
||||
int scroll = meow_listview_scroll(g_list);
|
||||
if (scroll + r_list.h < n * item - item * 2) return; /* 未近底 */
|
||||
g_req_tab = g_tab; g_req_offset = n; g_req_replace = 0;
|
||||
/* 单飞:无 worker 才发(search_worker 尾部解锁) */
|
||||
g_thr_on = 1; g_loading = 1; g_more_err = 0;
|
||||
if (pthread_create(&g_thr, NULL, search_worker, NULL) != 0) {
|
||||
g_thr_on = 0; g_loading = 0; g_more_err = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void *search_worker(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
int tab = g_req_tab, off = g_req_offset, repl = g_req_replace;
|
||||
const char *kw = g_query;
|
||||
int n = 0;
|
||||
int rc = ncm_api_cloudsearch(g_st.ctx.device_id, g_st.ctx.cookie, kw,
|
||||
g_st.songs, NCM_SEARCH_MAX, &n);
|
||||
pthread_mutex_lock(&g_mu);
|
||||
if (rc == NCM_OK) {
|
||||
appstate_adopt_songs(n); /* 收编 songs[0..n) 为窗口(写侧在锁内) */
|
||||
g_st.search_count = n;
|
||||
g_st.search_err = 0;
|
||||
int rc = NCM_ERR_IO, n = 0;
|
||||
int64_t total = -1;
|
||||
if (tab == 0) {
|
||||
static ncm_song_t sbuf[NCM_SEARCH_PAGE];
|
||||
rc = ncm_api_cloudsearch_song(g_st.ctx.device_id, g_st.ctx.cookie, kw,
|
||||
off, NCM_SEARCH_PAGE, sbuf, NCM_SEARCH_PAGE, &n, &total);
|
||||
pthread_mutex_lock(&g_mu);
|
||||
if (rc == NCM_OK) {
|
||||
if (repl) appstate_search_clear(0);
|
||||
appstate_search_append_songs(n, sbuf, total);
|
||||
g_st.search_err = 0; g_more_err = 0;
|
||||
} else if (repl) g_st.search_err = rc;
|
||||
else g_more_err = 1;
|
||||
} else {
|
||||
appstate_adopt_songs(0);
|
||||
g_st.search_count = 0;
|
||||
g_st.search_err = rc;
|
||||
static ncm_playlist_t pbuf[NCM_SEARCH_PAGE];
|
||||
rc = ncm_api_cloudsearch_playlist(g_st.ctx.device_id, g_st.ctx.cookie, kw,
|
||||
off, NCM_SEARCH_PAGE, pbuf, NCM_SEARCH_PAGE, &n, &total);
|
||||
pthread_mutex_lock(&g_mu);
|
||||
if (rc == NCM_OK) {
|
||||
if (repl) appstate_search_clear(1);
|
||||
appstate_search_append_pls(n, pbuf, total);
|
||||
g_st.search_err = 0; g_more_err = 0;
|
||||
} else if (repl) g_st.search_err = rc;
|
||||
else g_more_err = 1;
|
||||
}
|
||||
g_loading = 0;
|
||||
g_gen++;
|
||||
@@ -125,13 +257,18 @@ void search_submit(const char *kw)
|
||||
if (!kw || !kw[0]) { ncm_snack("请输入关键词"); return; }
|
||||
ncm_strlcpy(g_query, kw, sizeof g_query);
|
||||
ncm_strlcpy(g_st.search_query, kw, sizeof g_st.search_query);
|
||||
if (g_host_mock) { /* 宿主/测试:预置结果,同步 */
|
||||
appstate_search_history_add(kw); /* §23:历史(去重+最新在前+≤12)*/
|
||||
g_searched = 1;
|
||||
g_scroll_song = g_scroll_pl = 0;
|
||||
if (g_list) { meow_listview_set_scroll(g_list, 0); }
|
||||
appstate_search_clear(g_tab ^ 1); /* 新查询:清非当前页签,切过去时懒加载新词 */
|
||||
if (g_host_mock) { /* 宿主/测试:预置结果,同步 */
|
||||
g_loading = 0; g_st.search_err = 0; sr_apply(); return;
|
||||
}
|
||||
if (g_thr_on) return; /* 已有 worker 在跑 */
|
||||
if (g_thr_on) return; /* 单飞:已有 worker 在跑 */
|
||||
g_req_tab = g_tab; g_req_offset = 0; g_req_replace = 1;
|
||||
g_st.search_err = 0;
|
||||
g_st.search_count = 0;
|
||||
g_loading = 1;
|
||||
g_loading = 1; g_more_err = 0;
|
||||
g_thr_on = 1;
|
||||
if (pthread_create(&g_thr, NULL, search_worker, NULL) != 0) {
|
||||
g_thr_on = 0; g_loading = 0; g_st.search_err = NCM_ERR_IO;
|
||||
@@ -139,6 +276,26 @@ void search_submit(const char *kw)
|
||||
sr_apply();
|
||||
}
|
||||
|
||||
/* 切页签:保存旧滚动 → 换页签 → 恢复该页签滚动;空结果页签懒加载(用户切页签动作)。 */
|
||||
static void sr_set_tab(int t)
|
||||
{
|
||||
if (t == g_tab) return;
|
||||
sr_save_scroll();
|
||||
g_tab = t;
|
||||
g_st.search_tab = t; /* 应用态同步(重启/重进保持) */
|
||||
sr_restore_scroll();
|
||||
sr_apply();
|
||||
if (g_st.search_query[0] && !g_thr_on && !g_loading &&
|
||||
g_st.search_err == 0 && appstate_search_count(g_tab) == 0) {
|
||||
g_req_tab = g_tab; g_req_offset = 0; g_req_replace = 1;
|
||||
g_loading = 1; g_more_err = 0;
|
||||
g_thr_on = 1;
|
||||
if (pthread_create(&g_thr, NULL, search_worker, NULL) != 0) {
|
||||
g_thr_on = 0; g_loading = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void search_tick(uint64_t now)
|
||||
{
|
||||
(void)now;
|
||||
@@ -148,6 +305,7 @@ void search_tick(uint64_t now)
|
||||
g_acked = g_gen;
|
||||
g_thr_on = 0;
|
||||
g_loading = 0;
|
||||
if (g_more_err) { ncm_snack("加载失败"); g_more_err = 0; }
|
||||
sr_apply();
|
||||
}
|
||||
|
||||
@@ -170,24 +328,77 @@ int search_on_tap(int x, int y)
|
||||
search_submit(t ? t : "");
|
||||
return 1;
|
||||
}
|
||||
if (g_field && meow_widget_hit(g_field, x, y)) { /* 唤 IME */
|
||||
int v = sr_view();
|
||||
if (v == SR_RES) { /* 页签切换 */
|
||||
for (int i = 0; i < 2; i++)
|
||||
if (x >= r_tabs[i].x && x < r_tabs[i].x + r_tabs[i].w &&
|
||||
y >= r_tabs[i].y && y < r_tabs[i].y + r_tabs[i].h) { sr_set_tab(i); return 1; }
|
||||
}
|
||||
if (v == SR_HIST && /* 历史「清空全部」按钮 */
|
||||
y >= r_cont.y + r_cont.h - APP_FOOTER_H && y < r_cont.y + r_cont.h &&
|
||||
x >= r_cont.x + r_cont.w - 16 - 70) {
|
||||
sr_drag_reset();
|
||||
appstate_search_history_clear();
|
||||
sr_apply();
|
||||
return 1;
|
||||
}
|
||||
if (g_field && meow_widget_hit(g_field, x, y)) { /* 唤 IME */
|
||||
meow_textfield_activate(g_field);
|
||||
return 1;
|
||||
}
|
||||
if (g_st.search_count > 0 && g_st.search_err == 0 && meow_widget_hit(g_list, x, y)) {
|
||||
int row = meow_listview_hit(g_list, x, y);
|
||||
if (row < 0) { sr_drag_reset(); return 1; }
|
||||
if (meow_widget_hit(g_list, x, y)) {
|
||||
if (v == SR_EMPTY) { /* 失败态:点内容区重试(风控:用户动作) */
|
||||
if (g_st.search_err && g_st.search_query[0]) {
|
||||
sr_drag_reset(); g_st.search_err = 0;
|
||||
if (!g_thr_on) {
|
||||
g_req_tab = g_tab; g_req_offset = 0; g_req_replace = 1;
|
||||
g_loading = 1;
|
||||
g_thr_on = 1;
|
||||
if (pthread_create(&g_thr, NULL, search_worker, NULL) != 0) {
|
||||
g_thr_on = 0; g_loading = 0; g_st.search_err = NCM_ERR_IO;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
sr_drag_reset();
|
||||
return 1;
|
||||
}
|
||||
/* 误触修复(§13.3/§23):按下行 + 拖动>10 抑制(对齐 home/playlist) */
|
||||
int was_drag = (g_press_delta > 10);
|
||||
int row = (g_press_row >= 0) ? g_press_row : meow_listview_hit(g_list, x, y);
|
||||
sr_drag_reset();
|
||||
player_play_song(row); /* 结果集即 g_st.songs[],点击可播 */
|
||||
if (row < 0 || was_drag) return 1;
|
||||
if (v == SR_HIST) {
|
||||
char q[NCM_SEARCH_HIST_LEN];
|
||||
if (appstate_search_history_get(row, q, sizeof q)) search_submit(q);
|
||||
return 1;
|
||||
}
|
||||
if (g_tab == 0) player_play_search_song(row); /* 单曲:搜索队列起播(§23)*/
|
||||
else {
|
||||
ncm_playlist_t p;
|
||||
if (appstate_search_pl(row, &p) == 0) playlist_open_pl(&p); /* 歌单:进详情 */
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int search_swipe_left(void)
|
||||
{
|
||||
if (sr_view() != SR_HIST) return 0; /* 仅历史模式左滑删单条 */
|
||||
int row = g_press_row; /* touch_down 已记按下行 */
|
||||
sr_drag_reset();
|
||||
if (row >= 0) {
|
||||
appstate_search_history_remove(row);
|
||||
sr_apply();
|
||||
return 1; /* 已消费:不触返回 */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void search_touch_down(int x, int y)
|
||||
{
|
||||
sr_drag_reset();
|
||||
if (g_st.search_count == 0 || g_st.search_err != 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;
|
||||
@@ -209,19 +420,23 @@ void search_touch_move(int x, int 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)
|
||||
if (meow_listview_scroll(g_list) != before) {
|
||||
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||
sr_maybe_load_more();
|
||||
}
|
||||
}
|
||||
|
||||
void search_swipe(int dir, int dist)
|
||||
{
|
||||
if (dir != MEOW_SWIPE_UP && dir != MEOW_SWIPE_DOWN) return;
|
||||
if (g_st.search_count == 0 || g_st.search_err != 0) return;
|
||||
if (sr_view() != SR_RES) 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)
|
||||
if (meow_listview_scroll(g_list) != before) {
|
||||
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||
sr_maybe_load_more();
|
||||
}
|
||||
}
|
||||
|
||||
static void sr_page_create(meow_view *v, void *ctx)
|
||||
@@ -231,12 +446,14 @@ static void sr_page_create(meow_view *v, void *ctx)
|
||||
r_cont = ncm_content_rect();
|
||||
ncm_rect_t field = NR(16, r_cont.y + 12, r_cont.w - 32, 48);
|
||||
r_btn = NR(field.x, field.y + field.h + 12, field.w, 44);
|
||||
r_list = NR(16, r_btn.y + r_btn.h + 12, r_cont.w - 32,
|
||||
r_cont.y + r_cont.h - APP_FOOTER_H - (r_btn.y + r_btn.h + 12));
|
||||
int by = r_btn.y + r_btn.h + 8;
|
||||
r_tabs[0] = NR(r_btn.x, by, r_btn.w / 2 - 6, 36);
|
||||
r_tabs[1] = NR(r_btn.x + r_btn.w / 2 + 6, by, r_btn.w / 2 - 6, 36);
|
||||
r_list = NR(16, by + 36 + 8, r_cont.w - 32,
|
||||
r_cont.y + r_cont.h - APP_FOOTER_H - (by + 36 + 8));
|
||||
if (r_list.h < 0) r_list.h = 0;
|
||||
meow_ui_activity_push(ACT_SEARCH); /* owner=ACT_SEARCH */
|
||||
g_tb = ncm_topbar_create("搜索", 1, NULL);
|
||||
if (!g_st.search_query[0]) ncm_strlcpy(g_st.search_query, "", sizeof g_st.search_query);
|
||||
/* 中文搜索:IME 切 MEOW_IME_PINYIN(拼音候选上屏 UTF-8 到 g_st.search_query,经 on_change 实时回写;
|
||||
* 键盘「拼/英」键可切回 ASCII 输英文,「中」热区回拼音——英文零回归)。 */
|
||||
g_field = meow_textfield_create(field.x, field.y, field.w, field.h,
|
||||
@@ -249,10 +466,14 @@ static void sr_page_create(meow_view *v, void *ctx)
|
||||
meow_widget_set_z(g_list, 10);
|
||||
g_btn_id = meow_draw_custom_register(60, r_btn.x, r_btn.y, r_btn.w, r_btn.h,
|
||||
sr_draw_btn, NULL);
|
||||
g_tabbar_id = meow_draw_custom_register(40, r_tabs[0].x, r_tabs[0].y,
|
||||
r_btn.w, r_tabs[0].h, sr_draw_tabs, NULL);
|
||||
g_empty_id = meow_draw_custom_register(1, r_list.x, r_list.y, r_list.w, r_list.h,
|
||||
sr_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, sr_draw_footer, NULL);
|
||||
/* 页签初始 = 应用态记录值(宿主 dump 可指定 search_tab);越界回落 0。 */
|
||||
g_tab = (g_st.search_tab == 0 || g_st.search_tab == 1) ? g_st.search_tab : 0;
|
||||
sr_drag_reset();
|
||||
}
|
||||
|
||||
@@ -260,9 +481,13 @@ static void sr_page_enter(meow_view *v, void *ctx)
|
||||
{
|
||||
(void)v;
|
||||
((search_ctrl *)ctx)->active = 1;
|
||||
const meow_theme *th = meow_theme_get();
|
||||
meow_ui_set_page_bg(th->bg);
|
||||
meow_ui_paint_full();
|
||||
sr_apply(); /* 进页即按当前模式设列表 count(历史/结果/空态),避免首屏空列表 */
|
||||
#ifndef MEOW_UI_HOST_TEST
|
||||
/* §23:进页自动弹键盘(字段为空时;返回浏览结果不打断)。宿主离屏为阻塞式模态 IME,跳过。 */
|
||||
if (g_field && g_st.search_query[0] == 0) meow_textfield_activate(g_field);
|
||||
#else
|
||||
(void)0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void sr_page_leave(meow_view *v, void *ctx)
|
||||
@@ -274,9 +499,10 @@ static void sr_page_leave(meow_view *v, void *ctx)
|
||||
static void sr_page_destroy(meow_view *v, void *ctx)
|
||||
{
|
||||
(void)v; (void)ctx;
|
||||
if (g_btn_id) { meow_draw_unregister(g_btn_id); g_btn_id = 0; }
|
||||
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_btn_id) { meow_draw_unregister(g_btn_id); g_btn_id = 0; }
|
||||
if (g_tabbar_id){ meow_draw_unregister(g_tabbar_id);g_tabbar_id = 0; }
|
||||
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_field){ meow_widget_free(g_field); g_field = NULL; }
|
||||
if (g_tb) { meow_widget_free(g_tb); g_tb = NULL; }
|
||||
|
||||
Reference in new issue
Block a user