chore: publish native ui polish release v2.5.3
This commit is contained in:
@@ -36,8 +36,8 @@ android {
|
||||
applicationId "com.hyzq.boss"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 15
|
||||
versionName "2.5.2"
|
||||
versionCode 16
|
||||
versionName "2.5.3"
|
||||
buildConfigField "String", "BOSS_API_BASE_URL", "\"https://boss.hyzq.net\""
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -912,10 +912,7 @@ public final class BossUi {
|
||||
wrapper.setLayoutParams(wrapperParams);
|
||||
|
||||
TextView metaView = new TextView(context);
|
||||
String metaText = senderLabel;
|
||||
if (!TextUtils.isEmpty(meta)) {
|
||||
metaText = metaText + " · " + meta;
|
||||
}
|
||||
String metaText = buildMessageMetaText(senderLabel, meta, outgoing);
|
||||
metaView.setText(metaText);
|
||||
metaView.setTextSize(11);
|
||||
metaView.setTextColor(context.getColor(R.color.boss_text_soft));
|
||||
@@ -1049,10 +1046,7 @@ public final class BossUi {
|
||||
wrapper.setLayoutParams(wrapperParams);
|
||||
|
||||
TextView metaView = new TextView(context);
|
||||
String metaText = senderLabel;
|
||||
if (!TextUtils.isEmpty(meta)) {
|
||||
metaText = metaText + " · " + meta;
|
||||
}
|
||||
String metaText = buildMessageMetaText(senderLabel, meta, outgoing);
|
||||
metaView.setText(metaText);
|
||||
metaView.setTextSize(11);
|
||||
metaView.setTextColor(context.getColor(R.color.boss_text_soft));
|
||||
@@ -1061,6 +1055,23 @@ public final class BossUi {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
private static String buildMessageMetaText(
|
||||
String senderLabel,
|
||||
@Nullable String meta,
|
||||
boolean outgoing
|
||||
) {
|
||||
if (outgoing && !TextUtils.isEmpty(meta)) {
|
||||
return meta;
|
||||
}
|
||||
if (TextUtils.isEmpty(meta)) {
|
||||
return senderLabel;
|
||||
}
|
||||
if (TextUtils.isEmpty(senderLabel)) {
|
||||
return meta;
|
||||
}
|
||||
return senderLabel + " · " + meta;
|
||||
}
|
||||
|
||||
private static TextView buildAttachmentPrimaryText(Context context, String text) {
|
||||
TextView primary = new TextView(context);
|
||||
primary.setText(TextUtils.isEmpty(text) ? "未命名附件" : text);
|
||||
|
||||
@@ -2,8 +2,11 @@ package com.hyzq.boss;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -85,15 +88,13 @@ public class GroupCreateActivity extends BossScreenActivity {
|
||||
? sourceProjectName
|
||||
: threadMeta.optString("threadDisplayName", sourceProjectName == null ? "当前会话" : sourceProjectName);
|
||||
}
|
||||
for (SummaryCardSpec cardSpec : buildHeaderCardSpecs(
|
||||
appendContent(buildHeaderView(
|
||||
hasSourceProject(),
|
||||
sourceProjectId,
|
||||
sourceProjectName,
|
||||
threadMeta,
|
||||
participants
|
||||
)) {
|
||||
appendContent(BossUi.buildCard(this, cardSpec.title, cardSpec.body, cardSpec.meta));
|
||||
}
|
||||
));
|
||||
|
||||
if (rebuildCandidates) {
|
||||
List<JSONObject> selectableConversations = collectSelectableConversationItems(conversationsPayload, sourceProjectId);
|
||||
@@ -121,16 +122,10 @@ public class GroupCreateActivity extends BossScreenActivity {
|
||||
lastCandidateProjectIds.addAll(nextCandidateProjectIds);
|
||||
}
|
||||
|
||||
SummaryCardSpec selectionCard = buildSelectionCardSpec(
|
||||
candidates.size(),
|
||||
selectedProjectIds.size(),
|
||||
hasSourceProject()
|
||||
);
|
||||
appendContent(BossUi.buildCard(
|
||||
appendContent(buildSectionLabel("选择其他线程"));
|
||||
appendContent(BossUi.buildHintPill(
|
||||
this,
|
||||
selectionCard.title,
|
||||
selectionCard.body,
|
||||
selectionCard.meta
|
||||
buildSelectionHintText(candidates.size(), selectedProjectIds.size(), hasSourceProject())
|
||||
));
|
||||
|
||||
candidateListLayout = new LinearLayout(this);
|
||||
@@ -145,16 +140,53 @@ public class GroupCreateActivity extends BossScreenActivity {
|
||||
|
||||
createButton = BossUi.buildPrimaryButton(this, "创建群聊");
|
||||
createButton.setOnClickListener(v -> createGroupChat());
|
||||
appendContent(createButton);
|
||||
|
||||
Button cancelButton = BossUi.buildSecondaryButton(this, "取消");
|
||||
cancelButton.setOnClickListener(v -> finish());
|
||||
appendContent(cancelButton);
|
||||
appendContent(BossUi.buildInlineActionRow(this, cancelButton, createButton));
|
||||
|
||||
setRefreshing(false);
|
||||
updateCreateButtonState();
|
||||
}
|
||||
|
||||
private View buildHeaderView(
|
||||
boolean hasSourceProject,
|
||||
@Nullable String sourceProjectId,
|
||||
@Nullable String sourceProjectName,
|
||||
@Nullable JSONObject threadMeta,
|
||||
@Nullable JSONArray participants
|
||||
) {
|
||||
if (hasSourceProject) {
|
||||
return BossUi.buildSimpleProfileHeader(
|
||||
this,
|
||||
TextUtils.isEmpty(sourceProjectName) ? "当前会话" : sourceProjectName,
|
||||
"从当前会话发起群聊",
|
||||
buildSourceHeaderDetail(sourceProjectId, threadMeta, participants)
|
||||
);
|
||||
}
|
||||
return BossUi.buildSimpleProfileHeader(
|
||||
this,
|
||||
"发起新群聊",
|
||||
"从会话列表直接建群",
|
||||
"至少选择 2 个线程后创建新群"
|
||||
);
|
||||
}
|
||||
|
||||
private TextView buildSectionLabel(String text) {
|
||||
TextView label = new TextView(this);
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
params.leftMargin = BossUi.dp(this, 16);
|
||||
params.rightMargin = BossUi.dp(this, 16);
|
||||
params.bottomMargin = BossUi.dp(this, 6);
|
||||
label.setLayoutParams(params);
|
||||
label.setText(text);
|
||||
label.setTextSize(13);
|
||||
label.setTextColor(getColor(R.color.boss_text_muted));
|
||||
return label;
|
||||
}
|
||||
|
||||
static List<JSONObject> collectSelectableConversationItems(@Nullable JSONObject conversationsPayload, String sourceProjectId) {
|
||||
List<JSONObject> result = new ArrayList<>();
|
||||
JSONArray conversations = conversationsPayload == null ? null : conversationsPayload.optJSONArray("conversations");
|
||||
@@ -192,65 +224,39 @@ public class GroupCreateActivity extends BossScreenActivity {
|
||||
);
|
||||
}
|
||||
|
||||
static List<SummaryCardSpec> buildHeaderCardSpecs(
|
||||
boolean hasSourceProject,
|
||||
static String buildSourceHeaderDetail(
|
||||
@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;
|
||||
return buildSourceBody(sourceProjectId, threadMeta, participants);
|
||||
}
|
||||
|
||||
static SummaryCardSpec buildSelectionCardSpec(
|
||||
static String buildSelectionHintText(
|
||||
int candidateCount,
|
||||
int selectedCount,
|
||||
boolean hasSourceProject
|
||||
) {
|
||||
if (candidateCount <= 0) {
|
||||
return new SummaryCardSpec(
|
||||
"选择其他线程",
|
||||
"当前没有可加入的其他线程",
|
||||
"候选 0 个 · 已选 0 个"
|
||||
);
|
||||
return "当前没有可加入的其他线程";
|
||||
}
|
||||
if (selectedCount <= 0) {
|
||||
return new SummaryCardSpec(
|
||||
"选择其他线程",
|
||||
hasSourceProject ? "至少选择 1 个其他线程" : "至少选择 2 个线程",
|
||||
"候选 " + candidateCount + " 个 · 已选 0 个"
|
||||
);
|
||||
return hasSourceProject ? "至少选择 1 个其他线程" : "至少选择 2 个线程";
|
||||
}
|
||||
return new SummaryCardSpec(
|
||||
"选择其他线程",
|
||||
"继续点选可调整成员",
|
||||
"候选 " + candidateCount + " 个 · 已选 " + selectedCount + " 个"
|
||||
);
|
||||
return "已选 " + selectedCount + " 个线程";
|
||||
}
|
||||
|
||||
private LinearLayout buildCandidateRow(CandidateConversation candidate) {
|
||||
return BossUi.buildConversationRow(
|
||||
LinearLayout row = BossUi.buildConversationRow(
|
||||
this,
|
||||
toCandidateConversationRow(candidate.sourceItem, selectedProjectIds.contains(candidate.projectId)),
|
||||
v -> toggleSelection(candidate.projectId)
|
||||
);
|
||||
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) row.getLayoutParams();
|
||||
params.bottomMargin = BossUi.dp(this, 8);
|
||||
row.setLayoutParams(params);
|
||||
row.setPadding(BossUi.dp(this, 12), BossUi.dp(this, 12), BossUi.dp(this, 12), BossUi.dp(this, 12));
|
||||
return row;
|
||||
}
|
||||
|
||||
private void toggleSelection(String projectId) {
|
||||
@@ -399,7 +405,7 @@ public class GroupCreateActivity extends BossScreenActivity {
|
||||
String folderName = threadMeta == null ? "" : threadMeta.optString("folderName", "");
|
||||
String resolvedFolderName = folderName.isEmpty() ? "未命名文件夹" : folderName;
|
||||
String resolvedThreadId = threadId == null || threadId.isEmpty() ? "未命名线程" : threadId;
|
||||
return "来源线程 " + resolvedThreadId
|
||||
return resolvedThreadId
|
||||
+ " · "
|
||||
+ resolvedFolderName
|
||||
+ " · "
|
||||
@@ -407,18 +413,6 @@ public class GroupCreateActivity extends BossScreenActivity {
|
||||
+ " 个参与线程";
|
||||
}
|
||||
|
||||
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 {
|
||||
private final String projectId;
|
||||
private final JSONObject sourceItem;
|
||||
|
||||
@@ -14,7 +14,7 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class GroupCreateActivityTest {
|
||||
@Test
|
||||
public void buildHeaderCardSpecs_collapsesSourceFlowIntoSingleCompactCard() {
|
||||
public void buildSourceHeaderDetail_usesCompactWechatSummary() {
|
||||
JSONObject threadMeta = new StubJSONObject()
|
||||
.withString("threadDisplayName", "北区试产线回归")
|
||||
.withString("threadId", "thread-7")
|
||||
@@ -25,27 +25,20 @@ public class GroupCreateActivityTest {
|
||||
new StubJSONObject()
|
||||
);
|
||||
|
||||
List<GroupCreateActivity.SummaryCardSpec> cards = GroupCreateActivity.buildHeaderCardSpecs(
|
||||
true,
|
||||
String detail = GroupCreateActivity.buildSourceHeaderDetail(
|
||||
"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);
|
||||
assertEquals("thread-7 · Mac Studio · 3 个参与线程", detail);
|
||||
}
|
||||
|
||||
@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);
|
||||
public void buildSelectionHintText_usesCompactWechatStyleHint() {
|
||||
assertEquals("至少选择 1 个其他线程", GroupCreateActivity.buildSelectionHintText(5, 0, true));
|
||||
assertEquals("已选 2 个线程", GroupCreateActivity.buildSelectionHintText(5, 2, true));
|
||||
assertEquals("当前没有可加入的其他线程", GroupCreateActivity.buildSelectionHintText(0, 0, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -40,11 +40,10 @@ public class GroupCreateActivityUiTest {
|
||||
|
||||
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(0), "从当前会话发起群聊"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(0), "thread-7 · Mac Studio · 3 个参与线程"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(1), "选择其他线程"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(1), "继续点选可调整成员"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(1), "候选 2 个 · 已选 2 个"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(2), "已选 2 个线程"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -77,8 +76,32 @@ public class GroupCreateActivityUiTest {
|
||||
);
|
||||
|
||||
LinearLayout content = activity.findViewById(R.id.screen_content);
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(1), "至少选择 1 个其他线程"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(1), "候选 2 个 · 已选 0 个"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(2), "至少选择 1 个其他线程"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderCreatePageUsesInlineActionRowInsteadOfStackedButtons() 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);
|
||||
View lastChild = content.getChildAt(content.getChildCount() - 1);
|
||||
assertTrue(lastChild instanceof LinearLayout);
|
||||
assertTrue(viewTreeContainsText(lastChild, "取消"));
|
||||
assertTrue(viewTreeContainsText(lastChild, "创建群聊"));
|
||||
}
|
||||
|
||||
private static JSONObject buildParticipantsPayload() throws Exception {
|
||||
|
||||
@@ -190,6 +190,42 @@ public class ProjectDetailActivityUiTest {
|
||||
assertFalse(viewTreeContainsText(attachmentView, "已分析"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void outgoingAttachmentMetaPrefersTimeOnly() throws Exception {
|
||||
Intent intent = new Intent()
|
||||
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "project-1")
|
||||
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "北区试产线回归");
|
||||
TestProjectDetailActivity activity = Robolectric
|
||||
.buildActivity(TestProjectDetailActivity.class, intent)
|
||||
.setup()
|
||||
.get();
|
||||
|
||||
JSONObject attachment = new JSONObject()
|
||||
.put("attachmentId", "att-meta")
|
||||
.put("fileName", "现场照片.jpg")
|
||||
.put("mimeType", "image/jpeg")
|
||||
.put("attachmentKind", "image")
|
||||
.put("analysisState", "queued_auto")
|
||||
.put("fileSizeBytes", 1024);
|
||||
JSONObject message = new JSONObject()
|
||||
.put("id", "msg-meta")
|
||||
.put("kind", "attachment")
|
||||
.put("body", "已发送附件")
|
||||
.put("attachments", new JSONArray().put(attachment));
|
||||
|
||||
View attachmentView = ReflectionHelpers.callInstanceMethod(
|
||||
activity,
|
||||
"buildAttachmentMessageView",
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, message),
|
||||
ReflectionHelpers.ClassParameter.from(String.class, "你"),
|
||||
ReflectionHelpers.ClassParameter.from(String.class, "09:26"),
|
||||
ReflectionHelpers.ClassParameter.from(boolean.class, true)
|
||||
);
|
||||
|
||||
assertTrue(viewTreeContainsText(attachmentView, "09:26"));
|
||||
assertFalse(viewTreeContainsText(attachmentView, "你 · 09:26"));
|
||||
}
|
||||
|
||||
private static View buildBoundMessageView(TestProjectDetailActivity activity, String messageId, String body) {
|
||||
TextView messageView = new TextView(activity);
|
||||
messageView.setText(body);
|
||||
|
||||
Reference in New Issue
Block a user