Restore chat-first project detail surface

This commit is contained in:
kris
2026-03-27 02:14:30 +08:00
parent b794ba05fa
commit 05e26afbf1
8 changed files with 423 additions and 157 deletions

View File

@@ -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();

View File

@@ -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(

View File

@@ -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;
}
}
}

View File

@@ -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;

View 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>

View File

@@ -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>

View 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>

View File

@@ -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<>();