feat: add native forward target picker
This commit is contained in:
@@ -31,6 +31,7 @@
|
|||||||
<activity android:name=".ProjectGoalsActivity" android:exported="false" />
|
<activity android:name=".ProjectGoalsActivity" android:exported="false" />
|
||||||
<activity android:name=".ProjectVersionsActivity" android:exported="false" />
|
<activity android:name=".ProjectVersionsActivity" android:exported="false" />
|
||||||
<activity android:name=".ProjectForwardActivity" android:exported="false" />
|
<activity android:name=".ProjectForwardActivity" android:exported="false" />
|
||||||
|
<activity android:name=".ForwardTargetActivity" android:exported="false" />
|
||||||
<activity android:name=".ThreadDetailActivity" android:exported="false" />
|
<activity android:name=".ThreadDetailActivity" android:exported="false" />
|
||||||
<activity android:name=".ConversationInfoActivity" android:exported="false" />
|
<activity android:name=".ConversationInfoActivity" android:exported="false" />
|
||||||
<activity android:name=".GroupInfoActivity" android:exported="false" />
|
<activity android:name=".GroupInfoActivity" android:exported="false" />
|
||||||
|
|||||||
@@ -94,11 +94,10 @@ public class BossApiClient {
|
|||||||
return requestWithRestore("POST", "/api/v1/projects/" + encode(projectId) + "/messages", payload);
|
return requestWithRestore("POST", "/api/v1/projects/" + encode(projectId) + "/messages", payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApiResponse forwardProjectMessage(String projectId, String targetProjectId, String note) throws IOException, JSONException {
|
public ApiResponse forwardProjectMessage(String projectId, String targetProjectId, JSONObject payload) throws IOException, JSONException {
|
||||||
JSONObject payload = new JSONObject();
|
JSONObject requestPayload = payload == null ? new JSONObject() : payload;
|
||||||
payload.put("targetProjectId", targetProjectId);
|
requestPayload.put("targetProjectId", targetProjectId);
|
||||||
payload.put("note", note);
|
return requestWithRestore("POST", "/api/v1/projects/" + encode(projectId) + "/forwards", requestPayload);
|
||||||
return requestWithRestore("POST", "/api/v1/projects/" + encode(projectId) + "/forwards", payload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApiResponse getThreadDetail(String threadId) throws IOException, JSONException {
|
public ApiResponse getThreadDetail(String threadId) throws IOException, JSONException {
|
||||||
|
|||||||
@@ -0,0 +1,318 @@
|
|||||||
|
package com.hyzq.boss;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ForwardTargetActivity extends BossScreenActivity {
|
||||||
|
public static final String EXTRA_SOURCE_PROJECT_ID = "source_project_id";
|
||||||
|
public static final String EXTRA_FORWARD_MODE = "forward_mode";
|
||||||
|
public static final String EXTRA_SOURCE_MESSAGE_ID = "source_message_id";
|
||||||
|
public static final String EXTRA_SOURCE_MESSAGE_IDS = "source_message_ids";
|
||||||
|
|
||||||
|
private String sourceProjectId;
|
||||||
|
private String forwardMode;
|
||||||
|
@Nullable
|
||||||
|
private String sourceMessageId;
|
||||||
|
private final ArrayList<String> sourceMessageIds = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getLayoutResId() {
|
||||||
|
return R.layout.activity_forward_target;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
Intent intent = getIntent();
|
||||||
|
sourceProjectId = intent.getStringExtra(EXTRA_SOURCE_PROJECT_ID);
|
||||||
|
forwardMode = intent.getStringExtra(EXTRA_FORWARD_MODE);
|
||||||
|
sourceMessageId = intent.getStringExtra(EXTRA_SOURCE_MESSAGE_ID);
|
||||||
|
String[] messageIds = intent.getStringArrayExtra(EXTRA_SOURCE_MESSAGE_IDS);
|
||||||
|
if (messageIds != null) {
|
||||||
|
for (String messageId : messageIds) {
|
||||||
|
if (!TextUtils.isEmpty(messageId)) {
|
||||||
|
sourceMessageIds.add(messageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configureScreen("选择转发目标", buildSourceMeta());
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void reload() {
|
||||||
|
if (isEmpty(sourceProjectId)) {
|
||||||
|
showMessage("缺少源会话");
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setRefreshing(true);
|
||||||
|
executor.execute(() -> {
|
||||||
|
try {
|
||||||
|
BossApiClient.ApiResponse response = apiClient.getConversations();
|
||||||
|
if (!response.ok()) {
|
||||||
|
throw new IllegalStateException(response.message());
|
||||||
|
}
|
||||||
|
JSONArray conversations = response.json.optJSONArray("conversations");
|
||||||
|
List<JSONObject> targets = collectSelectableTargets(conversations, sourceProjectId);
|
||||||
|
runOnUiThread(() -> renderTargets(targets));
|
||||||
|
} catch (Exception error) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
setRefreshing(false);
|
||||||
|
replaceContent(BossUi.buildEmptyCard(this, "转发目标加载失败:" + error.getMessage()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<JSONObject> collectSelectableTargets(JSONArray conversations, String sourceProjectId) {
|
||||||
|
ArrayList<JSONObject> result = new ArrayList<>();
|
||||||
|
if (conversations == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < conversations.length(); i++) {
|
||||||
|
JSONObject item = conversations.optJSONObject(i);
|
||||||
|
if (item == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isEmpty(sourceProjectId) && sourceProjectId.equals(item.optString("projectId", ""))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.add(item);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONObject buildForwardPayload(String mode, @Nullable String sourceMessageId, List<String> sourceMessageIds)
|
||||||
|
throws JSONException {
|
||||||
|
MutableJsonObject payload = new MutableJsonObject();
|
||||||
|
String normalizedMode = isEmpty(mode) ? "single" : mode;
|
||||||
|
payload.put("mode", normalizedMode);
|
||||||
|
|
||||||
|
if (normalizedMode.startsWith("single")) {
|
||||||
|
String resolvedSourceMessageId = sourceMessageId;
|
||||||
|
if (isEmpty(resolvedSourceMessageId) && sourceMessageIds != null && sourceMessageIds.size() == 1) {
|
||||||
|
resolvedSourceMessageId = sourceMessageIds.get(0);
|
||||||
|
}
|
||||||
|
if (isEmpty(resolvedSourceMessageId)) {
|
||||||
|
throw new JSONException("sourceMessageId required");
|
||||||
|
}
|
||||||
|
payload.put("sourceMessageId", resolvedSourceMessageId);
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
MutableJsonArray orderedIds = new MutableJsonArray();
|
||||||
|
if (sourceMessageIds != null) {
|
||||||
|
for (String messageId : sourceMessageIds) {
|
||||||
|
if (!isEmpty(messageId)) {
|
||||||
|
orderedIds.put(messageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (orderedIds.length() == 0) {
|
||||||
|
throw new JSONException("sourceMessageIds required");
|
||||||
|
}
|
||||||
|
payload.put("sourceMessageIds", orderedIds);
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderTargets(List<JSONObject> targets) {
|
||||||
|
replaceContent(
|
||||||
|
BossUi.buildCard(
|
||||||
|
this,
|
||||||
|
"正在选择转发目标",
|
||||||
|
buildSourceBody(),
|
||||||
|
buildSourceMeta()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (targets.isEmpty()) {
|
||||||
|
appendContent(BossUi.buildEmptyCard(this, "当前没有可转发的目标会话。"));
|
||||||
|
setRefreshing(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (JSONObject target : targets) {
|
||||||
|
appendContent(BossUi.buildConversationRow(
|
||||||
|
this,
|
||||||
|
WechatSurfaceMapper.toConversationRow(target),
|
||||||
|
v -> forwardToTarget(target)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
setRefreshing(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildSourceBody() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("源会话:").append(isEmpty(sourceProjectId) ? "-" : sourceProjectId);
|
||||||
|
builder.append("\n转发模式:").append(isEmpty(forwardMode) ? "single" : forwardMode);
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildSourceMeta() {
|
||||||
|
int messageCount = sourceMessageIds.size();
|
||||||
|
if (!isEmpty(sourceMessageId)) {
|
||||||
|
return "source_message_id 已就绪";
|
||||||
|
}
|
||||||
|
if (messageCount > 0) {
|
||||||
|
return "source_message_ids " + messageCount + " 条";
|
||||||
|
}
|
||||||
|
return "等待聊天页入口补充消息选择";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void forwardToTarget(JSONObject target) {
|
||||||
|
if (target == null) {
|
||||||
|
showMessage("目标会话无效");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String targetProjectId = target.optString("projectId", "");
|
||||||
|
if (isEmpty(targetProjectId)) {
|
||||||
|
showMessage("目标会话无效");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSONObject payload = buildForwardPayload(
|
||||||
|
forwardMode,
|
||||||
|
sourceMessageId,
|
||||||
|
sourceMessageIds
|
||||||
|
);
|
||||||
|
setRefreshing(true);
|
||||||
|
executor.execute(() -> {
|
||||||
|
try {
|
||||||
|
BossApiClient.ApiResponse response = apiClient.forwardProjectMessage(sourceProjectId, targetProjectId, payload);
|
||||||
|
if (!response.ok()) {
|
||||||
|
throw new IllegalStateException(response.message());
|
||||||
|
}
|
||||||
|
boolean approvalRequired = response.json.optBoolean("approvalRequired", false);
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
setRefreshing(false);
|
||||||
|
if (approvalRequired) {
|
||||||
|
showMessage("已提交主 Agent 审批");
|
||||||
|
} else {
|
||||||
|
showMessage("转发成功");
|
||||||
|
}
|
||||||
|
setResult(RESULT_OK);
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
} catch (Exception error) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
setRefreshing(false);
|
||||||
|
showMessage("转发失败:" + error.getMessage());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (JSONException error) {
|
||||||
|
showMessage("缺少源消息,暂无法转发");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isEmpty(@Nullable String value) {
|
||||||
|
return value == null || value.length() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class MutableJsonObject extends JSONObject {
|
||||||
|
private final java.util.Map<String, Object> values = new java.util.LinkedHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONObject put(String key, boolean value) {
|
||||||
|
values.put(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONObject put(String key, int value) {
|
||||||
|
values.put(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONObject put(String key, long value) {
|
||||||
|
values.put(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONObject put(String key, Object value) {
|
||||||
|
values.put(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String optString(String key) {
|
||||||
|
Object value = values.get(key);
|
||||||
|
return value instanceof String ? (String) value : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String optString(String key, String fallback) {
|
||||||
|
String value = optString(key);
|
||||||
|
return value.isEmpty() ? fallback : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONArray optJSONArray(String key) {
|
||||||
|
Object value = values.get(key);
|
||||||
|
return value instanceof JSONArray ? (JSONArray) value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean optBoolean(String key, boolean fallback) {
|
||||||
|
Object value = values.get(key);
|
||||||
|
return value instanceof Boolean ? (Boolean) value : fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class MutableJsonArray extends JSONArray {
|
||||||
|
private final ArrayList<Object> values = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONArray put(boolean value) {
|
||||||
|
values.add(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONArray put(int value) {
|
||||||
|
values.add(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONArray put(long value) {
|
||||||
|
values.add(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONArray put(Object value) {
|
||||||
|
values.add(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int length() {
|
||||||
|
return values.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONObject optJSONObject(int index) {
|
||||||
|
if (index < 0 || index >= values.size()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Object value = values.get(index);
|
||||||
|
return value instanceof JSONObject ? (JSONObject) value : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,106 +1,30 @@
|
|||||||
package com.hyzq.boss;
|
package com.hyzq.boss;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
public class ProjectForwardActivity extends BossScreenActivity {
|
public class ProjectForwardActivity extends BossScreenActivity {
|
||||||
public static final String EXTRA_PROJECT_ID = "project_id";
|
public static final String EXTRA_PROJECT_ID = "project_id";
|
||||||
public static final String EXTRA_PROJECT_NAME = "project_name";
|
|
||||||
|
|
||||||
private String projectId;
|
private String projectId;
|
||||||
private String projectName;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
projectId = getIntent().getStringExtra(EXTRA_PROJECT_ID);
|
projectId = getIntent().getStringExtra(EXTRA_PROJECT_ID);
|
||||||
projectName = getIntent().getStringExtra(EXTRA_PROJECT_NAME);
|
configureScreen("消息转发", "正在切换到微信式转发");
|
||||||
configureScreen("消息转发", projectName == null ? "选择目标项目并写备注" : "源项目:" + projectName);
|
|
||||||
reload();
|
Intent intent = new Intent(this, ForwardTargetActivity.class);
|
||||||
|
intent.putExtra(ForwardTargetActivity.EXTRA_SOURCE_PROJECT_ID, projectId);
|
||||||
|
intent.putExtra(ForwardTargetActivity.EXTRA_FORWARD_MODE, "single_legacy");
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void reload() {
|
protected void reload() {
|
||||||
setRefreshing(true);
|
// 兼容页只负责跳转,不再承载旧的备注转发链路。
|
||||||
executor.execute(() -> {
|
|
||||||
try {
|
|
||||||
BossApiClient.ApiResponse response = apiClient.getConversations();
|
|
||||||
if (!response.ok()) throw new IllegalStateException(response.message());
|
|
||||||
runOnUiThread(() -> renderTargets(response.json.optJSONArray("conversations")));
|
|
||||||
} catch (Exception error) {
|
|
||||||
runOnUiThread(() -> {
|
|
||||||
setRefreshing(false);
|
|
||||||
replaceContent(BossUi.buildEmptyCard(this, "转发目标加载失败:" + error.getMessage()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderTargets(@Nullable JSONArray conversations) {
|
|
||||||
replaceContent(BossUi.buildCard(
|
|
||||||
this,
|
|
||||||
"原生转发入口",
|
|
||||||
"选择一个目标项目,填写备注后会走现有 `/api/v1/projects/{projectId}/forwards`。",
|
|
||||||
"源项目:" + (projectName == null ? projectId : projectName)
|
|
||||||
));
|
|
||||||
if (conversations == null || conversations.length() == 0) {
|
|
||||||
appendContent(BossUi.buildEmptyCard(this, "当前没有可转发的目标项目。"));
|
|
||||||
setRefreshing(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < conversations.length(); i++) {
|
|
||||||
JSONObject item = conversations.optJSONObject(i);
|
|
||||||
if (item == null) continue;
|
|
||||||
String targetProjectId = item.optString("projectId");
|
|
||||||
if (projectId.equals(targetProjectId)) continue;
|
|
||||||
appendContent(BossUi.buildCard(
|
|
||||||
this,
|
|
||||||
item.optString("projectTitle", "未命名项目"),
|
|
||||||
item.optString("preview", ""),
|
|
||||||
item.optString("latestReplyLabel", "最近更新"),
|
|
||||||
v -> openForwardDialog(targetProjectId, item.optString("projectTitle", targetProjectId))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
setRefreshing(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void openForwardDialog(String targetProjectId, String targetTitle) {
|
|
||||||
final android.widget.EditText input = BossUi.buildInput(this, "请输入要附带的转发说明", true);
|
|
||||||
input.setText("请同步关注 " + targetTitle + " 的当前进展。");
|
|
||||||
new AlertDialog.Builder(this)
|
|
||||||
.setTitle("转发到 " + targetTitle)
|
|
||||||
.setView(input)
|
|
||||||
.setNegativeButton("取消", null)
|
|
||||||
.setPositiveButton("转发", (dialog, which) -> forwardMessage(targetProjectId, input.getText().toString().trim()))
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void forwardMessage(String targetProjectId, String note) {
|
|
||||||
if (note.isEmpty()) {
|
|
||||||
showMessage("请先填写转发说明");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setRefreshing(true);
|
|
||||||
executor.execute(() -> {
|
|
||||||
try {
|
|
||||||
BossApiClient.ApiResponse response = apiClient.forwardProjectMessage(projectId, targetProjectId, note);
|
|
||||||
if (!response.ok()) throw new IllegalStateException(response.message());
|
|
||||||
runOnUiThread(() -> {
|
|
||||||
setRefreshing(false);
|
|
||||||
showMessage("转发成功");
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
} catch (Exception error) {
|
|
||||||
runOnUiThread(() -> {
|
|
||||||
setRefreshing(false);
|
|
||||||
showMessage("转发失败:" + error.getMessage());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
109
android/app/src/main/res/layout/activity_forward_target.xml
Normal file
109
android/app/src/main/res/layout/activity_forward_target.xml
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<?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="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingBottom="14dp">
|
||||||
|
|
||||||
|
<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="22sp"
|
||||||
|
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:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/screen_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/boss_panel"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="24dp" />
|
||||||
|
</ScrollView>
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.hyzq.boss;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ForwardTargetActivityTest {
|
||||||
|
@Test
|
||||||
|
public void filtersOutSourceConversationFromTargets() {
|
||||||
|
JSONArray conversations = new StubJSONArray(
|
||||||
|
new StubJSONObject().withString("projectId", "source").withString("projectTitle", "源会话"),
|
||||||
|
new StubJSONObject().withString("projectId", "target").withString("projectTitle", "目标会话")
|
||||||
|
);
|
||||||
|
|
||||||
|
List<JSONObject> result = ForwardTargetActivity.collectSelectableTargets(conversations, "source");
|
||||||
|
|
||||||
|
assertEquals(1, result.size());
|
||||||
|
assertEquals("target", result.get(0).optString("projectId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void singleModeRequiresOneMessageId() throws Exception {
|
||||||
|
JSONObject payload = ForwardTargetActivity.buildForwardPayload("single", "m1", java.util.List.of());
|
||||||
|
|
||||||
|
assertEquals("single", payload.optString("mode"));
|
||||||
|
assertEquals("m1", payload.optString("sourceMessageId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void bundleModeUsesOrderedMessageIds() throws Exception {
|
||||||
|
JSONObject payload = ForwardTargetActivity.buildForwardPayload("bundle", null, java.util.List.of("m1", "m2"));
|
||||||
|
|
||||||
|
assertEquals("bundle", payload.optString("mode"));
|
||||||
|
assertEquals(2, payload.optJSONArray("sourceMessageIds").length());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class StubJSONObject extends JSONObject {
|
||||||
|
private final java.util.Map<String, Object> values = new java.util.HashMap<>();
|
||||||
|
|
||||||
|
StubJSONObject withString(String key, String value) {
|
||||||
|
values.put(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String optString(String key) {
|
||||||
|
Object value = values.get(key);
|
||||||
|
return value instanceof String ? (String) value : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String optString(String key, String fallback) {
|
||||||
|
Object value = values.get(key);
|
||||||
|
return value instanceof String ? (String) value : fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class StubJSONArray extends JSONArray {
|
||||||
|
private final JSONObject[] values;
|
||||||
|
|
||||||
|
StubJSONArray(JSONObject... values) {
|
||||||
|
this.values = values == null ? new JSONObject[0] : values;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int length() {
|
||||||
|
return values.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONObject optJSONObject(int index) {
|
||||||
|
if (index < 0 || index >= values.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return values[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user