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
@@ -45,6 +45,8 @@ static void app_on_swipe(int dir, int dx, int dy)
|
||||
{
|
||||
(void)dx;
|
||||
if (dir == MEOW_SWIPE_LEFT) {
|
||||
/* 搜索历史模式:左滑删单条(§23);未消费才走上/下一层返回语义。 */
|
||||
if (search_is_active() && search_swipe_left()) return;
|
||||
if (player_is_active()) { meow_view_page_pop(); return; }
|
||||
if (settings_is_active()) { meow_view_page_pop(); return; }
|
||||
if (playlist_is_active()) { meow_view_page_pop(); return; }
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
/* 滑动窗口互斥(REPORT §17):窗口读写(滑窗/物化/拷贝)全走此锁,见下方窗口节注释。 */
|
||||
static pthread_mutex_t g_song_mu = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
/* 搜索结果独立数组互斥(§23):g_st.search_songs/search_pls 读写 + queue_src 读。 */
|
||||
static pthread_mutex_t g_search_mu = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static void appstate_srch_hist_init(void); /* §23:读入本地搜索历史(appstate_init 首调) */
|
||||
|
||||
int appstate_init(void)
|
||||
{
|
||||
const char *dd = getenv("MEOW_DATA_DIR");
|
||||
@@ -37,6 +42,9 @@ int appstate_init(void)
|
||||
|
||||
account_load(); /* §20:恢复会员态缓存(account.json;无文件则 NCM_VIP_NONE) */
|
||||
|
||||
g_st.queue_src = NCM_QUEUE_PLAYLIST; /* §23:默认播放队列=歌单窗口 */
|
||||
g_st.search_tab = 0;
|
||||
appstate_srch_hist_init(); /* §23:读取本地搜索历史 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -362,10 +370,11 @@ void appstate_adopt_songs(int n)
|
||||
|
||||
int appstate_load_playlist_songs_page(int idx, int offset, int limit)
|
||||
{
|
||||
if (idx < 0 || idx >= g_st.playlist_count) return NCM_ERR_ARG;
|
||||
/* 搜索歌单详情(§23):idx==NCM_PL_SEARCH 时读 g_st.search_pl_open(复用本链路)。 */
|
||||
if (idx != NCM_PL_SEARCH && (idx < 0 || idx >= g_st.playlist_count)) return NCM_ERR_ARG;
|
||||
/* 不在此处写 g_st.active_playlist:调用方(playlist_open/UI)已在 UI 线程设定;
|
||||
* 若后台 worker 在此改写,会覆盖用户已切换到的新歌单,造成“打开 B 却显示 A”。 */
|
||||
const ncm_playlist_t *pl = &g_st.playlists[idx];
|
||||
const ncm_playlist_t *pl = (idx == NCM_PL_SEARCH) ? &g_st.search_pl_open : &g_st.playlists[idx];
|
||||
if (limit <= 0 || limit > NCM_SONG_BATCH) limit = NCM_SONG_BATCH;
|
||||
if (offset < 0) offset = 0;
|
||||
|
||||
@@ -412,6 +421,10 @@ int appstate_load_playlist_songs(int idx)
|
||||
|
||||
void appstate_apply_playlist_cover(int idx)
|
||||
{
|
||||
if (idx == NCM_PL_SEARCH) {
|
||||
ncm_strlcpy(g_st.cover_url, g_st.search_pl_open.cover, sizeof g_st.cover_url);
|
||||
return;
|
||||
}
|
||||
if (idx < 0 || idx >= g_st.playlist_count) return;
|
||||
ncm_strlcpy(g_st.cover_url, g_st.playlists[idx].cover, sizeof g_st.cover_url);
|
||||
}
|
||||
@@ -427,7 +440,9 @@ void appstate_set_playing_song(int idx, const char *cover_url)
|
||||
|
||||
int appstate_current_playing(void)
|
||||
{
|
||||
if (g_st.playing_song < 0 || g_st.playing_song >= g_st.track_ids_count) return -1;
|
||||
if (g_st.playing_song < 0) return -1;
|
||||
int cnt = appstate_queue_count(); /* §23:搜索队列时按搜索条数界(不误判歌单亮点) */
|
||||
if (g_st.playing_song >= cnt) return -1;
|
||||
return g_st.playing_song;
|
||||
}
|
||||
|
||||
@@ -523,3 +538,214 @@ void autoplay_maybe(void)
|
||||
if (pthread_create(&t, NULL, autoplay_worker, sel) == 0) pthread_detach(t);
|
||||
else free(sel);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 搜索(REPORT §23):独立结果数组(不覆写歌单窗口)+ 播放队列来源 + 本地历史。
|
||||
* 结果数组读写经 g_search_mu;queue_src 决定超时/起播读数组还是窗口。
|
||||
* ============================================================ */
|
||||
|
||||
void appstate_set_queue(int src) { g_st.queue_src = src; }
|
||||
int appstate_get_queue(void) { return g_st.queue_src; }
|
||||
|
||||
int appstate_queue_count(void)
|
||||
{
|
||||
int c;
|
||||
pthread_mutex_lock(&g_search_mu);
|
||||
c = (g_st.queue_src == NCM_QUEUE_SEARCH) ? g_st.search_song_count : g_st.track_ids_count;
|
||||
pthread_mutex_unlock(&g_search_mu);
|
||||
return c < 0 ? 0 : c;
|
||||
}
|
||||
|
||||
int appstate_queue_song(int idx, ncm_song_t *out)
|
||||
{
|
||||
if (!out) return NCM_ERR_ARG;
|
||||
if (g_st.queue_src == NCM_QUEUE_SEARCH) {
|
||||
pthread_mutex_lock(&g_search_mu);
|
||||
int ok = (idx >= 0 && idx < g_st.search_song_count);
|
||||
if (ok) *out = g_st.search_songs[idx];
|
||||
pthread_mutex_unlock(&g_search_mu);
|
||||
return ok ? NCM_OK : NCM_ERR_ARG;
|
||||
}
|
||||
if (appstate_song_copy(idx, out) == 0) return NCM_OK;
|
||||
return appstate_song_fetch(idx, out);
|
||||
}
|
||||
|
||||
int appstate_search_count(int tab)
|
||||
{
|
||||
int c;
|
||||
pthread_mutex_lock(&g_search_mu);
|
||||
c = (tab == 0) ? g_st.search_song_count : g_st.search_pl_count;
|
||||
pthread_mutex_unlock(&g_search_mu);
|
||||
return c;
|
||||
}
|
||||
|
||||
int appstate_search_offset(int tab)
|
||||
{
|
||||
int o;
|
||||
pthread_mutex_lock(&g_search_mu);
|
||||
o = (tab == 0) ? g_st.search_song_offset : g_st.search_pl_offset;
|
||||
pthread_mutex_unlock(&g_search_mu);
|
||||
return o;
|
||||
}
|
||||
|
||||
int appstate_search_has_more(int tab)
|
||||
{
|
||||
int m;
|
||||
pthread_mutex_lock(&g_search_mu);
|
||||
m = (tab == 0) ? g_st.search_song_more : g_st.search_pl_more;
|
||||
pthread_mutex_unlock(&g_search_mu);
|
||||
return m;
|
||||
}
|
||||
|
||||
int appstate_search_song(int i, ncm_song_t *out)
|
||||
{
|
||||
if (!out) return NCM_ERR_ARG;
|
||||
pthread_mutex_lock(&g_search_mu);
|
||||
int ok = (i >= 0 && i < g_st.search_song_count);
|
||||
if (ok) *out = g_st.search_songs[i];
|
||||
pthread_mutex_unlock(&g_search_mu);
|
||||
return ok ? NCM_OK : NCM_ERR_ARG;
|
||||
}
|
||||
|
||||
int appstate_search_pl(int i, ncm_playlist_t *out)
|
||||
{
|
||||
if (!out) return NCM_ERR_ARG;
|
||||
pthread_mutex_lock(&g_search_mu);
|
||||
int ok = (i >= 0 && i < g_st.search_pl_count);
|
||||
if (ok) *out = g_st.search_pls[i];
|
||||
pthread_mutex_unlock(&g_search_mu);
|
||||
return ok ? NCM_OK : NCM_ERR_ARG;
|
||||
}
|
||||
|
||||
void appstate_search_clear(int tab)
|
||||
{
|
||||
pthread_mutex_lock(&g_search_mu);
|
||||
if (tab == 0) {
|
||||
g_st.search_song_count = 0; g_st.search_song_offset = 0;
|
||||
g_st.search_song_total = -1; g_st.search_song_more = 1;
|
||||
memset(g_st.search_songs, 0, sizeof g_st.search_songs);
|
||||
} else {
|
||||
g_st.search_pl_count = 0; g_st.search_pl_offset = 0;
|
||||
g_st.search_pl_total = -1; g_st.search_pl_more = 1;
|
||||
memset(g_st.search_pls, 0, sizeof g_st.search_pls);
|
||||
}
|
||||
pthread_mutex_unlock(&g_search_mu);
|
||||
}
|
||||
|
||||
/* 追加单曲结果(worker 锁内):写 g_st.search_songs[count..),回填 offset/total/has_more。 */
|
||||
void appstate_search_append_songs(int n, const ncm_song_t *s, int64_t total)
|
||||
{
|
||||
if (n <= 0 || !s) return;
|
||||
pthread_mutex_lock(&g_search_mu);
|
||||
int room = NCM_SEARCH_SONG_MAX - g_st.search_song_count;
|
||||
if (n > room) n = room;
|
||||
if (n > 0) memcpy(&g_st.search_songs[g_st.search_song_count], s, (size_t)n * sizeof(ncm_song_t));
|
||||
g_st.search_song_count += n;
|
||||
g_st.search_song_offset = g_st.search_song_count;
|
||||
g_st.search_song_total = (total >= 0) ? (int)total : g_st.search_song_total;
|
||||
int t = g_st.search_song_total;
|
||||
g_st.search_song_more = (t >= 0) ? (g_st.search_song_count < t) : (n >= NCM_SEARCH_PAGE);
|
||||
pthread_mutex_unlock(&g_search_mu);
|
||||
}
|
||||
|
||||
void appstate_search_append_pls(int n, const ncm_playlist_t *p, int64_t total)
|
||||
{
|
||||
if (n <= 0 || !p) return;
|
||||
pthread_mutex_lock(&g_search_mu);
|
||||
int room = NCM_SEARCH_PL_MAX - g_st.search_pl_count;
|
||||
if (n > room) n = room;
|
||||
if (n > 0) memcpy(&g_st.search_pls[g_st.search_pl_count], p, (size_t)n * sizeof(ncm_playlist_t));
|
||||
g_st.search_pl_count += n;
|
||||
g_st.search_pl_offset = g_st.search_pl_count;
|
||||
g_st.search_pl_total = (total >= 0) ? (int)total : g_st.search_pl_total;
|
||||
int t = g_st.search_pl_total;
|
||||
g_st.search_pl_more = (t >= 0) ? (g_st.search_pl_count < t) : (n >= NCM_SEARCH_PAGE);
|
||||
pthread_mutex_unlock(&g_search_mu);
|
||||
}
|
||||
|
||||
/* ---- 搜索历史(≤NCM_SEARCH_HIST_MAX,最新在前;单键 meow_config 持久化,条目以 \n 连接)---- */
|
||||
#define NCM_SEARCH_HIST_KEY "ncm.search_history"
|
||||
static char g_hist[NCM_SEARCH_HIST_MAX][NCM_SEARCH_HIST_LEN];
|
||||
static int g_hist_count;
|
||||
static int g_hist_loaded;
|
||||
|
||||
static void srch_hist_load(void)
|
||||
{
|
||||
char buf[2048];
|
||||
if (meow_config_get(NCM_SEARCH_HIST_KEY, buf, sizeof buf) != MEOW_CFG_OK) { g_hist_count = 0; return; }
|
||||
g_hist_count = 0;
|
||||
char *p = buf;
|
||||
while (p && *p && g_hist_count < NCM_SEARCH_HIST_MAX) {
|
||||
char *nl = strchr(p, '\n');
|
||||
size_t len = nl ? (size_t)(nl - p) : strlen(p);
|
||||
if (len >= NCM_SEARCH_HIST_LEN) len = NCM_SEARCH_HIST_LEN - 1;
|
||||
if (len > 0) { memcpy(g_hist[g_hist_count], p, len); g_hist[g_hist_count][len] = 0; g_hist_count++; }
|
||||
if (!nl) break;
|
||||
p = nl + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void srch_hist_save(void)
|
||||
{
|
||||
char buf[2048];
|
||||
size_t bl = 0;
|
||||
buf[0] = 0;
|
||||
for (int i = 0; i < g_hist_count; i++) {
|
||||
int n = snprintf(buf + bl, sizeof buf - bl, "%s%s", i ? "\n" : "", g_hist[i]);
|
||||
if (n < 0) break;
|
||||
bl += (size_t)n;
|
||||
if (bl >= sizeof buf - 1) break;
|
||||
}
|
||||
meow_config_set(NCM_SEARCH_HIST_KEY, buf);
|
||||
}
|
||||
|
||||
static void appstate_srch_hist_init(void)
|
||||
{
|
||||
if (!g_hist_loaded) { srch_hist_load(); g_hist_loaded = 1; }
|
||||
}
|
||||
|
||||
int appstate_search_history_count(void)
|
||||
{
|
||||
appstate_srch_hist_init();
|
||||
return g_hist_count;
|
||||
}
|
||||
|
||||
int appstate_search_history_get(int i, char *out, size_t sz)
|
||||
{
|
||||
appstate_srch_hist_init();
|
||||
if (i < 0 || i >= g_hist_count) return 0;
|
||||
ncm_strlcpy(out, g_hist[i], sz);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void appstate_search_history_add(const char *kw)
|
||||
{
|
||||
if (!kw || !kw[0]) return;
|
||||
appstate_srch_hist_init();
|
||||
char tmp[NCM_SEARCH_HIST_MAX][NCM_SEARCH_HIST_LEN];
|
||||
int m = 0;
|
||||
ncm_strlcpy(tmp[m], kw, sizeof tmp[m]);
|
||||
m++;
|
||||
for (int i = 0; i < g_hist_count && m < NCM_SEARCH_HIST_MAX; i++)
|
||||
if (strcmp(g_hist[i], kw) != 0) { ncm_strlcpy(tmp[m], g_hist[i], sizeof tmp[m]); m++; }
|
||||
g_hist_count = m;
|
||||
memcpy(g_hist, tmp, sizeof g_hist);
|
||||
srch_hist_save();
|
||||
}
|
||||
|
||||
void appstate_search_history_remove(int i)
|
||||
{
|
||||
appstate_srch_hist_init();
|
||||
if (i < 0 || i >= g_hist_count) return;
|
||||
for (int j = i; j < g_hist_count - 1; j++)
|
||||
ncm_strlcpy(g_hist[j], g_hist[j + 1], sizeof g_hist[j]);
|
||||
g_hist_count--;
|
||||
srch_hist_save();
|
||||
}
|
||||
|
||||
void appstate_search_history_clear(void)
|
||||
{
|
||||
g_hist_count = 0;
|
||||
g_hist_loaded = 1;
|
||||
srch_hist_save();
|
||||
}
|
||||
@@ -63,7 +63,20 @@
|
||||
* 窗口补载粒度;c 数组缓冲 clist[4096] 装 100 首 ≈2KB) */
|
||||
#define NCM_LYRIC_LINES 600 /* 歌词行上限 */
|
||||
#define NCM_COVER_MAX 288*1024 /* 封面下载限幅 ~288KB */
|
||||
#define NCM_SEARCH_MAX 32 /* 搜索结果上限(v1 只显示前 32 条,无大缓冲) */
|
||||
|
||||
/* ---- 搜索(REPORT §23:独立结果数组 + 单曲/歌单两类页签 + 历史 + 分页)---- */
|
||||
#define NCM_SEARCH_PAGE 30 /* cloudsearch limit/页(用户定案;offset 累加) */
|
||||
#define NCM_SEARCH_SONG_MAX 256 /* 单曲结果累计上限(独立数组 ≈156KB bss,不覆写歌单窗口) */
|
||||
#define NCM_SEARCH_PL_MAX 60 /* 歌单结果累计上限(≈48KB bss) */
|
||||
#define NCM_SEARCH_HIST_MAX 12 /* 搜索历史条数(上次在最新,≤12) */
|
||||
#define NCM_SEARCH_HIST_LEN 128 /* 单条历史长度 */
|
||||
|
||||
/* 播放队列来源(§23:搜索单曲起播建立「搜索队列」上下文,不覆写歌单窗口) */
|
||||
#define NCM_QUEUE_PLAYLIST 0 /* 默认:歌单滑动窗口 */
|
||||
#define NCM_QUEUE_SEARCH 1 /* 搜索单曲结果队列 */
|
||||
|
||||
/* active_playlist 哨兵:搜索歌单详情(复用 playlist_open 链路,读 g_st.search_pl_open) */
|
||||
#define NCM_PL_SEARCH (-2)
|
||||
|
||||
/* ---- 播放/媒体会话状态(play 线程 ↔ UI/IPC 共享,mu 保护)---- */
|
||||
enum {
|
||||
@@ -169,11 +182,26 @@ typedef struct ncm_app_state {
|
||||
int cache_limit_gb;
|
||||
int player_bg_mode; /* 歌词页背景模式(§22):NCM_BG_DEFAULT / NCM_BG_POWER_SAVE */
|
||||
|
||||
/* 搜索(结果复用 g_st.songs[] 浅拷贝区;仅搜索页上下文有效)*/
|
||||
/* 搜索(REPORT §23:独立结果数组,不覆写歌单窗口;单曲/歌单两类页签各自独立结果集/分页/滚动) */
|
||||
char search_query[256];
|
||||
int search_count; /* 当前搜索结果数(含 0=无/未搜) */
|
||||
volatile int search_loading; /* 1=搜索请求在途 */
|
||||
int search_err; /* 最近一次搜索错误(0=ok) */
|
||||
int search_tab; /* 当前页签:0=单曲 1=歌单 */
|
||||
ncm_song_t search_songs[NCM_SEARCH_SONG_MAX]; /* 单曲结果(独立数组) */
|
||||
int search_song_count;
|
||||
int search_song_offset; /* 下次 offset(=已加载条数) */
|
||||
int search_song_total; /* 服务端总数(-1=未知) */
|
||||
int search_song_more; /* 1=单曲还有下一页 */
|
||||
ncm_playlist_t search_pls[NCM_SEARCH_PL_MAX]; /* 歌单结果(独立数组) */
|
||||
int search_pl_count;
|
||||
int search_pl_offset;
|
||||
int search_pl_total;
|
||||
int search_pl_more;
|
||||
/* 搜索歌单打开的详情(active_playlist==NCM_PL_SEARCH 时用;复用 playlist_open 链路) */
|
||||
ncm_playlist_t search_pl_open;
|
||||
|
||||
/* 播放队列来源(§23):默认 NCM_QUEUE_PLAYLIST;搜索单曲起播切 NCM_QUEUE_SEARCH */
|
||||
int queue_src;
|
||||
} ncm_app_state_t;
|
||||
|
||||
extern ncm_app_state_t g_st; /* 全局状态(app.c 定义) */
|
||||
@@ -246,6 +274,7 @@ void playlist_swipe(int dir, int dist);
|
||||
void playlist_touch_down(int x, int y);
|
||||
void playlist_touch_move(int x, int y);
|
||||
void playlist_open(int idx); /* 打开第 idx 个歌单(下拉进歌单详情) */
|
||||
void playlist_open_pl(const ncm_playlist_t *pl); /* 打开「搜索来的歌单」详情(§23,复用链路) */
|
||||
void playlist_refresh(void);
|
||||
void playlist_tick(uint64_t now);/* UI tick:检查歌单 worker 是否完成并刷新 */
|
||||
|
||||
@@ -254,7 +283,8 @@ meow_view *player_view_create(void);
|
||||
int player_is_active(void);
|
||||
void player_on_tap(int x, int y);
|
||||
void player_tick(uint64_t now); /* 播放进度/歌词滚动 */
|
||||
void player_play_song(int idx); /* 从歌单第 idx 首开始播放 */
|
||||
void player_play_song(int idx); /* 从歌单窗口第 idx 首开始播放(queue=PLAYLIST) */
|
||||
void player_play_search_song(int i); /* 从搜索单曲第 i 首开始播放(queue=SEARCH,§23) */
|
||||
void player_pause_toggle(int on);
|
||||
void player_next(int dir); /* dir=+1 下一首 / -1 上一首 */
|
||||
void player_seek(int64_t ms);
|
||||
@@ -286,6 +316,7 @@ void search_touch_move(int x, int y);
|
||||
void search_open(void); /* 进搜索页 */
|
||||
void search_submit(const char *kw); /* 用户确认后发起搜索(风控:仅在确认时发) */
|
||||
void search_tick(uint64_t now); /* UI tick:检查搜索 worker 完成并刷新 */
|
||||
int search_swipe_left(void); /* 历史模式左滑删单条:1=已消费(不触返回) */
|
||||
|
||||
/* ---- logic ---- */
|
||||
int appstate_init(void); /* 初始化 ctx/data/cookie/设置;0=成功 */
|
||||
@@ -314,6 +345,29 @@ int appstate_song_fetch(int gidx, ncm_song_t *out); /* 任意全局下标物化
|
||||
void appstate_adopt_songs(int n); /* 收编外部已填的 songs[0..n)(搜索结果)为窗口 base=0,
|
||||
* 同步 song_ids/track_ids_count(播放上下文) */
|
||||
|
||||
/* ---- 播放队列来源(§23)---- */
|
||||
void appstate_set_queue(int src); /* NCM_QUEUE_PLAYLIST / NCM_QUEUE_SEARCH */
|
||||
int appstate_get_queue(void);
|
||||
int appstate_queue_count(void); /* 当前播放队列长度(按 queue_src) */
|
||||
int appstate_queue_song(int idx, ncm_song_t *out); /* 队列第 idx 首;0=ok(按 queue_src) */
|
||||
|
||||
/* ---- 搜索结果独立数组(§23;g_search_mu 保护,UI/worker 经此访问)---- */
|
||||
int appstate_search_count(int tab); /* tab: 0=单曲 1=歌单 */
|
||||
int appstate_search_offset(int tab); /* 下次 offset(=已加载条数) */
|
||||
int appstate_search_has_more(int tab); /* tab 是否还有下一页 */
|
||||
int appstate_search_song(int i, ncm_song_t *out); /* 单曲条 i;0=ok */
|
||||
int appstate_search_pl(int i, ncm_playlist_t *out); /* 歌单条 i;0=ok */
|
||||
void appstate_search_clear(int tab); /* 清空某 tab 结果(offset=0, total=-1, more=1) */
|
||||
void appstate_search_append_songs(int n, const ncm_song_t *s, int64_t total); /* 追加单曲(锁内) */
|
||||
void appstate_search_append_pls(int n, const ncm_playlist_t *p, int64_t total); /* 追加歌单 */
|
||||
|
||||
/* ---- 搜索历史(≤NCM_SEARCH_HIST_MAX,最新在前;meow_config 持久化)---- */
|
||||
int appstate_search_history_count(void);
|
||||
int appstate_search_history_get(int i, char *out, size_t sz); /* 1=存在 */
|
||||
void appstate_search_history_add(const char *kw); /* 去重 + 最新在前 + 截断 ≤12 */
|
||||
void appstate_search_history_remove(int i); /* 删第 i 条 */
|
||||
void appstate_search_history_clear(void);
|
||||
|
||||
/* account(app/logic/account.c,§20 会员态):account.json 落盘 + 刷新策略
|
||||
* (登录后/缓存超 1 天/到期后 各查一次,不轮询——风控)。 */
|
||||
void account_load(void); /* appstate_init 时读 account.json 恢复 vip 态 */
|
||||
|
||||
@@ -126,7 +126,8 @@ void player_manager_next(int dir)
|
||||
pthread_mutex_lock(&g_play.mu);
|
||||
int c = g_play.cur_song + dir;
|
||||
if (c < 0) c = 0;
|
||||
if (c >= g_st.track_ids_count) c = g_st.track_ids_count - 1; /* 滑窗:边界=全量索引数 */
|
||||
int cnt = appstate_queue_count(); /* §23:按队列来源(窗口全量 或 搜索条数) */
|
||||
if (c >= cnt) c = cnt - 1;
|
||||
if (c != g_play.cur_song) {
|
||||
g_play.cur_song = c;
|
||||
g_play.cmd = 0;
|
||||
@@ -387,9 +388,9 @@ static void run_song(int idx)
|
||||
/* §17 滑窗兼容:物化目标曲(窗内命中 / 盘缓存 / song_detail 单曲)并整体快照进
|
||||
* g_play.play_song——之后本曲所有字段读快照,窗口滑出当前播放曲不影响播放。 */
|
||||
ncm_song_t s;
|
||||
if (appstate_song_copy(idx, &s) != 0) {
|
||||
int fr = appstate_song_fetch(idx, &s);
|
||||
if (fr != NCM_OK) { set_err(fr, "load failed"); return; }
|
||||
if (appstate_queue_song(idx, &s) != 0) { /* §23:按 queue_src 取(窗口 或 搜索队列) */
|
||||
set_err(NCM_ERR_ARG, "load failed");
|
||||
return;
|
||||
}
|
||||
if (!still_current(idx)) return; /* 物化期间已切歌/停止:不写过期快照 */
|
||||
pthread_mutex_lock(&g_play.mu);
|
||||
@@ -571,7 +572,7 @@ static void *player_thread(void *arg)
|
||||
int st = g_play.state;
|
||||
pthread_mutex_unlock(&g_play.mu);
|
||||
|
||||
if (cur < 0 || cur >= g_st.track_ids_count || st == NCM_PC_STOPPED) {
|
||||
if (cur < 0 || cur >= appstate_queue_count() || st == NCM_PC_STOPPED) {
|
||||
usleep(60 * 1000);
|
||||
continue;
|
||||
}
|
||||
@@ -583,9 +584,10 @@ static void *player_thread(void *arg)
|
||||
int st2 = g_play.state;
|
||||
pthread_mutex_unlock(&g_play.mu);
|
||||
/* 自然播完且无新命令 → 顺延下一首;否则回到循环等待 */
|
||||
if (cur2 >= 0 && cur2 < g_st.track_ids_count && st2 != NCM_PC_STOPPED && cur2 == cur) {
|
||||
int qc = appstate_queue_count();
|
||||
if (cur2 >= 0 && cur2 < qc && st2 != NCM_PC_STOPPED && cur2 == cur) {
|
||||
int nx = cur + 1;
|
||||
if (nx < g_st.track_ids_count) { player_manager_next(1); }
|
||||
if (nx < qc) { player_manager_next(1); }
|
||||
else { player_manager_stop(); }
|
||||
} else {
|
||||
usleep(50 * 1000);
|
||||
@@ -616,7 +618,7 @@ static void *lyric_worker(void *arg)
|
||||
int have = g_play.play_song_valid;
|
||||
ncm_strlcpy(id, have ? g_play.play_song.id : "", sizeof id);
|
||||
pthread_mutex_unlock(&g_play.mu);
|
||||
if (idx < 0 || idx >= g_st.track_ids_count || !have) {
|
||||
if (idx < 0 || idx >= appstate_queue_count() || !have) {
|
||||
lyric_clear();
|
||||
pthread_mutex_lock(&g_lyrmu);
|
||||
g_lyr_song_id[0] = 0;
|
||||
|
||||
@@ -97,6 +97,9 @@ static int page_id(const char *name)
|
||||
if (strcmp(name, "playererr") == 0) return ACT_PLAYER; /* §18 错误态 */
|
||||
if (strcmp(name, "settings") == 0) return ACT_SETTINGS;
|
||||
if (strcmp(name, "search") == 0) return ACT_SEARCH;
|
||||
if (strcmp(name, "searchhist") == 0) return ACT_SEARCH; /* §23 空态=历史列表 */
|
||||
if (strcmp(name, "searchpl") == 0) return ACT_SEARCH; /* §23 歌单页签 */
|
||||
if (strcmp(name, "searchtap") == 0) return ACT_SEARCH; /* §23 误触抑制自检 */
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -179,26 +182,53 @@ int main(int argc, char **argv)
|
||||
g_st.cache_limit_gb = 10;
|
||||
ncm_cache_init(&g_st.ctx.cache, g_st.cache_dir); /* 初始化缓存目录(「占用」行读取)*/
|
||||
ncm_cache_set_limit_gb(&g_st.ctx.cache, g_st.cache_limit_gb);
|
||||
} else if (strcmp(name, "search") == 0) {
|
||||
} else if (strcmp(name, "search") == 0 || strcmp(name, "searchtap") == 0) {
|
||||
mock_playlists();
|
||||
static const char *sn[] = { "Song One", "Song Two", "Song Three" };
|
||||
static const char *ar[] = { "Artist A", "Artist B", "Artist C" };
|
||||
static const char *al[] = { "Album 1", "Album 2", "Album 3" };
|
||||
ncm_strlcpy(g_st.search_query, "test", sizeof g_st.search_query);
|
||||
g_st.search_count = 3;
|
||||
g_st.search_tab = 0;
|
||||
g_st.search_err = 0;
|
||||
g_st.search_song_count = 3; /* §23:结果写独立数组,不覆写窗口 */
|
||||
g_st.search_song_offset = 3;
|
||||
g_st.search_song_total = 3;
|
||||
g_st.search_song_more = 0;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
snprintf(g_st.songs[i].id, sizeof g_st.songs[i].id, "%ld", 1000L + i);
|
||||
snprintf(g_st.songs[i].name, sizeof g_st.songs[i].name, "%s", sn[i]);
|
||||
snprintf(g_st.songs[i].art_name, sizeof g_st.songs[i].art_name, "%s", ar[i]);
|
||||
snprintf(g_st.songs[i].album_name, sizeof g_st.songs[i].album_name, "%s", al[i]);
|
||||
g_st.songs[i].duration_ms = 180000 + i * 20000;
|
||||
g_st.songs[i].fee = 0;
|
||||
g_st.song_valid[i] = 1;
|
||||
g_st.song_ids[i] = 1000 + i;
|
||||
snprintf(g_st.search_songs[i].id, sizeof g_st.search_songs[i].id, "%ld", 1000L + i);
|
||||
snprintf(g_st.search_songs[i].name, sizeof g_st.search_songs[i].name, "%s", sn[i]);
|
||||
snprintf(g_st.search_songs[i].art_name, sizeof g_st.search_songs[i].art_name, "%s", ar[i]);
|
||||
snprintf(g_st.search_songs[i].album_name, sizeof g_st.search_songs[i].album_name, "%s", al[i]);
|
||||
g_st.search_songs[i].duration_ms = 180000 + i * 20000;
|
||||
g_st.search_songs[i].fee = 0;
|
||||
}
|
||||
g_st.song_count = 3;
|
||||
g_st.track_ids_count = 3;
|
||||
} else if (strcmp(name, "searchpl") == 0) { /* §23:歌单页签结果 */
|
||||
mock_playlists();
|
||||
static const char *pn[] = { "Playlist Alpha", "Playlist Beta" };
|
||||
ncm_strlcpy(g_st.search_query, "test", sizeof g_st.search_query);
|
||||
g_st.search_tab = 1; /* 1=歌单页签 */
|
||||
g_st.search_err = 0;
|
||||
g_st.search_pl_count = 2;
|
||||
g_st.search_pl_offset = 2;
|
||||
g_st.search_pl_total = 2;
|
||||
g_st.search_pl_more = 0;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
snprintf(g_st.search_pls[i].id, sizeof g_st.search_pls[i].id, "sp%d", i);
|
||||
snprintf(g_st.search_pls[i].name, sizeof g_st.search_pls[i].name, "%s", pn[i]);
|
||||
g_st.search_pls[i].track_count = 30 + i * 10;
|
||||
snprintf(g_st.search_pls[i].cover, sizeof g_st.search_pls[i].cover,
|
||||
"http://p1.music.126.net/pl%d.jpg", i);
|
||||
}
|
||||
} else if (strcmp(name, "searchhist") == 0) { /* §23:空态=历史列表 */
|
||||
mock_playlists();
|
||||
g_st.search_query[0] = 0; /* 未输入 */
|
||||
g_st.search_tab = 0;
|
||||
g_st.search_err = 0;
|
||||
appstate_search_history_clear(); /* 清已有,重建 3 条 */
|
||||
appstate_search_history_add("\u5468\u6770\u4f26 \u6674\u5929");
|
||||
appstate_search_history_add("Jay \u5553\u8868");
|
||||
appstate_search_history_add("lofi beat");
|
||||
appstate_search_history_add("Jay \u5553\u8868"); /* 去重 → 仍 3 条 */
|
||||
} else {
|
||||
mock_playlists();
|
||||
}
|
||||
@@ -209,7 +239,8 @@ int main(int argc, char **argv)
|
||||
else if (strcmp(name, "playlist") == 0) v = playlist_view_create();
|
||||
else if (page_id(name) == ACT_PLAYER) v = player_view_create(); /* player/lands/playerbuf/playererr */
|
||||
else if (strcmp(name, "settings") == 0) v = settings_view_create();
|
||||
else if (strcmp(name, "search") == 0) v = search_view_create();
|
||||
else if (strcmp(name, "search") == 0 || strcmp(name, "searchhist") == 0 ||
|
||||
strcmp(name, "searchpl") == 0 || strcmp(name, "searchtap") == 0) v = search_view_create();
|
||||
if (!v) { fprintf(stderr, "bad page\n"); return 1; }
|
||||
|
||||
if (strcmp(name, "grid") == 0) player_set_screen(1); /* 兼容占位(重做后恒竖屏) */
|
||||
@@ -250,9 +281,40 @@ int main(int argc, char **argv)
|
||||
if (page_id(name) == ACT_PLAYER) {
|
||||
player_tick(meow_ui_time_ms());
|
||||
}
|
||||
if (strcmp(name, "search") == 0) {
|
||||
if (strcmp(name, "search") == 0 || strcmp(name, "searchtap") == 0) {
|
||||
search_submit(g_st.search_query); /* 宿主:应用预置搜索结果 */
|
||||
}
|
||||
if (strcmp(name, "searchtap") == 0) {
|
||||
/* §23 误触抑制自检(真实页面 + 真实 SDK 手势管线):
|
||||
* ① 拖动(|Δy|>10)后抬手 → 不点行(was_drag 抑制,g_play.cur_song 不变);
|
||||
* ② 干净点击 → 播按下行(g_press_row=g_press_delta 阈值内,g_play.cur_song 变为该行)。 */
|
||||
ncm_rect_t rc = ncm_content_rect();
|
||||
int listy = rc.y + 168; /* field+btn+tab 布局:r_list.y = rc.y+168 */
|
||||
int x = 60;
|
||||
pthread_mutex_lock(&g_play.mu);
|
||||
g_play.cur_song = -1; g_play.state = NCM_PC_STOPPED;
|
||||
pthread_mutex_unlock(&g_play.mu);
|
||||
/* ② 干净点击 row0(小移动 6px ≤10)→ 播按下行 row0(scroll=0,命中准确) */
|
||||
search_touch_down(x, listy + 28);
|
||||
search_touch_move(x, listy + 34);
|
||||
int r0 = search_on_tap(x, listy + 34);
|
||||
pthread_mutex_lock(&g_play.mu); int played = g_play.cur_song; int qsrc = g_st.queue_src; pthread_mutex_unlock(&g_play.mu);
|
||||
int ok2 = (r0 == 1 && played == 0 && qsrc == NCM_QUEUE_SEARCH);
|
||||
/* 回到搜索页 + 复位播放态,再做 ① 拖动抑制 */
|
||||
meow_view_page_pop();
|
||||
pthread_mutex_lock(&g_play.mu); g_play.cur_song = -1; pthread_mutex_unlock(&g_play.mu);
|
||||
/* ① 拖动 row1(+30px >10)→ was_drag 抑制,不播(cur_song 不变) */
|
||||
search_touch_down(x, listy + 56 + 28);
|
||||
search_touch_move(x, listy + 56 + 28 + 30);
|
||||
pthread_mutex_lock(&g_play.mu); int before = g_play.cur_song; pthread_mutex_unlock(&g_play.mu);
|
||||
int r1 = search_on_tap(x, listy + 56 + 28 + 30);
|
||||
pthread_mutex_lock(&g_play.mu); int after = g_play.cur_song; pthread_mutex_unlock(&g_play.mu);
|
||||
int ok1 = (r1 == 1 && after == before);
|
||||
printf("searchtap: clean-play=%s (cur=%d qsrc=%d) drag-suppress=%s (cur %d->%d) -> %s\n",
|
||||
ok2 ? "PASS" : "FAIL", played, qsrc, ok1 ? "PASS" : "FAIL", before, after,
|
||||
(ok1 && ok2) ? "PASS" : "FAIL");
|
||||
if (!(ok1 && ok2)) return 1;
|
||||
}
|
||||
|
||||
meow_ui_shutdown();
|
||||
printf("dumped %s -> %s\n", name, dump);
|
||||
|
||||
@@ -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