chore: polish native wechat ui release v2.5.1
This commit is contained in:
@@ -36,8 +36,8 @@ android {
|
||||
applicationId "com.hyzq.boss"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 13
|
||||
versionName "2.5.0"
|
||||
versionCode 14
|
||||
versionName "2.5.1"
|
||||
buildConfigField "String", "BOSS_API_BASE_URL", "\"https://boss.hyzq.net\""
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -84,27 +84,15 @@ public class GroupCreateActivity extends BossScreenActivity {
|
||||
sourceProjectName = threadMeta == null
|
||||
? sourceProjectName
|
||||
: threadMeta.optString("threadDisplayName", sourceProjectName == null ? "当前会话" : sourceProjectName);
|
||||
|
||||
appendContent(BossUi.buildCard(
|
||||
this,
|
||||
"新建独立群聊",
|
||||
"群聊不是升级原会话,而是以当前会话为源,新建一个独立线程。",
|
||||
buildSourceMeta(threadMeta, participants)
|
||||
));
|
||||
|
||||
appendContent(BossUi.buildCard(
|
||||
this,
|
||||
sourceProjectName,
|
||||
buildSourceBody(threadMeta, participants),
|
||||
sourceProjectId + (sourceFolderName.isEmpty() ? "" : " · " + sourceFolderName)
|
||||
));
|
||||
} else {
|
||||
appendContent(BossUi.buildCard(
|
||||
this,
|
||||
"从会话首页发起群聊",
|
||||
"你可以直接把任意线程拉进一个新的独立群聊,原来的单线程会话会保留不变。",
|
||||
"至少选择 2 个线程"
|
||||
));
|
||||
}
|
||||
for (SummaryCardSpec cardSpec : buildHeaderCardSpecs(
|
||||
hasSourceProject(),
|
||||
sourceProjectId,
|
||||
sourceProjectName,
|
||||
threadMeta,
|
||||
participants
|
||||
)) {
|
||||
appendContent(BossUi.buildCard(this, cardSpec.title, cardSpec.body, cardSpec.meta));
|
||||
}
|
||||
|
||||
if (rebuildCandidates) {
|
||||
@@ -133,15 +121,16 @@ public class GroupCreateActivity extends BossScreenActivity {
|
||||
lastCandidateProjectIds.addAll(nextCandidateProjectIds);
|
||||
}
|
||||
|
||||
SummaryCardSpec selectionCard = buildSelectionCardSpec(
|
||||
candidates.size(),
|
||||
selectedProjectIds.size(),
|
||||
hasSourceProject()
|
||||
);
|
||||
appendContent(BossUi.buildCard(
|
||||
this,
|
||||
"选择其他线程",
|
||||
candidates.isEmpty()
|
||||
? "当前没有可加入的其他线程。"
|
||||
: selectedProjectIds.isEmpty()
|
||||
? "你已取消全部勾选,可继续手动选择。"
|
||||
: "已保留你当前的勾选状态。",
|
||||
"已选 " + selectedProjectIds.size() + " 个线程"
|
||||
selectionCard.title,
|
||||
selectionCard.body,
|
||||
selectionCard.meta
|
||||
));
|
||||
|
||||
candidateListLayout = new LinearLayout(this);
|
||||
@@ -203,6 +192,59 @@ public class GroupCreateActivity extends BossScreenActivity {
|
||||
);
|
||||
}
|
||||
|
||||
static List<SummaryCardSpec> buildHeaderCardSpecs(
|
||||
boolean hasSourceProject,
|
||||
@Nullable String sourceProjectId,
|
||||
@Nullable String sourceProjectName,
|
||||
@Nullable JSONObject threadMeta,
|
||||
@Nullable JSONArray participants
|
||||
) {
|
||||
List<SummaryCardSpec> cards = new ArrayList<>(1);
|
||||
if (hasSourceProject) {
|
||||
String resolvedSourceProjectName = sourceProjectName == null || sourceProjectName.isEmpty()
|
||||
? "当前会话"
|
||||
: sourceProjectName;
|
||||
cards.add(new SummaryCardSpec(
|
||||
resolvedSourceProjectName,
|
||||
buildSourceBody(sourceProjectId, threadMeta, participants),
|
||||
"新群会单独创建,原会话保留"
|
||||
));
|
||||
return cards;
|
||||
}
|
||||
cards.add(new SummaryCardSpec(
|
||||
"从会话中发起群聊",
|
||||
"至少选择 2 个线程,新群会单独创建。",
|
||||
"原有单线程会话会保留"
|
||||
));
|
||||
return cards;
|
||||
}
|
||||
|
||||
static SummaryCardSpec buildSelectionCardSpec(
|
||||
int candidateCount,
|
||||
int selectedCount,
|
||||
boolean hasSourceProject
|
||||
) {
|
||||
if (candidateCount <= 0) {
|
||||
return new SummaryCardSpec(
|
||||
"选择其他线程",
|
||||
"当前没有可加入的其他线程",
|
||||
"候选 0 个 · 已选 0 个"
|
||||
);
|
||||
}
|
||||
if (selectedCount <= 0) {
|
||||
return new SummaryCardSpec(
|
||||
"选择其他线程",
|
||||
hasSourceProject ? "至少选择 1 个其他线程" : "至少选择 2 个线程",
|
||||
"候选 " + candidateCount + " 个 · 已选 0 个"
|
||||
);
|
||||
}
|
||||
return new SummaryCardSpec(
|
||||
"选择其他线程",
|
||||
"继续点选可调整成员",
|
||||
"候选 " + candidateCount + " 个 · 已选 " + selectedCount + " 个"
|
||||
);
|
||||
}
|
||||
|
||||
private LinearLayout buildCandidateRow(CandidateConversation candidate) {
|
||||
return BossUi.buildConversationRow(
|
||||
this,
|
||||
@@ -348,25 +390,33 @@ public class GroupCreateActivity extends BossScreenActivity {
|
||||
return sourceProjectId != null && !sourceProjectId.isEmpty();
|
||||
}
|
||||
|
||||
private String buildSourceMeta(@Nullable JSONObject threadMeta, @Nullable JSONArray participants) {
|
||||
String folderName = threadMeta == null ? "" : threadMeta.optString("folderName", "");
|
||||
int count = participants == null ? 0 : participants.length();
|
||||
String memberLabel = count <= 0 ? "暂无参与线程" : count + " 个参与线程";
|
||||
if (folderName.isEmpty()) {
|
||||
return memberLabel;
|
||||
}
|
||||
return folderName + " · " + memberLabel;
|
||||
}
|
||||
|
||||
private String buildSourceBody(@Nullable JSONObject threadMeta, @Nullable JSONArray participants) {
|
||||
private static String buildSourceBody(
|
||||
@Nullable String sourceProjectId,
|
||||
@Nullable JSONObject threadMeta,
|
||||
@Nullable JSONArray participants
|
||||
) {
|
||||
String threadId = threadMeta == null ? sourceProjectId : threadMeta.optString("threadId", sourceProjectId);
|
||||
String folderName = threadMeta == null ? "" : threadMeta.optString("folderName", "");
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("来源线程:").append(threadId);
|
||||
builder.append("\n文件夹:").append(folderName.isEmpty() ? "未命名文件夹" : folderName);
|
||||
builder.append("\n参与线程:").append(participants == null ? 0 : participants.length());
|
||||
builder.append("\n默认规则:会自动勾选当前会话之外的其他线程");
|
||||
return builder.toString();
|
||||
String resolvedFolderName = folderName.isEmpty() ? "未命名文件夹" : folderName;
|
||||
String resolvedThreadId = threadId == null || threadId.isEmpty() ? "未命名线程" : threadId;
|
||||
return "来源线程 " + resolvedThreadId
|
||||
+ " · "
|
||||
+ resolvedFolderName
|
||||
+ " · "
|
||||
+ (participants == null ? 0 : participants.length())
|
||||
+ " 个参与线程";
|
||||
}
|
||||
|
||||
static final class SummaryCardSpec {
|
||||
final String title;
|
||||
final String body;
|
||||
final String meta;
|
||||
|
||||
SummaryCardSpec(String title, String body, String meta) {
|
||||
this.title = title;
|
||||
this.body = body;
|
||||
this.meta = meta;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CandidateConversation {
|
||||
|
||||
@@ -136,7 +136,7 @@ public final class ProjectChatUiState {
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
!conversationInfoReady,
|
||||
conversationInfoReady,
|
||||
false,
|
||||
"返回",
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -12,6 +13,41 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class GroupCreateActivityTest {
|
||||
@Test
|
||||
public void buildHeaderCardSpecs_collapsesSourceFlowIntoSingleCompactCard() {
|
||||
JSONObject threadMeta = new StubJSONObject()
|
||||
.withString("threadDisplayName", "北区试产线回归")
|
||||
.withString("threadId", "thread-7")
|
||||
.withString("folderName", "Mac Studio");
|
||||
JSONArray participants = new StubJSONArray(
|
||||
new StubJSONObject(),
|
||||
new StubJSONObject(),
|
||||
new StubJSONObject()
|
||||
);
|
||||
|
||||
List<GroupCreateActivity.SummaryCardSpec> cards = GroupCreateActivity.buildHeaderCardSpecs(
|
||||
true,
|
||||
"source-1",
|
||||
"北区试产线回归",
|
||||
threadMeta,
|
||||
participants
|
||||
);
|
||||
|
||||
assertEquals(1, cards.size());
|
||||
assertEquals("北区试产线回归", cards.get(0).title);
|
||||
assertEquals("来源线程 thread-7 · Mac Studio · 3 个参与线程", cards.get(0).body);
|
||||
assertEquals("新群会单独创建,原会话保留", cards.get(0).meta);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildSelectionCardSpec_usesCompactWechatStyleHint() {
|
||||
GroupCreateActivity.SummaryCardSpec card = GroupCreateActivity.buildSelectionCardSpec(5, 0, true);
|
||||
|
||||
assertEquals("选择其他线程", card.title);
|
||||
assertEquals("至少选择 1 个其他线程", card.body);
|
||||
assertEquals("候选 5 个 · 已选 0 个", card.meta);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void collectSelectableConversationItems_filtersOutExistingGroupChats() {
|
||||
JSONObject threadConversation = new StubJSONObject()
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.hyzq.boss;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(sdk = 34)
|
||||
public class GroupCreateActivityUiTest {
|
||||
@Test
|
||||
public void renderCreatePageUsesCompactSummaryCardsForSourceFlow() throws Exception {
|
||||
Intent intent = new Intent()
|
||||
.putExtra(GroupCreateActivity.EXTRA_SOURCE_PROJECT_ID, "source-1")
|
||||
.putExtra(GroupCreateActivity.EXTRA_SOURCE_PROJECT_NAME, "北区试产线回归");
|
||||
TestGroupCreateActivity activity = Robolectric
|
||||
.buildActivity(TestGroupCreateActivity.class, intent)
|
||||
.setup()
|
||||
.get();
|
||||
|
||||
ReflectionHelpers.callInstanceMethod(
|
||||
activity,
|
||||
"renderCreatePage",
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildParticipantsPayload()),
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildConversationsPayload()),
|
||||
ReflectionHelpers.ClassParameter.from(boolean.class, true)
|
||||
);
|
||||
|
||||
LinearLayout content = activity.findViewById(R.id.screen_content);
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(0), "北区试产线回归"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(0), "来源线程 thread-7 · Mac Studio · 3 个参与线程"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(0), "新群会单独创建,原会话保留"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(1), "选择其他线程"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(1), "继续点选可调整成员"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(1), "候选 2 个 · 已选 2 个"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toggleSelectionRefreshesRenderedSelectionSummary() throws Exception {
|
||||
Intent intent = new Intent()
|
||||
.putExtra(GroupCreateActivity.EXTRA_SOURCE_PROJECT_ID, "source-1")
|
||||
.putExtra(GroupCreateActivity.EXTRA_SOURCE_PROJECT_NAME, "北区试产线回归");
|
||||
TestGroupCreateActivity activity = Robolectric
|
||||
.buildActivity(TestGroupCreateActivity.class, intent)
|
||||
.setup()
|
||||
.get();
|
||||
|
||||
ReflectionHelpers.callInstanceMethod(
|
||||
activity,
|
||||
"renderCreatePage",
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildParticipantsPayload()),
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildConversationsPayload()),
|
||||
ReflectionHelpers.ClassParameter.from(boolean.class, true)
|
||||
);
|
||||
|
||||
ReflectionHelpers.callInstanceMethod(
|
||||
activity,
|
||||
"toggleSelection",
|
||||
ReflectionHelpers.ClassParameter.from(String.class, "thread-2")
|
||||
);
|
||||
ReflectionHelpers.callInstanceMethod(
|
||||
activity,
|
||||
"toggleSelection",
|
||||
ReflectionHelpers.ClassParameter.from(String.class, "thread-3")
|
||||
);
|
||||
|
||||
LinearLayout content = activity.findViewById(R.id.screen_content);
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(1), "至少选择 1 个其他线程"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(1), "候选 2 个 · 已选 0 个"));
|
||||
}
|
||||
|
||||
private static JSONObject buildParticipantsPayload() throws Exception {
|
||||
JSONObject threadMeta = new JSONObject()
|
||||
.put("threadDisplayName", "北区试产线回归")
|
||||
.put("threadId", "thread-7")
|
||||
.put("folderName", "Mac Studio");
|
||||
JSONArray participants = new JSONArray()
|
||||
.put(new JSONObject().put("projectId", "source-1"))
|
||||
.put(new JSONObject().put("projectId", "thread-2"))
|
||||
.put(new JSONObject().put("projectId", "thread-3"));
|
||||
return new JSONObject()
|
||||
.put("threadMeta", threadMeta)
|
||||
.put("participants", participants);
|
||||
}
|
||||
|
||||
private static JSONObject buildConversationsPayload() throws Exception {
|
||||
JSONArray conversations = new JSONArray()
|
||||
.put(new JSONObject()
|
||||
.put("projectId", "thread-2")
|
||||
.put("projectTitle", "硬件审计协作")
|
||||
.put("folderLabel", "Mac Studio")
|
||||
.put("lastMessagePreview", "检查摄像头供电链路")
|
||||
.put("latestReplyLabel", "09:28")
|
||||
.put("isGroup", false))
|
||||
.put(new JSONObject()
|
||||
.put("projectId", "thread-3")
|
||||
.put("projectTitle", "Boss 移动控制台")
|
||||
.put("folderLabel", "Boss")
|
||||
.put("lastMessagePreview", "统一顶部按钮样式")
|
||||
.put("latestReplyLabel", "09:31")
|
||||
.put("isGroup", false));
|
||||
return new JSONObject().put("conversations", conversations);
|
||||
}
|
||||
|
||||
private static boolean viewTreeContainsText(View root, String expectedText) {
|
||||
if (root instanceof TextView) {
|
||||
CharSequence text = ((TextView) root).getText();
|
||||
if (expectedText.contentEquals(text)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!(root instanceof ViewGroup)) {
|
||||
return false;
|
||||
}
|
||||
ViewGroup group = (ViewGroup) root;
|
||||
for (int index = 0; index < group.getChildCount(); index += 1) {
|
||||
if (viewTreeContainsText(group.getChildAt(index), expectedText)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class TestGroupCreateActivity extends GroupCreateActivity {
|
||||
@Override
|
||||
protected void reload() {
|
||||
// Tests drive renderCreatePage manually to avoid network work.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@ public class ProjectChatUiStateTest {
|
||||
assertFalse(chromeState.multiSelecting);
|
||||
assertTrue(chromeState.showComposer);
|
||||
assertFalse(chromeState.showMultiSelectBar);
|
||||
assertTrue(chromeState.showRefresh);
|
||||
assertFalse(chromeState.showRefresh);
|
||||
assertTrue(chromeState.showHeaderAction);
|
||||
assertFalse(chromeState.forwardEnabled);
|
||||
assertEquals("返回", chromeState.backLabel);
|
||||
@@ -119,6 +119,22 @@ public class ProjectChatUiStateTest {
|
||||
assertEquals("归档确认", chromeState.subtitle);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chromeStateRestoresRefreshBeforeConversationInfoIsReady() {
|
||||
ProjectChatUiState.ChromeState chromeState =
|
||||
ProjectChatUiState.resolveChromeState(ProjectChatUiState.emptySelection(), false, "北区试产线回归", "归档确认");
|
||||
|
||||
assertFalse(chromeState.multiSelecting);
|
||||
assertTrue(chromeState.showComposer);
|
||||
assertFalse(chromeState.showMultiSelectBar);
|
||||
assertTrue(chromeState.showRefresh);
|
||||
assertFalse(chromeState.showHeaderAction);
|
||||
assertFalse(chromeState.forwardEnabled);
|
||||
assertEquals("返回", chromeState.backLabel);
|
||||
assertEquals("北区试产线回归", chromeState.title);
|
||||
assertEquals("归档确认", chromeState.subtitle);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reconcileSelectionDropsMessagesMissingFromRenderSet() {
|
||||
ProjectChatUiState.SelectionState state = ProjectChatUiState.toggleSelection(null, "m1");
|
||||
|
||||
@@ -46,7 +46,7 @@ public class ProjectDetailActivityChromeBindingsTest {
|
||||
assertFalse(bindings.multiSelecting);
|
||||
assertTrue(bindings.showComposer);
|
||||
assertFalse(bindings.showMultiSelectBar);
|
||||
assertTrue(bindings.showRefresh);
|
||||
assertFalse(bindings.showRefresh);
|
||||
assertTrue(bindings.showHeaderAction);
|
||||
assertFalse(bindings.enableForwardButton);
|
||||
assertTrue(bindings.enablePullRefresh);
|
||||
|
||||
@@ -75,7 +75,7 @@ public class ProjectDetailActivityUiTest {
|
||||
assertEquals(View.VISIBLE, composerRow.getVisibility());
|
||||
assertEquals(View.GONE, multiSelectActions.getVisibility());
|
||||
assertEquals("返回", backButton.getText().toString());
|
||||
assertEquals(View.VISIBLE, refreshButton.getVisibility());
|
||||
assertEquals(View.GONE, refreshButton.getVisibility());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user