fix: group fallback conversation feed into folder archives on android
This commit is contained in:
@@ -409,9 +409,14 @@ public class MainActivity extends AppCompatActivity {
|
||||
final boolean finalConversationsOk = conversationsOk;
|
||||
runOnUiThread(() -> {
|
||||
sessionData = session;
|
||||
JSONArray refreshedConversations = finalConversations == null
|
||||
? null
|
||||
: WechatSurfaceMapper.normalizeConversationHomeFeed(
|
||||
finalConversations.json.optJSONArray("conversations")
|
||||
);
|
||||
conversationsData = WechatSurfaceMapper.resolveRefreshValue(
|
||||
conversationsData,
|
||||
finalConversations == null ? null : finalConversations.json.optJSONArray("conversations"),
|
||||
refreshedConversations,
|
||||
finalConversationsOk
|
||||
);
|
||||
maybeApplyPreferredEntry();
|
||||
@@ -699,9 +704,14 @@ public class MainActivity extends AppCompatActivity {
|
||||
final boolean finalSettingsOk = settingsOk;
|
||||
runOnUiThread(() -> {
|
||||
sessionData = finalSession;
|
||||
JSONArray refreshedConversations = finalConversations == null
|
||||
? null
|
||||
: WechatSurfaceMapper.normalizeConversationHomeFeed(
|
||||
finalConversations.json.optJSONArray("conversations")
|
||||
);
|
||||
conversationsData = WechatSurfaceMapper.resolveRefreshValue(
|
||||
conversationsData,
|
||||
finalConversations == null ? null : finalConversations.json.optJSONArray("conversations"),
|
||||
refreshedConversations,
|
||||
finalConversationsOk
|
||||
);
|
||||
devicesData = WechatSurfaceMapper.resolveRefreshValue(
|
||||
|
||||
@@ -5,7 +5,11 @@ import org.json.JSONArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class WechatSurfaceMapper {
|
||||
private static final List<String> ROOT_TAB_LABELS = Arrays.asList(
|
||||
@@ -255,6 +259,310 @@ public final class WechatSurfaceMapper {
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
public static JSONArray normalizeConversationHomeFeed(JSONArray source) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
JSONArray passthrough = new JSONArray();
|
||||
Map<String, List<JSONObject>> grouped = new LinkedHashMap<>();
|
||||
for (int index = 0; index < source.length(); index += 1) {
|
||||
JSONObject item = source.optJSONObject(index);
|
||||
if (item == null) {
|
||||
continue;
|
||||
}
|
||||
if (!"single_device".equals(item.optString("conversationType", ""))) {
|
||||
passthrough.put(copyJson(item));
|
||||
continue;
|
||||
}
|
||||
String folderKey = item.optString("folderKey", "").trim();
|
||||
if (folderKey.isEmpty()) {
|
||||
passthrough.put(copyJson(item));
|
||||
continue;
|
||||
}
|
||||
List<JSONObject> items = grouped.get(folderKey);
|
||||
if (items == null) {
|
||||
items = new ArrayList<>();
|
||||
grouped.put(folderKey, items);
|
||||
}
|
||||
items.add(copyJson(item));
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<JSONObject>> entry : grouped.entrySet()) {
|
||||
List<JSONObject> items = entry.getValue();
|
||||
if (items == null || items.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (items.size() == 1) {
|
||||
passthrough.put(items.get(0));
|
||||
continue;
|
||||
}
|
||||
passthrough.put(buildFolderArchiveItem(entry.getKey(), items));
|
||||
}
|
||||
return sortConversationItems(passthrough);
|
||||
}
|
||||
|
||||
private static JSONObject buildFolderArchiveItem(String folderKey, List<JSONObject> items) {
|
||||
List<JSONObject> sortedByLatest = new ArrayList<>(items);
|
||||
sortedByLatest.sort((left, right) -> compareConversationFreshness(right, left));
|
||||
JSONObject latest = sortedByLatest.get(0);
|
||||
JSONObject topContext = selectTopContextItem(items);
|
||||
JSONArray searchAliases = new JSONArray();
|
||||
JSONArray searchTargetProjectIds = new JSONArray();
|
||||
for (JSONObject item : sortedByLatest) {
|
||||
String alias = firstNonBlank(item.optString("threadTitle", ""), item.optString("projectTitle", ""));
|
||||
String projectId = item.optString("projectId", "").trim();
|
||||
if (!alias.isEmpty() && !projectId.isEmpty()) {
|
||||
searchAliases.put(alias);
|
||||
searchTargetProjectIds.put(projectId);
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject folder = new JSONObject();
|
||||
putIfNotEmpty(folder, "conversationId", "folder-" + folderKey);
|
||||
putIfNotEmpty(folder, "conversationType", "folder_archive");
|
||||
putIfNotEmpty(folder, "projectId", folderKey);
|
||||
String projectTitle = firstNonBlank(
|
||||
latest.optString("folderLabel", ""),
|
||||
latest.optString("projectTitle", ""),
|
||||
latest.optString("threadTitle", "")
|
||||
);
|
||||
putIfNotEmpty(folder, "projectTitle", projectTitle);
|
||||
putIfNotEmpty(folder, "threadTitle", projectTitle);
|
||||
String recentThread = latest.optString("threadTitle", "").trim();
|
||||
putIfNotEmpty(
|
||||
folder,
|
||||
"folderLabel",
|
||||
recentThread.isEmpty()
|
||||
? items.size() + " 个线程"
|
||||
: items.size() + " 个线程 · 最近:" + recentThread
|
||||
);
|
||||
putIfNotEmpty(folder, "folderKey", folderKey);
|
||||
safePut(folder, "threadCount", items.size());
|
||||
if (searchAliases.length() > 0) {
|
||||
safePut(folder, "searchAliases", searchAliases);
|
||||
safePut(folder, "searchTargetProjectIds", searchTargetProjectIds);
|
||||
}
|
||||
putIfNotEmpty(folder, "preview", firstNonBlank(latest.optString("preview", ""), latest.optString("lastMessagePreview", "")));
|
||||
putIfNotEmpty(
|
||||
folder,
|
||||
"lastMessagePreview",
|
||||
firstNonBlank(latest.optString("lastMessagePreview", ""), latest.optString("preview", ""))
|
||||
);
|
||||
safePut(folder, "activityIconCount", Math.max(0, Math.min(4, sumInt(items, "activityIconCount"))));
|
||||
if (hasPinnedLabel(items)) {
|
||||
safePut(folder, "topPinnedLabel", "置顶");
|
||||
}
|
||||
safePut(folder, "manualPinned", hasManualPinned(items));
|
||||
putIfNotEmpty(folder, "latestReplyAt", latest.optString("latestReplyAt", ""));
|
||||
putIfNotEmpty(folder, "latestReplyLabel", latest.optString("latestReplyLabel", ""));
|
||||
safePut(folder, "unreadCount", sumInt(items, "unreadCount"));
|
||||
putIfNotEmpty(folder, "riskLevel", resolveHighestRisk(items));
|
||||
safePut(folder, "activeDeviceCount", Math.max(1, latest.optInt("activeDeviceCount", 1)));
|
||||
JSONArray deviceNamesPreview = latest.optJSONArray("deviceNamesPreview");
|
||||
if (deviceNamesPreview != null) {
|
||||
safePut(folder, "deviceNamesPreview", copyArray(deviceNamesPreview));
|
||||
}
|
||||
JSONObject avatar = latest.optJSONObject("avatar");
|
||||
safePut(folder, "avatar", avatar == null ? new JSONObject() : copyJson(avatar));
|
||||
JSONObject indicator = topContext == null
|
||||
? buildContextIndicator(100, "safe")
|
||||
: copyJson(topContext.optJSONObject("contextBudgetIndicator"));
|
||||
if (indicator == null) {
|
||||
indicator = buildContextIndicator(100, "safe");
|
||||
}
|
||||
safePut(indicator, "visible", true);
|
||||
safePut(indicator, "style", "ring_percent");
|
||||
safePut(folder, "contextBudgetIndicator", indicator);
|
||||
putIfNotEmpty(folder, "contextBudgetSourceNodeId", topContext == null ? "" : topContext.optString("contextBudgetSourceNodeId", ""));
|
||||
putIfNotEmpty(folder, "contextBudgetUpdatedAt", topContext == null ? "" : topContext.optString("contextBudgetUpdatedAt", ""));
|
||||
safePut(folder, "mustFinishBeforeCompaction", topContext != null && topContext.optBoolean("mustFinishBeforeCompaction", false));
|
||||
return folder;
|
||||
}
|
||||
|
||||
private static JSONArray sortConversationItems(JSONArray source) {
|
||||
List<JSONObject> items = new ArrayList<>();
|
||||
for (int index = 0; index < source.length(); index += 1) {
|
||||
JSONObject item = source.optJSONObject(index);
|
||||
if (item != null) {
|
||||
items.add(item);
|
||||
}
|
||||
}
|
||||
items.sort((left, right) -> {
|
||||
boolean leftPinned = "置顶".equals(left.optString("topPinnedLabel", "")) || left.optBoolean("manualPinned", false);
|
||||
boolean rightPinned = "置顶".equals(right.optString("topPinnedLabel", "")) || right.optBoolean("manualPinned", false);
|
||||
if (leftPinned != rightPinned) {
|
||||
return leftPinned ? -1 : 1;
|
||||
}
|
||||
return compareConversationFreshness(right, left);
|
||||
});
|
||||
JSONArray sorted = new JSONArray();
|
||||
for (JSONObject item : items) {
|
||||
sorted.put(item);
|
||||
}
|
||||
return sorted;
|
||||
}
|
||||
|
||||
private static int compareConversationFreshness(JSONObject left, JSONObject right) {
|
||||
String leftAt = left.optString("latestReplyAt", "");
|
||||
String rightAt = right.optString("latestReplyAt", "");
|
||||
if (!leftAt.equals(rightAt)) {
|
||||
return leftAt.compareTo(rightAt);
|
||||
}
|
||||
return left.optString("projectId", "").compareTo(right.optString("projectId", ""));
|
||||
}
|
||||
|
||||
private static JSONObject selectTopContextItem(List<JSONObject> items) {
|
||||
List<JSONObject> visible = new ArrayList<>();
|
||||
for (JSONObject item : items) {
|
||||
JSONObject indicator = item.optJSONObject("contextBudgetIndicator");
|
||||
if (indicator != null && indicator.optBoolean("visible", false)) {
|
||||
visible.add(item);
|
||||
}
|
||||
}
|
||||
if (visible.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Collections.sort(visible, new Comparator<JSONObject>() {
|
||||
@Override
|
||||
public int compare(JSONObject left, JSONObject right) {
|
||||
boolean leftMustFinish = left.optBoolean("mustFinishBeforeCompaction", false);
|
||||
boolean rightMustFinish = right.optBoolean("mustFinishBeforeCompaction", false);
|
||||
if (leftMustFinish != rightMustFinish) {
|
||||
return leftMustFinish ? -1 : 1;
|
||||
}
|
||||
String leftLevel = contextLevel(left);
|
||||
String rightLevel = contextLevel(right);
|
||||
if (contextLevelPriority(leftLevel) != contextLevelPriority(rightLevel)) {
|
||||
return Integer.compare(contextLevelPriority(leftLevel), contextLevelPriority(rightLevel));
|
||||
}
|
||||
return compareConversationFreshness(right, left);
|
||||
}
|
||||
});
|
||||
return visible.get(0);
|
||||
}
|
||||
|
||||
private static String resolveHighestRisk(List<JSONObject> items) {
|
||||
boolean hasMedium = false;
|
||||
for (JSONObject item : items) {
|
||||
String risk = item.optString("riskLevel", "low");
|
||||
if ("high".equals(risk)) {
|
||||
return "high";
|
||||
}
|
||||
if ("medium".equals(risk)) {
|
||||
hasMedium = true;
|
||||
}
|
||||
}
|
||||
return hasMedium ? "medium" : "low";
|
||||
}
|
||||
|
||||
private static boolean hasPinnedLabel(List<JSONObject> items) {
|
||||
for (JSONObject item : items) {
|
||||
if ("置顶".equals(item.optString("topPinnedLabel", ""))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean hasManualPinned(List<JSONObject> items) {
|
||||
for (JSONObject item : items) {
|
||||
if (item.optBoolean("manualPinned", false)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int sumInt(List<JSONObject> items, String key) {
|
||||
int total = 0;
|
||||
for (JSONObject item : items) {
|
||||
total += item.optInt(key, 0);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
private static String contextLevel(JSONObject item) {
|
||||
JSONObject indicator = item.optJSONObject("contextBudgetIndicator");
|
||||
if (indicator == null) {
|
||||
return "safe";
|
||||
}
|
||||
return indicator.optString("level", "safe");
|
||||
}
|
||||
|
||||
private static int contextLevelPriority(String level) {
|
||||
switch (level) {
|
||||
case "critical":
|
||||
return 0;
|
||||
case "urgent":
|
||||
return 1;
|
||||
case "watch":
|
||||
return 2;
|
||||
default:
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
private static JSONObject buildContextIndicator(int percent, String level) {
|
||||
JSONObject indicator = new JSONObject();
|
||||
safePut(indicator, "visible", true);
|
||||
safePut(indicator, "style", "ring_percent");
|
||||
safePut(indicator, "percent", percent);
|
||||
safePut(indicator, "level", level);
|
||||
return indicator;
|
||||
}
|
||||
|
||||
private static void putIfNotEmpty(JSONObject target, String key, String value) {
|
||||
if (target == null || value == null || value.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
safePut(target, key, value);
|
||||
}
|
||||
|
||||
private static JSONObject copyJson(JSONObject source) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new JSONObject(source.toString());
|
||||
} catch (Exception error) {
|
||||
return new JSONObject();
|
||||
}
|
||||
}
|
||||
|
||||
private static JSONArray copyArray(JSONArray source) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new JSONArray(source.toString());
|
||||
} catch (Exception error) {
|
||||
return new JSONArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static void safePut(JSONObject target, String key, Object value) {
|
||||
if (target == null || key == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
target.put(key, value);
|
||||
} catch (Exception ignored) {
|
||||
// Best effort normalization for fallback feed.
|
||||
}
|
||||
}
|
||||
|
||||
private static String firstNonBlank(String... values) {
|
||||
if (values == null) {
|
||||
return "";
|
||||
}
|
||||
for (String value : values) {
|
||||
if (value != null && !value.trim().isEmpty()) {
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static final class RootTopAction {
|
||||
public final String label;
|
||||
public final boolean primaryStyle;
|
||||
|
||||
@@ -241,7 +241,7 @@ public class MainActivityRealtimeTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refreshConversationsData_fallsBackToFlatConversationsFeedWhenHomeFeedFails() throws Exception {
|
||||
public void refreshConversationsData_groupsFlatFallbackFeedWhenHomeFeedFails() throws Exception {
|
||||
MainActivity activity = Robolectric.buildActivity(MainActivity.class).setup().resume().get();
|
||||
Shadows.shadowOf(activity.getMainLooper()).idle();
|
||||
|
||||
@@ -253,16 +253,18 @@ public class MainActivityRealtimeTest {
|
||||
Shadows.shadowOf(activity.getMainLooper()).idle();
|
||||
|
||||
activity.refreshConversationsData();
|
||||
waitFor(() -> apiClient.homeCalls > 0 && apiClient.conversationsCalls > 0);
|
||||
waitFor(() -> apiClient.homeCalls > 0 && apiClient.conversationsCalls > 0 && hasConversationData(activity));
|
||||
|
||||
assertEquals(1, apiClient.homeCalls);
|
||||
assertEquals(1, apiClient.conversationsCalls);
|
||||
JSONArray conversationsData = ReflectionHelpers.getField(activity, "conversationsData");
|
||||
assertEquals("flat-thread", conversationsData.optJSONObject(0).optString("projectId", ""));
|
||||
assertEquals("folder_archive", conversationsData.optJSONObject(0).optString("conversationType", ""));
|
||||
assertEquals("mac-studio:boss", conversationsData.optJSONObject(0).optString("projectId", ""));
|
||||
assertEquals(2, conversationsData.optJSONObject(0).optInt("threadCount", 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refreshConversationsData_fallsBackToFlatConversationsFeedWhenHomeFeedThrowsIOException() throws Exception {
|
||||
public void refreshConversationsData_groupsFlatFallbackFeedWhenHomeFeedThrowsIOException() throws Exception {
|
||||
MainActivity activity = Robolectric.buildActivity(MainActivity.class).setup().resume().get();
|
||||
Shadows.shadowOf(activity.getMainLooper()).idle();
|
||||
|
||||
@@ -274,12 +276,14 @@ public class MainActivityRealtimeTest {
|
||||
Shadows.shadowOf(activity.getMainLooper()).idle();
|
||||
|
||||
activity.refreshConversationsData();
|
||||
waitFor(() -> apiClient.homeCalls > 0 && apiClient.conversationsCalls > 0);
|
||||
waitFor(() -> apiClient.homeCalls > 0 && apiClient.conversationsCalls > 0 && hasConversationData(activity));
|
||||
|
||||
assertEquals(1, apiClient.homeCalls);
|
||||
assertEquals(1, apiClient.conversationsCalls);
|
||||
JSONArray conversationsData = ReflectionHelpers.getField(activity, "conversationsData");
|
||||
assertEquals("flat-thread", conversationsData.optJSONObject(0).optString("projectId", ""));
|
||||
assertEquals("folder_archive", conversationsData.optJSONObject(0).optString("conversationType", ""));
|
||||
assertEquals("mac-studio:boss", conversationsData.optJSONObject(0).optString("projectId", ""));
|
||||
assertEquals(2, conversationsData.optJSONObject(0).optInt("threadCount", 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -306,7 +310,7 @@ public class MainActivityRealtimeTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refreshAllData_fallsBackToFlatConversationsFeedWhenHomeFeedFails() throws Exception {
|
||||
public void refreshAllData_groupsFlatFallbackFeedWhenHomeFeedFails() throws Exception {
|
||||
MainActivity activity = Robolectric.buildActivity(MainActivity.class).setup().resume().get();
|
||||
Shadows.shadowOf(activity.getMainLooper()).idle();
|
||||
|
||||
@@ -322,16 +326,18 @@ public class MainActivityRealtimeTest {
|
||||
"refreshAllData",
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, new JSONObject())
|
||||
);
|
||||
waitFor(() -> apiClient.homeCalls > 0 && apiClient.conversationsCalls > 0);
|
||||
waitFor(() -> apiClient.homeCalls > 0 && apiClient.conversationsCalls > 0 && hasConversationData(activity));
|
||||
|
||||
assertEquals(1, apiClient.homeCalls);
|
||||
assertEquals(1, apiClient.conversationsCalls);
|
||||
JSONArray conversationsData = ReflectionHelpers.getField(activity, "conversationsData");
|
||||
assertEquals("flat-thread", conversationsData.optJSONObject(0).optString("projectId", ""));
|
||||
assertEquals("folder_archive", conversationsData.optJSONObject(0).optString("conversationType", ""));
|
||||
assertEquals("mac-studio:boss", conversationsData.optJSONObject(0).optString("projectId", ""));
|
||||
assertEquals(2, conversationsData.optJSONObject(0).optInt("threadCount", 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refreshAllData_fallsBackToFlatConversationsFeedWhenHomeFeedThrowsIOException() throws Exception {
|
||||
public void refreshAllData_groupsFlatFallbackFeedWhenHomeFeedThrowsIOException() throws Exception {
|
||||
MainActivity activity = Robolectric.buildActivity(MainActivity.class).setup().resume().get();
|
||||
Shadows.shadowOf(activity.getMainLooper()).idle();
|
||||
|
||||
@@ -347,12 +353,14 @@ public class MainActivityRealtimeTest {
|
||||
"refreshAllData",
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, new JSONObject())
|
||||
);
|
||||
waitFor(() -> apiClient.homeCalls > 0 && apiClient.conversationsCalls > 0);
|
||||
waitFor(() -> apiClient.homeCalls > 0 && apiClient.conversationsCalls > 0 && hasConversationData(activity));
|
||||
|
||||
assertEquals(1, apiClient.homeCalls);
|
||||
assertEquals(1, apiClient.conversationsCalls);
|
||||
JSONArray conversationsData = ReflectionHelpers.getField(activity, "conversationsData");
|
||||
assertEquals("flat-thread", conversationsData.optJSONObject(0).optString("projectId", ""));
|
||||
assertEquals("folder_archive", conversationsData.optJSONObject(0).optString("conversationType", ""));
|
||||
assertEquals("mac-studio:boss", conversationsData.optJSONObject(0).optString("projectId", ""));
|
||||
assertEquals(2, conversationsData.optJSONObject(0).optInt("threadCount", 0));
|
||||
}
|
||||
|
||||
private static void waitFor(BooleanSupplier condition) throws Exception {
|
||||
@@ -367,6 +375,11 @@ public class MainActivityRealtimeTest {
|
||||
throw new AssertionError("condition not met before timeout");
|
||||
}
|
||||
|
||||
private static boolean hasConversationData(MainActivity activity) {
|
||||
JSONArray conversationsData = ReflectionHelpers.getField(activity, "conversationsData");
|
||||
return conversationsData != null && conversationsData.length() > 0;
|
||||
}
|
||||
|
||||
public static class TestMainActivity extends MainActivity {
|
||||
int conversationRefreshCount;
|
||||
int deviceRefreshCount;
|
||||
@@ -455,14 +468,29 @@ public class MainActivityRealtimeTest {
|
||||
}
|
||||
|
||||
private static JSONArray buildFlatConversations() throws org.json.JSONException {
|
||||
return new JSONArray().put(new JSONObject()
|
||||
.put("projectId", "flat-thread")
|
||||
.put("conversationType", "single_device")
|
||||
.put("projectTitle", "发布回滚")
|
||||
.put("threadTitle", "发布回滚")
|
||||
.put("folderLabel", "Boss")
|
||||
.put("lastMessagePreview", "最近:发布回滚")
|
||||
.put("latestReplyLabel", "11:00"));
|
||||
return new JSONArray()
|
||||
.put(new JSONObject()
|
||||
.put("projectId", "thread-revert")
|
||||
.put("conversationType", "single_device")
|
||||
.put("projectTitle", "发布回滚")
|
||||
.put("threadTitle", "发布回滚")
|
||||
.put("folderLabel", "Boss")
|
||||
.put("folderKey", "mac-studio:boss")
|
||||
.put("lastMessagePreview", "最近:发布回滚")
|
||||
.put("latestReplyAt", "2026-04-06T10:00:00.000Z")
|
||||
.put("latestReplyLabel", "11:00")
|
||||
.put("contextBudgetIndicator", new JSONObject().put("visible", true).put("style", "ring_percent").put("percent", 80).put("level", "watch")))
|
||||
.put(new JSONObject()
|
||||
.put("projectId", "thread-ui")
|
||||
.put("conversationType", "single_device")
|
||||
.put("projectTitle", "Android UI 收尾")
|
||||
.put("threadTitle", "Android UI 收尾")
|
||||
.put("folderLabel", "Boss")
|
||||
.put("folderKey", "mac-studio:boss")
|
||||
.put("lastMessagePreview", "最近:Android UI 收尾")
|
||||
.put("latestReplyAt", "2026-04-06T09:59:00.000Z")
|
||||
.put("latestReplyLabel", "10:59")
|
||||
.put("contextBudgetIndicator", new JSONObject().put("visible", true).put("style", "ring_percent").put("percent", 95).put("level", "safe")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,14 +571,29 @@ public class MainActivityRealtimeTest {
|
||||
}
|
||||
|
||||
private static JSONArray buildFlatConversations() throws org.json.JSONException {
|
||||
return new JSONArray().put(new JSONObject()
|
||||
.put("projectId", "flat-thread")
|
||||
.put("conversationType", "single_device")
|
||||
.put("projectTitle", "发布回滚")
|
||||
.put("threadTitle", "发布回滚")
|
||||
.put("folderLabel", "Boss")
|
||||
.put("lastMessagePreview", "最近:发布回滚")
|
||||
.put("latestReplyLabel", "11:00"));
|
||||
return new JSONArray()
|
||||
.put(new JSONObject()
|
||||
.put("projectId", "thread-revert")
|
||||
.put("conversationType", "single_device")
|
||||
.put("projectTitle", "发布回滚")
|
||||
.put("threadTitle", "发布回滚")
|
||||
.put("folderLabel", "Boss")
|
||||
.put("folderKey", "mac-studio:boss")
|
||||
.put("lastMessagePreview", "最近:发布回滚")
|
||||
.put("latestReplyAt", "2026-04-06T10:00:00.000Z")
|
||||
.put("latestReplyLabel", "11:00")
|
||||
.put("contextBudgetIndicator", new JSONObject().put("visible", true).put("style", "ring_percent").put("percent", 80).put("level", "watch")))
|
||||
.put(new JSONObject()
|
||||
.put("projectId", "thread-ui")
|
||||
.put("conversationType", "single_device")
|
||||
.put("projectTitle", "Android UI 收尾")
|
||||
.put("threadTitle", "Android UI 收尾")
|
||||
.put("folderLabel", "Boss")
|
||||
.put("folderKey", "mac-studio:boss")
|
||||
.put("lastMessagePreview", "最近:Android UI 收尾")
|
||||
.put("latestReplyAt", "2026-04-06T09:59:00.000Z")
|
||||
.put("latestReplyLabel", "10:59")
|
||||
.put("contextBudgetIndicator", new JSONObject().put("visible", true).put("style", "ring_percent").put("percent", 95).put("level", "safe")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,14 +659,29 @@ public class MainActivityRealtimeTest {
|
||||
}
|
||||
|
||||
private static JSONArray buildFlatConversations() throws org.json.JSONException {
|
||||
return new JSONArray().put(new JSONObject()
|
||||
.put("projectId", "flat-thread")
|
||||
.put("conversationType", "single_device")
|
||||
.put("projectTitle", "发布回滚")
|
||||
.put("threadTitle", "发布回滚")
|
||||
.put("folderLabel", "Boss")
|
||||
.put("lastMessagePreview", "最近:发布回滚")
|
||||
.put("latestReplyLabel", "11:00"));
|
||||
return new JSONArray()
|
||||
.put(new JSONObject()
|
||||
.put("projectId", "thread-revert")
|
||||
.put("conversationType", "single_device")
|
||||
.put("projectTitle", "发布回滚")
|
||||
.put("threadTitle", "发布回滚")
|
||||
.put("folderLabel", "Boss")
|
||||
.put("folderKey", "mac-studio:boss")
|
||||
.put("lastMessagePreview", "最近:发布回滚")
|
||||
.put("latestReplyAt", "2026-04-06T10:00:00.000Z")
|
||||
.put("latestReplyLabel", "11:00")
|
||||
.put("contextBudgetIndicator", new JSONObject().put("visible", true).put("style", "ring_percent").put("percent", 80).put("level", "watch")))
|
||||
.put(new JSONObject()
|
||||
.put("projectId", "thread-ui")
|
||||
.put("conversationType", "single_device")
|
||||
.put("projectTitle", "Android UI 收尾")
|
||||
.put("threadTitle", "Android UI 收尾")
|
||||
.put("folderLabel", "Boss")
|
||||
.put("folderKey", "mac-studio:boss")
|
||||
.put("lastMessagePreview", "最近:Android UI 收尾")
|
||||
.put("latestReplyAt", "2026-04-06T09:59:00.000Z")
|
||||
.put("latestReplyLabel", "10:59")
|
||||
.put("contextBudgetIndicator", new JSONObject().put("visible", true).put("style", "ring_percent").put("percent", 95).put("level", "safe")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user