Restore chat-first project detail surface
This commit is contained in:
@@ -28,7 +28,7 @@ public abstract class BossScreenActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_screen);
|
||||
setContentView(getLayoutResId());
|
||||
apiClient = new BossApiClient(this);
|
||||
|
||||
backButton = findViewById(R.id.screen_back_button);
|
||||
@@ -44,6 +44,10 @@ public abstract class BossScreenActivity extends AppCompatActivity {
|
||||
refreshLayout.setOnRefreshListener(this::reload);
|
||||
}
|
||||
|
||||
protected int getLayoutResId() {
|
||||
return R.layout.activity_screen;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
executor.shutdownNow();
|
||||
|
||||
@@ -171,6 +171,80 @@ public final class BossUi {
|
||||
return buildCard(context, "暂无内容", text, "下拉或点击顶部刷新按钮重试。");
|
||||
}
|
||||
|
||||
public static LinearLayout buildMessageBubble(
|
||||
Context context,
|
||||
String senderLabel,
|
||||
String body,
|
||||
@Nullable String meta,
|
||||
boolean outgoing,
|
||||
@Nullable String kindLabel
|
||||
) {
|
||||
LinearLayout wrapper = new LinearLayout(context);
|
||||
wrapper.setOrientation(LinearLayout.VERTICAL);
|
||||
wrapper.setGravity(outgoing ? Gravity.END : Gravity.START);
|
||||
LinearLayout.LayoutParams wrapperParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
wrapperParams.bottomMargin = dp(context, 12);
|
||||
wrapper.setLayoutParams(wrapperParams);
|
||||
|
||||
TextView metaView = new TextView(context);
|
||||
String metaText = senderLabel;
|
||||
if (!TextUtils.isEmpty(meta)) {
|
||||
metaText = metaText + " · " + meta;
|
||||
}
|
||||
metaView.setText(metaText);
|
||||
metaView.setTextSize(11);
|
||||
metaView.setTextColor(context.getColor(R.color.boss_text_soft));
|
||||
metaView.setPadding(dp(context, 6), 0, dp(context, 6), dp(context, 4));
|
||||
wrapper.addView(metaView);
|
||||
|
||||
LinearLayout bubble = new LinearLayout(context);
|
||||
bubble.setOrientation(LinearLayout.VERTICAL);
|
||||
bubble.setBackgroundResource(outgoing ? R.drawable.bg_message_outgoing : R.drawable.bg_message_incoming);
|
||||
bubble.setPadding(dp(context, 14), dp(context, 12), dp(context, 14), dp(context, 12));
|
||||
int maxBubbleWidth = Math.round(context.getResources().getDisplayMetrics().widthPixels * 0.72f);
|
||||
bubble.setMinimumWidth(dp(context, 84));
|
||||
|
||||
if (!TextUtils.isEmpty(kindLabel)) {
|
||||
TextView kindView = new TextView(context);
|
||||
kindView.setText(kindLabel);
|
||||
kindView.setTextSize(11);
|
||||
kindView.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
kindView.setTextColor(context.getColor(outgoing ? R.color.boss_surface : R.color.boss_text_muted));
|
||||
kindView.setPadding(0, 0, 0, dp(context, 6));
|
||||
bubble.addView(kindView);
|
||||
}
|
||||
|
||||
TextView bodyView = new TextView(context);
|
||||
bodyView.setText(TextUtils.isEmpty(body) ? "(空消息)" : body);
|
||||
bodyView.setTextSize(15);
|
||||
bodyView.setLineSpacing(0f, 1.2f);
|
||||
bodyView.setTextColor(context.getColor(outgoing ? R.color.boss_surface : R.color.boss_text_primary));
|
||||
bodyView.setMaxWidth(maxBubbleWidth);
|
||||
bubble.addView(bodyView);
|
||||
|
||||
wrapper.addView(bubble);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
public static TextView buildMessagePlaceholder(Context context, String text) {
|
||||
TextView placeholder = new TextView(context);
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
params.topMargin = dp(context, 24);
|
||||
placeholder.setLayoutParams(params);
|
||||
placeholder.setGravity(Gravity.CENTER);
|
||||
placeholder.setPadding(dp(context, 24), dp(context, 12), dp(context, 24), dp(context, 12));
|
||||
placeholder.setText(text);
|
||||
placeholder.setTextSize(13);
|
||||
placeholder.setTextColor(context.getColor(R.color.boss_text_soft));
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
public static Button buildPrimaryButton(Context context, String label) {
|
||||
Button button = new Button(context);
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||
|
||||
@@ -2,15 +2,16 @@ package com.hyzq.boss;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.ViewGroup;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class ProjectDetailActivity extends BossScreenActivity {
|
||||
@@ -19,14 +20,29 @@ public class ProjectDetailActivity extends BossScreenActivity {
|
||||
|
||||
private String projectId;
|
||||
private String initialProjectName;
|
||||
private LinearLayout quickActionsLayout;
|
||||
private EditText composerInput;
|
||||
private Button composerSendButton;
|
||||
private ScrollView chatScrollView;
|
||||
|
||||
@Override
|
||||
protected int getLayoutResId() {
|
||||
return R.layout.activity_project_chat;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
projectId = getIntent().getStringExtra(EXTRA_PROJECT_ID);
|
||||
initialProjectName = getIntent().getStringExtra(EXTRA_PROJECT_NAME);
|
||||
quickActionsLayout = findViewById(R.id.project_chat_quick_actions);
|
||||
composerInput = findViewById(R.id.project_chat_input);
|
||||
composerSendButton = findViewById(R.id.project_chat_send);
|
||||
chatScrollView = findViewById(R.id.project_chat_scroll);
|
||||
|
||||
configureScreen(initialProjectName == null ? "项目详情" : initialProjectName, "正在同步项目详情...");
|
||||
setHeaderAction("发消息", v -> chooseMessageKindAndSend());
|
||||
hideHeaderAction();
|
||||
composerSendButton.setOnClickListener(v -> sendTextMessageFromComposer());
|
||||
reload();
|
||||
}
|
||||
|
||||
@@ -54,167 +70,92 @@ public class ProjectDetailActivity extends BossScreenActivity {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setRefreshing(boolean refreshing) {
|
||||
super.setRefreshing(refreshing);
|
||||
if (composerInput != null) {
|
||||
composerInput.setEnabled(!refreshing);
|
||||
}
|
||||
if (composerSendButton != null) {
|
||||
composerSendButton.setEnabled(!refreshing);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderProject(JSONObject payload) {
|
||||
JSONObject project = payload.optJSONObject("project");
|
||||
JSONArray devices = payload.optJSONArray("devices");
|
||||
JSONArray threadContexts = payload.optJSONArray("activeThreadContexts");
|
||||
JSONArray recentLogs = payload.optJSONArray("recentAppLogs");
|
||||
|
||||
String title = project != null ? project.optString("name", "项目详情") : "项目详情";
|
||||
String subtitle = "设备:" + joinDeviceNames(devices);
|
||||
configureScreen(title, subtitle);
|
||||
initialProjectName = title;
|
||||
configureScreen(title, "设备:" + joinDeviceNames(devices));
|
||||
|
||||
renderQuickActions();
|
||||
replaceContent();
|
||||
appendContent(buildActionGrid());
|
||||
|
||||
JSONObject masterIdentity = payload.optJSONObject("masterIdentity");
|
||||
if (masterIdentity != null) {
|
||||
String body = masterIdentity.optString("roleLabel", "主控")
|
||||
+ " · " + masterIdentity.optString("displayName", "-")
|
||||
+ (masterIdentity.optString("nodeLabel").isEmpty() ? "" : " · " + masterIdentity.optString("nodeLabel"))
|
||||
+ (masterIdentity.optString("model").isEmpty() ? "" : "\n模型 " + masterIdentity.optString("model"));
|
||||
String meta = masterIdentity.optString("statusLabel", "")
|
||||
+ (masterIdentity.optString("lastSwitchedAt").isEmpty() ? "" : " · 最近切换 " + masterIdentity.optString("lastSwitchedAt"));
|
||||
appendContent(BossUi.buildCard(this, "当前主控身份", body, meta));
|
||||
}
|
||||
|
||||
appendContent(BossUi.buildCard(
|
||||
this,
|
||||
"主 Agent 调度结论",
|
||||
payload.optString("masterContextStrategySummary", "暂无调度摘要。"),
|
||||
"原生项目详情已接入 /api/v1/projects/{projectId}"
|
||||
));
|
||||
|
||||
if (threadContexts != null && threadContexts.length() > 0) {
|
||||
for (int i = 0; i < threadContexts.length(); i++) {
|
||||
JSONObject thread = threadContexts.optJSONObject(i);
|
||||
if (thread == null) continue;
|
||||
JSONObject snapshot = thread.optJSONObject("snapshot");
|
||||
if (snapshot == null) continue;
|
||||
String threadId = snapshot.optString("threadId");
|
||||
String body = snapshot.optString("summary", "暂无摘要");
|
||||
String meta = snapshot.optString("workerId", "-")
|
||||
+ " · " + snapshot.optString("nodeId", "-")
|
||||
+ " · " + snapshot.optInt("contextBudgetRemainingPct", 0) + "%"
|
||||
+ " · " + snapshot.optString("contextBudgetLevel", "safe");
|
||||
appendContent(BossUi.buildCard(
|
||||
this,
|
||||
snapshot.optString("title", "线程详情"),
|
||||
body,
|
||||
meta,
|
||||
v -> openThread(threadId)
|
||||
));
|
||||
}
|
||||
} else {
|
||||
appendContent(BossUi.buildEmptyCard(this, "当前项目还没有线程预算数据。"));
|
||||
}
|
||||
|
||||
if (recentLogs != null && recentLogs.length() > 0) {
|
||||
for (int i = 0; i < recentLogs.length(); i++) {
|
||||
JSONObject log = recentLogs.optJSONObject(i);
|
||||
if (log == null) continue;
|
||||
String body = log.optString("message", "无消息体");
|
||||
if (!log.optString("detail").isEmpty()) {
|
||||
body = body + "\n" + log.optString("detail");
|
||||
}
|
||||
String meta = log.optString("deviceId", "-")
|
||||
+ " · " + log.optString("category", "-")
|
||||
+ " · " + log.optString("createdAt", "-");
|
||||
appendContent(BossUi.buildCard(this, "实时 APP 日志", body, meta));
|
||||
}
|
||||
}
|
||||
|
||||
JSONArray messages = project == null ? null : project.optJSONArray("messages");
|
||||
if (messages != null && messages.length() > 0) {
|
||||
for (int i = 0; i < messages.length(); i++) {
|
||||
JSONObject message = messages.optJSONObject(i);
|
||||
if (message == null) continue;
|
||||
String meta = message.optString("sentAt", "-")
|
||||
+ (message.optString("kind").isEmpty() ? "" : " · " + message.optString("kind"));
|
||||
appendContent(BossUi.buildCard(
|
||||
if (message == null) {
|
||||
continue;
|
||||
}
|
||||
appendContent(BossUi.buildMessageBubble(
|
||||
this,
|
||||
message.optString("senderLabel", "消息"),
|
||||
message.optString("body", ""),
|
||||
meta
|
||||
formatMessageTime(message.optString("sentAt", "")),
|
||||
isOutgoingMessage(message.optString("senderLabel", "")),
|
||||
labelForMessageKind(message.optString("kind", ""))
|
||||
));
|
||||
}
|
||||
} else {
|
||||
appendContent(BossUi.buildMessagePlaceholder(this, "还没有项目消息,先发一条开始对话。"));
|
||||
}
|
||||
|
||||
appendContent(BossUi.buildCard(
|
||||
this,
|
||||
"媒体与转发说明",
|
||||
"语音、图片、视频与转发现在都通过原生入口触发,并写回现有 Boss 消息账本。",
|
||||
"对象存储与真实媒体文件仍保持 MVP 占位。"
|
||||
));
|
||||
setRefreshing(false);
|
||||
scrollChatToBottom();
|
||||
}
|
||||
|
||||
private LinearLayout buildActionGrid() {
|
||||
LinearLayout wrapper = new LinearLayout(this);
|
||||
wrapper.setOrientation(LinearLayout.VERTICAL);
|
||||
wrapper.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
));
|
||||
|
||||
wrapper.addView(buildActionRow(
|
||||
buildActionButton("发送消息", v -> chooseMessageKindAndSend()),
|
||||
buildActionButton("项目目标", v -> openGoals())
|
||||
));
|
||||
wrapper.addView(buildActionRow(
|
||||
buildActionButton("版本记录", v -> openVersions()),
|
||||
buildActionButton("消息转发", v -> openForward())
|
||||
));
|
||||
return wrapper;
|
||||
private void renderQuickActions() {
|
||||
if (quickActionsLayout == null) {
|
||||
return;
|
||||
}
|
||||
quickActionsLayout.removeAllViews();
|
||||
String[] actions = WechatSurfaceMapper.projectQuickActions();
|
||||
for (int i = 0; i < actions.length; i++) {
|
||||
String action = actions[i];
|
||||
Button button = buildQuickActionButton(action, i == 0);
|
||||
if ("项目目标".equals(action)) {
|
||||
button.setOnClickListener(v -> openGoals());
|
||||
} else if ("版本记录".equals(action)) {
|
||||
button.setOnClickListener(v -> openVersions());
|
||||
}
|
||||
quickActionsLayout.addView(button);
|
||||
}
|
||||
}
|
||||
|
||||
private LinearLayout buildActionRow(Button left, Button right) {
|
||||
LinearLayout row = new LinearLayout(this);
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
params.bottomMargin = BossUi.dp(this, 12);
|
||||
row.setLayoutParams(params);
|
||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||
|
||||
LinearLayout.LayoutParams childParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f);
|
||||
childParams.rightMargin = BossUi.dp(this, 6);
|
||||
left.setLayoutParams(childParams);
|
||||
|
||||
LinearLayout.LayoutParams rightParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f);
|
||||
rightParams.leftMargin = BossUi.dp(this, 6);
|
||||
right.setLayoutParams(rightParams);
|
||||
|
||||
row.addView(left);
|
||||
row.addView(right);
|
||||
return row;
|
||||
}
|
||||
|
||||
private Button buildActionButton(String label, android.view.View.OnClickListener listener) {
|
||||
Button button = BossUi.buildPrimaryButton(this, label);
|
||||
button.setOnClickListener(listener);
|
||||
private Button buildQuickActionButton(String label, boolean highlight) {
|
||||
Button button = new Button(this);
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, BossUi.dp(this, 40), 1f);
|
||||
if (quickActionsLayout.getChildCount() > 0) {
|
||||
params.leftMargin = BossUi.dp(this, 8);
|
||||
}
|
||||
button.setLayoutParams(params);
|
||||
button.setMinWidth(0);
|
||||
button.setText(label);
|
||||
button.setTextSize(14);
|
||||
button.setAllCaps(false);
|
||||
button.setPadding(BossUi.dp(this, 12), 0, BossUi.dp(this, 12), 0);
|
||||
button.setBackgroundResource(highlight ? R.drawable.bg_primary_button : R.drawable.bg_secondary_button);
|
||||
button.setTextColor(getColor(highlight ? R.color.boss_surface : R.color.boss_text_primary));
|
||||
return button;
|
||||
}
|
||||
|
||||
private void chooseMessageKindAndSend() {
|
||||
final String[] labels = {"文本消息", "语音意图", "图片意图", "视频意图"};
|
||||
final String[] kinds = {"text", "voice_intent", "image_intent", "video_intent"};
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("选择消息类型")
|
||||
.setItems(labels, (dialog, which) -> showSendDialog(kinds[which], labels[which]))
|
||||
.setNegativeButton("取消", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showSendDialog(String kind, String label) {
|
||||
final android.widget.EditText input = BossUi.buildInput(this, "请输入要发送给项目的内容", true);
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("发送" + label)
|
||||
.setView(input)
|
||||
.setNegativeButton("取消", null)
|
||||
.setPositiveButton("发送", (dialog, which) -> sendProjectMessage(kind, input.getText().toString().trim()))
|
||||
.show();
|
||||
private void sendTextMessageFromComposer() {
|
||||
if (composerInput == null) {
|
||||
return;
|
||||
}
|
||||
sendProjectMessage("text", composerInput.getText().toString().trim());
|
||||
}
|
||||
|
||||
private void sendProjectMessage(String kind, String body) {
|
||||
@@ -226,9 +167,11 @@ public class ProjectDetailActivity extends BossScreenActivity {
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
BossApiClient.ApiResponse response = apiClient.sendProjectMessage(projectId, body, kind);
|
||||
if (!response.ok()) throw new IllegalStateException(response.message());
|
||||
if (!response.ok()) {
|
||||
throw new IllegalStateException(response.message());
|
||||
}
|
||||
runOnUiThread(() -> {
|
||||
setRefreshing(false);
|
||||
composerInput.setText("");
|
||||
showMessage("消息已发送");
|
||||
reload();
|
||||
});
|
||||
@@ -255,20 +198,6 @@ public class ProjectDetailActivity extends BossScreenActivity {
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void openForward() {
|
||||
Intent intent = new Intent(this, ProjectForwardActivity.class);
|
||||
intent.putExtra(ProjectForwardActivity.EXTRA_PROJECT_ID, projectId);
|
||||
intent.putExtra(ProjectForwardActivity.EXTRA_PROJECT_NAME, initialProjectName);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void openThread(String threadId) {
|
||||
Intent intent = new Intent(this, ThreadDetailActivity.class);
|
||||
intent.putExtra(ThreadDetailActivity.EXTRA_THREAD_ID, threadId);
|
||||
intent.putExtra(ThreadDetailActivity.EXTRA_PROJECT_ID, projectId);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private String joinDeviceNames(@Nullable JSONArray devices) {
|
||||
if (devices == null || devices.length() == 0) {
|
||||
return "未绑定设备";
|
||||
@@ -276,10 +205,64 @@ public class ProjectDetailActivity extends BossScreenActivity {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < devices.length(); i++) {
|
||||
JSONObject device = devices.optJSONObject(i);
|
||||
if (device == null) continue;
|
||||
if (builder.length() > 0) builder.append(" / ");
|
||||
if (device == null) {
|
||||
continue;
|
||||
}
|
||||
if (builder.length() > 0) {
|
||||
builder.append(" / ");
|
||||
}
|
||||
builder.append(device.optString("name", device.optString("id", "设备")));
|
||||
}
|
||||
return builder.length() == 0 ? "未绑定设备" : builder.toString();
|
||||
}
|
||||
|
||||
private void scrollChatToBottom() {
|
||||
if (chatScrollView == null) {
|
||||
return;
|
||||
}
|
||||
chatScrollView.post(() -> chatScrollView.fullScroll(View.FOCUS_DOWN));
|
||||
}
|
||||
|
||||
private boolean isOutgoingMessage(String senderLabel) {
|
||||
if (TextUtils.isEmpty(senderLabel)) {
|
||||
return false;
|
||||
}
|
||||
return "你".equals(senderLabel)
|
||||
|| senderLabel.equals(apiClient.getDisplayName())
|
||||
|| senderLabel.equals(apiClient.getAccountLabel());
|
||||
}
|
||||
|
||||
private String formatMessageTime(String sentAt) {
|
||||
if (TextUtils.isEmpty(sentAt)) {
|
||||
return "";
|
||||
}
|
||||
int timeSeparator = sentAt.indexOf('T');
|
||||
if (timeSeparator >= 0 && sentAt.length() >= timeSeparator + 6) {
|
||||
return sentAt.substring(timeSeparator + 1, timeSeparator + 6);
|
||||
}
|
||||
int blankIndex = sentAt.indexOf(' ');
|
||||
if (blankIndex >= 0 && sentAt.length() >= blankIndex + 6) {
|
||||
return sentAt.substring(blankIndex + 1, blankIndex + 6);
|
||||
}
|
||||
return sentAt;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String labelForMessageKind(String kind) {
|
||||
if (TextUtils.isEmpty(kind) || "text".equals(kind)) {
|
||||
return null;
|
||||
}
|
||||
switch (kind) {
|
||||
case "voice_intent":
|
||||
return "语音";
|
||||
case "image_intent":
|
||||
return "图片";
|
||||
case "video_intent":
|
||||
return "视频";
|
||||
case "forward_notice":
|
||||
return "转发";
|
||||
default:
|
||||
return kind;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,12 @@ public final class WechatSurfaceMapper {
|
||||
"版本记录"
|
||||
);
|
||||
|
||||
private static final List<String> PROJECT_PRIMARY_SECTIONS = Arrays.asList(
|
||||
"quick_actions",
|
||||
"messages",
|
||||
"composer"
|
||||
);
|
||||
|
||||
private WechatSurfaceMapper() {
|
||||
}
|
||||
|
||||
@@ -58,6 +64,10 @@ public final class WechatSurfaceMapper {
|
||||
return PROJECT_QUICK_ACTIONS.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public static String[] projectPrimarySections() {
|
||||
return PROJECT_PRIMARY_SECTIONS.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private static String buildSubtitle(JSONObject source) {
|
||||
String statusValue = source.optString("status", "");
|
||||
String status;
|
||||
|
||||
12
android/app/src/main/res/drawable/bg_message_incoming.xml
Normal file
12
android/app/src/main/res/drawable/bg_message_incoming.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/boss_surface" />
|
||||
<corners
|
||||
android:topLeftRadius="8dp"
|
||||
android:topRightRadius="18dp"
|
||||
android:bottomLeftRadius="18dp"
|
||||
android:bottomRightRadius="18dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/boss_card_stroke" />
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/boss_green" />
|
||||
<corners
|
||||
android:topLeftRadius="18dp"
|
||||
android:topRightRadius="8dp"
|
||||
android:bottomLeftRadius="18dp"
|
||||
android:bottomRightRadius="18dp" />
|
||||
</shape>
|
||||
166
android/app/src/main/res/layout/activity_project_chat.xml
Normal file
166
android/app/src/main/res/layout/activity_project_chat.xml
Normal file
@@ -0,0 +1,166 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/boss_bg_app"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/boss_surface"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="14dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/screen_back_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:background="@drawable/bg_secondary_button"
|
||||
android:minWidth="0dp"
|
||||
android:paddingLeft="14dp"
|
||||
android:paddingRight="14dp"
|
||||
android:text="返回"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/boss_green"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/screen_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="项目详情"
|
||||
android:textColor="@color/boss_text_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/screen_subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:text="设备"
|
||||
android:textColor="@color/boss_text_muted"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/screen_header_action"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:background="@drawable/bg_secondary_button"
|
||||
android:minWidth="0dp"
|
||||
android:paddingLeft="14dp"
|
||||
android:paddingRight="14dp"
|
||||
android:text="操作"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/boss_green"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/screen_refresh_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:background="@drawable/bg_secondary_button"
|
||||
android:minWidth="0dp"
|
||||
android:paddingLeft="14dp"
|
||||
android:paddingRight="14dp"
|
||||
android:text="刷新"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/boss_green"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/screen_refresh_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/project_chat_scroll"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:overScrollMode="ifContentScrolls">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingBottom="20dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/project_chat_quick_actions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:orientation="horizontal" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/screen_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/boss_surface"
|
||||
android:gravity="bottom"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/project_chat_input"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_secondary_button"
|
||||
android:gravity="top|start"
|
||||
android:hint="输入消息"
|
||||
android:inputType="textCapSentences|textMultiLine"
|
||||
android:maxLines="4"
|
||||
android:minHeight="44dp"
|
||||
android:paddingLeft="14dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingRight="14dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:textColor="@color/boss_text_primary"
|
||||
android:textColorHint="@color/boss_text_muted" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/project_chat_send"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:background="@drawable/bg_primary_button"
|
||||
android:text="发送"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/boss_surface"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -85,6 +85,14 @@ public class WechatSurfaceMapperTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void projectPrimarySections_keepOnlyChatEssentials() throws Exception {
|
||||
assertArrayEquals(
|
||||
new String[]{"quick_actions", "messages", "composer"},
|
||||
WechatSurfaceMapper.projectPrimarySections()
|
||||
);
|
||||
}
|
||||
|
||||
private static final class StubJSONObject extends JSONObject {
|
||||
private final java.util.Map<String, Object> values = new java.util.HashMap<>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user