feat: align android conversation folders with drawer design
This commit is contained in:
@@ -1028,16 +1028,40 @@ public class MainActivity extends AppCompatActivity {
|
||||
String conversationType = item.optString("conversationType", "");
|
||||
String folderKey = item.optString("folderKey", "");
|
||||
WechatSurfaceMapper.ConversationRow row = WechatSurfaceMapper.toConversationRow(item);
|
||||
String displayTitle = conversationSearchMode ? buildConversationSearchLabel(item, row) : row.threadTitle;
|
||||
WechatSurfaceMapper.ConversationRow displayRow = row;
|
||||
if (!displayTitle.equals(row.threadTitle)) {
|
||||
displayRow = new WechatSurfaceMapper.ConversationRow(
|
||||
displayTitle,
|
||||
row.folderLabel,
|
||||
row.lastMessagePreview,
|
||||
row.timeLabel,
|
||||
row.unreadCount,
|
||||
row.topPinnedLabel,
|
||||
row.activityIconCount,
|
||||
row.isGroup,
|
||||
row.avatarPrimary,
|
||||
row.avatarSecondary,
|
||||
row.groupAvatarMembers,
|
||||
row.pinnedConversation,
|
||||
row.contextStatusLabel,
|
||||
row.contextStatusLevel,
|
||||
row.contextUsagePercent,
|
||||
row.contextIndicatorVisible,
|
||||
row.contextMustFinish
|
||||
);
|
||||
}
|
||||
final WechatSurfaceMapper.ConversationRow finalDisplayRow = displayRow;
|
||||
boolean selected = selectedConversationProjectIds.contains(projectId);
|
||||
String stableKey = !"".equals(projectId)
|
||||
? "conversation-project:" + projectId
|
||||
: "conversation-folder:" + folderKey;
|
||||
String signature = row.threadTitle + "|"
|
||||
+ row.lastMessagePreview + "|"
|
||||
+ row.timeLabel + "|"
|
||||
+ row.unreadCount + "|"
|
||||
+ row.contextStatusLabel + "|"
|
||||
+ row.activityIconCount + "|"
|
||||
String signature = finalDisplayRow.threadTitle + "|"
|
||||
+ finalDisplayRow.lastMessagePreview + "|"
|
||||
+ finalDisplayRow.timeLabel + "|"
|
||||
+ finalDisplayRow.unreadCount + "|"
|
||||
+ finalDisplayRow.contextStatusLabel + "|"
|
||||
+ finalDisplayRow.activityIconCount + "|"
|
||||
+ selected + "|"
|
||||
+ conversationSelectionMode;
|
||||
rootItems.add(rootListItem(
|
||||
@@ -1045,7 +1069,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
signature,
|
||||
() -> BossUi.buildConversationRow(
|
||||
this,
|
||||
row,
|
||||
finalDisplayRow,
|
||||
conversationSelectionMode,
|
||||
selected,
|
||||
v -> {
|
||||
@@ -1061,25 +1085,58 @@ public class MainActivity extends AppCompatActivity {
|
||||
toggleConversationSelection(projectId);
|
||||
return;
|
||||
}
|
||||
if ("folder_archive".equals(conversationType)) {
|
||||
if ("folder_archive".equals(conversationType)
|
||||
|| (conversationSearchMode && isArchivedProjectThread(item))) {
|
||||
if (folderKey.isEmpty()) {
|
||||
showMessage("缺少 folderKey");
|
||||
return;
|
||||
}
|
||||
openConversationFolder(folderKey, row.threadTitle);
|
||||
openConversationFolder(folderKey, resolveConversationFolderName(item, finalDisplayRow));
|
||||
return;
|
||||
}
|
||||
if (projectId.isEmpty()) {
|
||||
showMessage("缺少 projectId");
|
||||
return;
|
||||
}
|
||||
String projectName = row.threadTitle.isEmpty() ? "未命名会话" : row.threadTitle;
|
||||
String projectName = finalDisplayRow.threadTitle.isEmpty() ? "未命名会话" : finalDisplayRow.threadTitle;
|
||||
openProject(projectId, projectName);
|
||||
})
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
return row.threadTitle;
|
||||
}
|
||||
|
||||
private static String resolveConversationFolderName(JSONObject item, WechatSurfaceMapper.ConversationRow row) {
|
||||
if ("folder_archive".equals(item.optString("conversationType", ""))) {
|
||||
return row.threadTitle;
|
||||
}
|
||||
String folderLabel = row.folderLabel == null ? "" : row.folderLabel.trim();
|
||||
if (!folderLabel.isEmpty()) {
|
||||
return folderLabel;
|
||||
}
|
||||
return row.threadTitle;
|
||||
}
|
||||
|
||||
private void appendConversationSelectionControls(List<RootListItem> items) {
|
||||
items.add(rootListItem(
|
||||
"conversation-selection-summary",
|
||||
|
||||
@@ -54,9 +54,17 @@ public final class WechatSurfaceMapper {
|
||||
}
|
||||
JSONObject avatar = source.optJSONObject("avatar");
|
||||
boolean isGroup = source.optBoolean("isGroup", groupAvatarMembers.size() > 1);
|
||||
String conversationType = source.optString("conversationType", "");
|
||||
String threadTitle = source.optString("threadTitle", source.optString("title", source.optString("projectTitle", "")));
|
||||
if ("folder_archive".equals(conversationType)) {
|
||||
threadTitle = source.optString(
|
||||
"projectTitle",
|
||||
source.optString("threadTitle", source.optString("title", source.optString("folderLabel", "")))
|
||||
);
|
||||
}
|
||||
String pinnedLabel = source.optString("topPinnedLabel", "");
|
||||
return new ConversationRow(
|
||||
source.optString("threadTitle", source.optString("title", source.optString("projectTitle", ""))),
|
||||
threadTitle,
|
||||
source.optString("folderLabel", ""),
|
||||
source.optString("lastMessagePreview", source.optString("preview", "")),
|
||||
source.optString("timeLabel", source.optString("latestReplyLabel", "")),
|
||||
|
||||
@@ -5,10 +5,14 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
@@ -94,6 +98,43 @@ public class MainActivityConversationSearchTest {
|
||||
assertFalse(activity.findViewById(R.id.search_button).isShown());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void searchHitInsideArchivedProject_keepsProjectContextAndOpensFolderPage() throws Exception {
|
||||
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("folderKey", "mac-studio:boss")
|
||||
.put("folderLabel", "Boss")
|
||||
.put("projectTitle", "Boss")
|
||||
.put("threadTitle", "发布回滚")
|
||||
.put("lastMessagePreview", "最近:发布回滚")
|
||||
.put("latestReplyLabel", "11:00")
|
||||
.put("threadCount", 2)));
|
||||
|
||||
ReflectionHelpers.callInstanceMethod(activity, "showContent");
|
||||
Shadows.shadowOf(activity.getMainLooper()).idle();
|
||||
ReflectionHelpers.callInstanceMethod(activity, "enterConversationSearchMode");
|
||||
Shadows.shadowOf(activity.getMainLooper()).idle();
|
||||
|
||||
EditText searchInput = activity.findViewById(R.id.top_search_input);
|
||||
searchInput.setText("发布回滚");
|
||||
Shadows.shadowOf(activity.getMainLooper()).idle();
|
||||
|
||||
RecyclerView list = ReflectionHelpers.getField(activity, "screenList");
|
||||
View row = getRecyclerChild(list, 0);
|
||||
assertTrue(viewTreeContainsText(row, "Boss / 发布回滚"));
|
||||
|
||||
row.performClick();
|
||||
Shadows.shadowOf(activity.getMainLooper()).idle();
|
||||
|
||||
Intent nextIntent = Shadows.shadowOf(activity).getNextStartedActivity();
|
||||
assertEquals(ConversationFolderActivity.class.getName(), nextIntent.getComponent().getClassName());
|
||||
assertEquals("mac-studio:boss", nextIntent.getStringExtra(ConversationFolderActivity.EXTRA_FOLDER_KEY));
|
||||
assertEquals("Boss", nextIntent.getStringExtra(ConversationFolderActivity.EXTRA_FOLDER_NAME));
|
||||
}
|
||||
|
||||
private static JSONArray buildConversations() throws Exception {
|
||||
return new JSONArray()
|
||||
.put(new JSONObject()
|
||||
@@ -105,4 +146,31 @@ public class MainActivityConversationSearchTest {
|
||||
.put("latestReplyLabel", "09:40")
|
||||
.put("conversationType", "single_device"));
|
||||
}
|
||||
|
||||
private static View getRecyclerChild(RecyclerView recyclerView, int position) {
|
||||
RecyclerView.Adapter adapter = recyclerView.getAdapter();
|
||||
int viewType = adapter.getItemViewType(position);
|
||||
RecyclerView.ViewHolder holder = adapter.createViewHolder(recyclerView, viewType);
|
||||
adapter.bindViewHolder(holder, position);
|
||||
return ((android.widget.FrameLayout) holder.itemView).getChildAt(0);
|
||||
}
|
||||
|
||||
private static boolean viewTreeContainsText(View root, String expectedText) {
|
||||
if (root instanceof android.widget.TextView) {
|
||||
CharSequence text = ((android.widget.TextView) root).getText();
|
||||
if (expectedText.contentEquals(text)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!(root instanceof LinearLayout)) {
|
||||
return false;
|
||||
}
|
||||
LinearLayout group = (LinearLayout) root;
|
||||
for (int index = 0; index < group.getChildCount(); index += 1) {
|
||||
if (viewTreeContainsText(group.getChildAt(index), expectedText)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public class MainActivityPinnedConversationsTest {
|
||||
assertTrue(recyclerContainsText(list, "主 Agent"));
|
||||
assertTrue(recyclerContainsText(list, "Boss 移动控制台"));
|
||||
|
||||
View pinnedHeader = getRecyclerChild(list, 1);
|
||||
View pinnedHeader = getRecyclerChild(list, 0);
|
||||
pinnedHeader.performClick();
|
||||
|
||||
assertEquals(true, ReflectionHelpers.getField(activity, "pinnedConversationsCollapsed"));
|
||||
@@ -62,6 +62,32 @@ public class MainActivityPinnedConversationsTest {
|
||||
assertTrue("收起后普通会话仍应保留", recyclerContainsText(list, "Boss 移动控制台"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void folderArchiveRow_usesProjectTitleAndCompactSubtitleOnHomepage() throws Exception {
|
||||
MainActivity activity = Robolectric.buildActivity(MainActivity.class).setup().get();
|
||||
ReflectionHelpers.setField(activity, "conversationsData", new JSONArray()
|
||||
.put(new JSONObject()
|
||||
.put("projectId", "folder-boss")
|
||||
.put("conversationType", "folder_archive")
|
||||
.put("folderKey", "mac-studio:boss")
|
||||
.put("projectTitle", "Boss")
|
||||
.put("threadTitle", "发布回滚")
|
||||
.put("folderLabel", "2 个线程 · 最近:发布回滚")
|
||||
.put("lastMessagePreview", "最近:发布回滚")
|
||||
.put("latestReplyLabel", "11:00")));
|
||||
|
||||
ReflectionHelpers.callInstanceMethod(activity, "showContent");
|
||||
Shadows.shadowOf(activity.getMainLooper()).idle();
|
||||
ReflectionHelpers.callInstanceMethod(activity, "renderConversationsRoot");
|
||||
Shadows.shadowOf(activity.getMainLooper()).idle();
|
||||
|
||||
RecyclerView list = ReflectionHelpers.getField(activity, "screenList");
|
||||
View row = getRecyclerChild(list, 0);
|
||||
|
||||
assertTrue(viewTreeContainsText(row, "Boss"));
|
||||
assertTrue(viewTreeContainsText(row, "2 个线程 · 最近:发布回滚"));
|
||||
}
|
||||
|
||||
private static View getRecyclerChild(RecyclerView recyclerView, int position) {
|
||||
RecyclerView.Adapter adapter = recyclerView.getAdapter();
|
||||
int viewType = adapter.getItemViewType(position);
|
||||
|
||||
Reference in New Issue
Block a user