fix: support archived thread search on android homepage
This commit is contained in:
@@ -1086,12 +1086,12 @@ public class MainActivity extends AppCompatActivity {
|
||||
return;
|
||||
}
|
||||
if ("folder_archive".equals(conversationType)
|
||||
|| (conversationSearchMode && isArchivedProjectThread(item))) {
|
||||
|| (conversationSearchMode && !item.optString("searchMatchLabel", "").isEmpty())) {
|
||||
if (folderKey.isEmpty()) {
|
||||
showMessage("缺少 folderKey");
|
||||
return;
|
||||
}
|
||||
openConversationFolder(folderKey, resolveConversationFolderName(item, finalDisplayRow));
|
||||
openConversationFolder(folderKey, resolveConversationFolderName(item, row));
|
||||
return;
|
||||
}
|
||||
if (projectId.isEmpty()) {
|
||||
@@ -1105,22 +1105,14 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isArchivedProjectThread(JSONObject item) {
|
||||
String folderKey = item.optString("folderKey", "").trim();
|
||||
if (folderKey.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return item.optInt("threadCount", 0) > 1;
|
||||
}
|
||||
|
||||
private static String buildConversationSearchLabel(JSONObject item, WechatSurfaceMapper.ConversationRow row) {
|
||||
if (row == null) {
|
||||
return "";
|
||||
}
|
||||
if (isArchivedProjectThread(item)) {
|
||||
String folderLabel = row.folderLabel == null ? "" : row.folderLabel.trim();
|
||||
if (!folderLabel.isEmpty()) {
|
||||
return folderLabel + " / " + row.threadTitle;
|
||||
if ("folder_archive".equals(item.optString("conversationType", ""))) {
|
||||
String matchedThreadTitle = item.optString("searchMatchLabel", "").trim();
|
||||
if (!matchedThreadTitle.isEmpty()) {
|
||||
return row.threadTitle + " / " + matchedThreadTitle;
|
||||
}
|
||||
}
|
||||
return row.threadTitle;
|
||||
@@ -1321,7 +1313,17 @@ public class MainActivity extends AppCompatActivity {
|
||||
continue;
|
||||
}
|
||||
if (matchesConversationQuery(item, query)) {
|
||||
filtered.put(item);
|
||||
JSONObject filteredItem = item;
|
||||
try {
|
||||
filteredItem = new JSONObject(item.toString());
|
||||
String matchLabel = resolveConversationSearchMatchLabel(item, query);
|
||||
if (!matchLabel.isEmpty()) {
|
||||
filteredItem.put("searchMatchLabel", matchLabel);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// Keep the original item if JSON cloning fails.
|
||||
}
|
||||
filtered.put(filteredItem);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
@@ -1426,6 +1428,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
if (query.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (!resolveConversationSearchMatchLabel(item, query).isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
String[] fields = new String[] {
|
||||
item.optString("projectTitle", ""),
|
||||
item.optString("threadTitle", ""),
|
||||
@@ -1441,6 +1446,26 @@ public class MainActivity extends AppCompatActivity {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String resolveConversationSearchMatchLabel(JSONObject item, String query) {
|
||||
if (item == null || query == null || query.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
if (!"folder_archive".equals(item.optString("conversationType", ""))) {
|
||||
return "";
|
||||
}
|
||||
JSONArray searchAliases = item.optJSONArray("searchAliases");
|
||||
if (searchAliases == null) {
|
||||
return "";
|
||||
}
|
||||
for (int i = 0; i < searchAliases.length(); i++) {
|
||||
String alias = searchAliases.optString(i, "").trim();
|
||||
if (!alias.isEmpty() && alias.toLowerCase().contains(query)) {
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private void renderDevicesRoot() {
|
||||
if (screenList == null) {
|
||||
return;
|
||||
|
||||
@@ -49,6 +49,27 @@ public class MainActivityConversationSearchTest {
|
||||
assertEquals("p2", filteredByFolder.optJSONObject(0).optString("projectId", ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterConversationItemsMatchesFolderArchiveSearchAliases() throws Exception {
|
||||
JSONArray source = new JSONArray()
|
||||
.put(new JSONObject()
|
||||
.put("projectId", "folder-boss")
|
||||
.put("conversationType", "folder_archive")
|
||||
.put("folderKey", "mac-studio:boss")
|
||||
.put("projectTitle", "Boss")
|
||||
.put("threadTitle", "Boss")
|
||||
.put("folderLabel", "2 个线程 · 最近:发布回滚")
|
||||
.put("searchAliases", new JSONArray().put("发布回滚").put("Android UI 收尾"))
|
||||
.put("lastMessagePreview", "最近:发布回滚")
|
||||
.put("latestReplyLabel", "11:00"));
|
||||
|
||||
JSONArray filtered = MainActivity.filterConversationItems(source, "发布回滚");
|
||||
|
||||
assertEquals(1, filtered.length());
|
||||
assertEquals("folder-boss", filtered.optJSONObject(0).optString("projectId", ""));
|
||||
assertEquals("发布回滚", filtered.optJSONObject(0).optString("searchMatchLabel", ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conversationsHeader_usesSearchIconAndPlusButton() throws Exception {
|
||||
MainActivity activity = Robolectric.buildActivity(MainActivity.class).setup().get();
|
||||
@@ -103,15 +124,15 @@ public class MainActivityConversationSearchTest {
|
||||
MainActivity activity = Robolectric.buildActivity(MainActivity.class).setup().get();
|
||||
ReflectionHelpers.setField(activity, "conversationsData", new JSONArray()
|
||||
.put(new JSONObject()
|
||||
.put("projectId", "boss-thread-1")
|
||||
.put("conversationType", "single_device")
|
||||
.put("projectId", "folder-boss")
|
||||
.put("conversationType", "folder_archive")
|
||||
.put("folderKey", "mac-studio:boss")
|
||||
.put("folderLabel", "Boss")
|
||||
.put("projectTitle", "Boss")
|
||||
.put("threadTitle", "发布回滚")
|
||||
.put("threadTitle", "Boss")
|
||||
.put("lastMessagePreview", "最近:发布回滚")
|
||||
.put("latestReplyLabel", "11:00")
|
||||
.put("threadCount", 2)));
|
||||
.put("searchAliases", new JSONArray().put("发布回滚").put("Android UI 收尾"))));
|
||||
|
||||
ReflectionHelpers.callInstanceMethod(activity, "showContent");
|
||||
Shadows.shadowOf(activity.getMainLooper()).idle();
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface ConversationItem {
|
||||
folderLabel: string;
|
||||
folderKey?: string;
|
||||
threadCount?: number;
|
||||
searchAliases?: string[];
|
||||
preview: string;
|
||||
lastMessagePreview: string;
|
||||
activityIconCount: number;
|
||||
@@ -467,6 +468,19 @@ function sortConversationItems(items: ConversationItem[]) {
|
||||
});
|
||||
}
|
||||
|
||||
function buildFolderSearchAliases(items: ConversationItem[]) {
|
||||
const aliases: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const item of items) {
|
||||
const alias = (item.threadTitle?.trim() || item.projectTitle?.trim() || "").trim();
|
||||
if (!alias || seen.has(alias)) continue;
|
||||
aliases.push(alias);
|
||||
seen.add(alias);
|
||||
if (aliases.length >= 4) break;
|
||||
}
|
||||
return aliases.length > 0 ? aliases : undefined;
|
||||
}
|
||||
|
||||
export function getConversationItems(state: BossState): ConversationItem[] {
|
||||
const conversations = state.projects.map((project) => buildConversationItem(state, project));
|
||||
|
||||
@@ -541,6 +555,7 @@ export function getConversationHomeItems(state: BossState): ConversationItem[] {
|
||||
folderLabel: recentThreadLabel ? `${items.length} 个线程 · 最近:${recentThreadLabel}` : `${items.length} 个线程`,
|
||||
folderKey,
|
||||
threadCount: items.length,
|
||||
searchAliases: buildFolderSearchAliases(items),
|
||||
preview:
|
||||
latestItem.preview || `包含 ${items.length} 个线程,最近活跃:《${latestItem.threadTitle}》`,
|
||||
lastMessagePreview:
|
||||
|
||||
@@ -424,6 +424,7 @@ test("folder archive homepage rows keep the project title, compact subtitle, and
|
||||
const folder = getConversationHomeItems(state).find((item) => item.conversationType === "folder_archive");
|
||||
|
||||
assert.ok(folder, "expected grouped folder archive item");
|
||||
assert.deepEqual(folder?.searchAliases, ["发布回滚", "归档确认"]);
|
||||
const presentation = getConversationListItemPresentation({
|
||||
...folder!,
|
||||
projectTitle: "项目标题",
|
||||
@@ -435,6 +436,25 @@ test("folder archive homepage rows keep the project title, compact subtitle, and
|
||||
assert.equal(presentation.href, "/conversations/folders/mac-studio%3Aboss");
|
||||
});
|
||||
|
||||
test("folder archive search aliases stay bounded to the latest thread titles", async () => {
|
||||
await setup();
|
||||
const state = await readState();
|
||||
|
||||
state.projects = state.projects.filter((project) => project.id === "master-agent");
|
||||
state.projects.push(
|
||||
buildImportedThreadProject("mac-studio", "boss-thread-1", "Boss", "boss", "发布回滚", "thread-1", "2026-03-30T14:00:00+08:00"),
|
||||
buildImportedThreadProject("mac-studio", "boss-thread-2", "Boss", "boss", "Android UI 收尾", "thread-2", "2026-03-30T13:00:00+08:00"),
|
||||
buildImportedThreadProject("mac-studio", "boss-thread-3", "Boss", "boss", "日志收口", "thread-3", "2026-03-30T12:00:00+08:00"),
|
||||
buildImportedThreadProject("mac-studio", "boss-thread-4", "Boss", "boss", "网络修复", "thread-4", "2026-03-30T11:00:00+08:00"),
|
||||
buildImportedThreadProject("mac-studio", "boss-thread-5", "Boss", "boss", "审阅确认", "thread-5", "2026-03-30T10:00:00+08:00"),
|
||||
);
|
||||
|
||||
const folder = getConversationHomeItems(state).find((item) => item.conversationType === "folder_archive");
|
||||
|
||||
assert.ok(folder, "expected grouped folder archive item");
|
||||
assert.deepEqual(folder?.searchAliases, ["发布回滚", "Android UI 收尾", "日志收口", "网络修复"]);
|
||||
});
|
||||
|
||||
test("conversation items expose context status while keeping idle activity silent", async () => {
|
||||
await setup();
|
||||
const state = await readState();
|
||||
|
||||
Reference in New Issue
Block a user