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=".ProjectVersionsActivity" 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=".ConversationInfoActivity" 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);
|
||||
}
|
||||
|
||||
public ApiResponse forwardProjectMessage(String projectId, String targetProjectId, String note) throws IOException, JSONException {
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("targetProjectId", targetProjectId);
|
||||
payload.put("note", note);
|
||||
return requestWithRestore("POST", "/api/v1/projects/" + encode(projectId) + "/forwards", payload);
|
||||
public ApiResponse forwardProjectMessage(String projectId, String targetProjectId, JSONObject payload) throws IOException, JSONException {
|
||||
JSONObject requestPayload = payload == null ? new JSONObject() : payload;
|
||||
requestPayload.put("targetProjectId", targetProjectId);
|
||||
return requestWithRestore("POST", "/api/v1/projects/" + encode(projectId) + "/forwards", requestPayload);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class ProjectForwardActivity extends BossScreenActivity {
|
||||
public static final String EXTRA_PROJECT_ID = "project_id";
|
||||
public static final String EXTRA_PROJECT_NAME = "project_name";
|
||||
|
||||
private String projectId;
|
||||
private String projectName;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
projectId = getIntent().getStringExtra(EXTRA_PROJECT_ID);
|
||||
projectName = getIntent().getStringExtra(EXTRA_PROJECT_NAME);
|
||||
configureScreen("消息转发", projectName == null ? "选择目标项目并写备注" : "源项目:" + projectName);
|
||||
reload();
|
||||
configureScreen("消息转发", "正在切换到微信式转发");
|
||||
|
||||
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
|
||||
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