From ce8dcad41cb3454092b2b01b6e319f847c4f56d0 Mon Sep 17 00:00:00 2001 From: kris Date: Fri, 27 Mar 2026 01:58:34 +0800 Subject: [PATCH] feat: restore wechat-style root shell --- .../src/main/java/com/hyzq/boss/BossUi.java | 127 ++++++++---- .../main/java/com/hyzq/boss/MainActivity.java | 180 +++++++++--------- .../com/hyzq/boss/WechatSurfaceMapper.java | 10 + .../app/src/main/res/drawable/bg_list_row.xml | 16 ++ .../src/main/res/drawable/bg_tab_active.xml | 6 + .../src/main/res/drawable/bg_tab_inactive.xml | 6 + .../app/src/main/res/layout/activity_main.xml | 46 ++--- .../src/main/res/layout/activity_screen.xml | 12 +- android/app/src/main/res/values/colors.xml | 10 +- android/app/src/main/res/values/styles.xml | 6 +- .../hyzq/boss/WechatSurfaceMapperTest.java | 16 ++ 11 files changed, 278 insertions(+), 157 deletions(-) create mode 100644 android/app/src/main/res/drawable/bg_list_row.xml create mode 100644 android/app/src/main/res/drawable/bg_tab_active.xml create mode 100644 android/app/src/main/res/drawable/bg_tab_inactive.xml diff --git a/android/app/src/main/java/com/hyzq/boss/BossUi.java b/android/app/src/main/java/com/hyzq/boss/BossUi.java index 2b70ca4..618c02b 100644 --- a/android/app/src/main/java/com/hyzq/boss/BossUi.java +++ b/android/app/src/main/java/com/hyzq/boss/BossUi.java @@ -4,6 +4,8 @@ import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.graphics.Typeface; +import android.text.TextUtils; +import android.view.Gravity; import android.view.View; import android.widget.Button; import android.widget.EditText; @@ -16,6 +18,94 @@ import androidx.annotation.Nullable; public final class BossUi { private BossUi() {} + public static LinearLayout buildListRow( + Context context, + String title, + @Nullable String subtitle, + @Nullable String meta, + @Nullable String badge, + @Nullable View.OnClickListener listener + ) { + LinearLayout row = new LinearLayout(context); + row.setOrientation(LinearLayout.HORIZONTAL); + row.setGravity(Gravity.CENTER_VERTICAL); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ); + params.bottomMargin = dp(context, 1); + row.setLayoutParams(params); + row.setPadding(dp(context, 16), dp(context, 14), dp(context, 16), dp(context, 14)); + row.setBackgroundResource(R.drawable.bg_list_row); + if (listener != null) { + row.setClickable(true); + row.setFocusable(true); + row.setOnClickListener(listener); + } + + LinearLayout textWrap = new LinearLayout(context); + textWrap.setOrientation(LinearLayout.VERTICAL); + LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams( + 0, + LinearLayout.LayoutParams.WRAP_CONTENT, + 1f + ); + textWrap.setLayoutParams(textParams); + + TextView titleView = new TextView(context); + titleView.setText(title); + titleView.setTextSize(17); + titleView.setTypeface(Typeface.DEFAULT_BOLD); + titleView.setTextColor(context.getColor(R.color.boss_text_primary)); + textWrap.addView(titleView); + + if (!TextUtils.isEmpty(subtitle)) { + TextView subtitleView = new TextView(context); + subtitleView.setText(subtitle); + subtitleView.setTextSize(14); + subtitleView.setTextColor(context.getColor(R.color.boss_text_muted)); + subtitleView.setPadding(0, dp(context, 4), 0, 0); + textWrap.addView(subtitleView); + } + + if (!TextUtils.isEmpty(meta)) { + TextView metaView = new TextView(context); + metaView.setText(meta); + metaView.setTextSize(12); + metaView.setTextColor(context.getColor(R.color.boss_text_soft)); + metaView.setPadding(0, dp(context, 6), 0, 0); + textWrap.addView(metaView); + } + + row.addView(textWrap); + + LinearLayout accessoryWrap = new LinearLayout(context); + accessoryWrap.setOrientation(LinearLayout.VERTICAL); + accessoryWrap.setGravity(Gravity.END | Gravity.CENTER_VERTICAL); + + if (!TextUtils.isEmpty(badge)) { + TextView badgeView = new TextView(context); + badgeView.setText(badge); + badgeView.setTextSize(12); + badgeView.setTextColor(context.getColor(R.color.boss_green)); + badgeView.setBackgroundResource(R.drawable.bg_tab_active); + badgeView.setPadding(dp(context, 8), dp(context, 2), dp(context, 8), dp(context, 2)); + accessoryWrap.addView(badgeView); + } + + if (listener != null) { + TextView arrowView = new TextView(context); + arrowView.setText("›"); + arrowView.setTextSize(18); + arrowView.setTextColor(context.getColor(R.color.boss_text_soft)); + arrowView.setPadding(dp(context, 10), dp(context, 4), 0, 0); + accessoryWrap.addView(arrowView); + } + + row.addView(accessoryWrap); + return row; + } + public static LinearLayout buildCard(Context context, String title, String body, String meta) { return buildCard(context, title, body, meta, null); } @@ -74,42 +164,7 @@ public final class BossUi { @Nullable String badge, View.OnClickListener listener ) { - LinearLayout row = buildCard(context, title, description, badge == null ? "点击进入" : badge, listener); - row.setOrientation(LinearLayout.HORIZONTAL); - row.removeAllViews(); - - LinearLayout textWrap = new LinearLayout(context); - textWrap.setOrientation(LinearLayout.VERTICAL); - LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams( - 0, - LinearLayout.LayoutParams.WRAP_CONTENT, - 1f - ); - textWrap.setLayoutParams(textParams); - - TextView titleView = new TextView(context); - titleView.setText(title); - titleView.setTextSize(17); - titleView.setTypeface(Typeface.DEFAULT_BOLD); - titleView.setTextColor(context.getColor(R.color.boss_text_primary)); - - TextView descView = new TextView(context); - descView.setText(description); - descView.setTextSize(13); - descView.setTextColor(context.getColor(R.color.boss_text_muted)); - descView.setPadding(0, dp(context, 6), 0, 0); - - textWrap.addView(titleView); - textWrap.addView(descView); - - TextView accessory = new TextView(context); - accessory.setText(badge == null ? "›" : badge + " ›"); - accessory.setTextSize(13); - accessory.setTextColor(context.getColor(R.color.boss_green)); - - row.addView(textWrap); - row.addView(accessory); - return row; + return buildListRow(context, title, description, null, badge, listener); } public static LinearLayout buildEmptyCard(Context context, String text) { diff --git a/android/app/src/main/java/com/hyzq/boss/MainActivity.java b/android/app/src/main/java/com/hyzq/boss/MainActivity.java index ac7b4c5..7fe2229 100644 --- a/android/app/src/main/java/com/hyzq/boss/MainActivity.java +++ b/android/app/src/main/java/com/hyzq/boss/MainActivity.java @@ -108,6 +108,11 @@ public class MainActivity extends AppCompatActivity { tabMe = findViewById(R.id.tab_me); screenRefresh = findViewById(R.id.screen_refresh); screenContent = findViewById(R.id.screen_content); + + String[] rootTabs = WechatSurfaceMapper.rootTabLabels(); + tabConversations.setText(rootTabs[0]); + tabDevices.setText(rootTabs[1]); + tabMe.setText(rootTabs[2]); } private void bindActions() { @@ -277,16 +282,16 @@ public class MainActivity extends AppCompatActivity { switch (activeTab) { case "devices": - updateHeader("设备", "只展示当前正式接入生产链路的设备。"); + updateHeader("设备", "生产设备列表"); renderDevicesRoot(); break; case "me": - updateHeader("我的", "账号、安全、技能、运维、OTA 都从这里进入。"); + updateHeader("我的", "账号与应用设置"); renderMeRoot(); break; case "conversations": default: - updateHeader("会话", "原生会话列表直接消费 /api/v1/conversations。"); + updateHeader("会话", "最近消息"); renderConversationsRoot(); break; } @@ -304,19 +309,12 @@ public class MainActivity extends AppCompatActivity { } private void styleTab(Button button, boolean active) { - button.setBackgroundResource(active ? R.drawable.bg_primary_button : R.drawable.bg_secondary_button); - button.setTextColor(getColor(active ? R.color.boss_surface : R.color.boss_green)); + button.setBackgroundResource(active ? R.drawable.bg_tab_active : R.drawable.bg_tab_inactive); + button.setTextColor(getColor(active ? R.color.boss_green : R.color.boss_text_muted)); } private void renderConversationsRoot() { screenContent.removeAllViews(); - screenContent.addView(BossUi.buildCard( - this, - "会话首页", - "当前原生首页会直接进入项目详情、目标、版本、转发与线程预算详情。", - conversationsData == null ? "正在等待数据" : "会话数 " + conversationsData.length() - )); - if (conversationsData == null || conversationsData.length() == 0) { screenContent.addView(BossUi.buildEmptyCard(this, "当前没有会话数据。")); return; @@ -326,41 +324,35 @@ public class MainActivity extends AppCompatActivity { JSONObject item = conversationsData.optJSONObject(i); if (item == null) continue; String projectId = item.optString("projectId", ""); - String title = item.optString("projectTitle", "未命名会话"); - StringBuilder body = new StringBuilder(item.optString("preview", "暂无预览")); - if (item.optInt("activeDeviceCount", 0) > 0) { - body.append("\n设备 ").append(item.optString("deviceNamesPreview", "未标注")); - } - JSONObject budget = item.optJSONObject("contextBudgetIndicator"); - String meta = "风险 " + item.optString("riskLevel", "unknown") - + " · 未读 " + item.optInt("unreadCount", 0) - + " · " + item.optString("latestReplyLabel", "-"); - if (budget != null && budget.optBoolean("visible", false)) { - meta = meta + " · 预算 " + budget.optInt("percent", 0) + "%"; - } - screenContent.addView(BossUi.buildCard(this, title, body.toString(), meta, v -> { + WechatSurfaceMapper.ConversationRow row = WechatSurfaceMapper.toConversationRow(item); + String badge = row.unreadCount > 0 ? String.valueOf(row.unreadCount) : null; + screenContent.addView(BossUi.buildListRow( + this, + row.title.isEmpty() ? "未命名会话" : row.title, + row.preview.isEmpty() ? "暂无预览" : row.preview, + row.timeLabel.isEmpty() ? null : row.timeLabel, + badge, + v -> { if (projectId.isEmpty()) { showMessage("缺少 projectId"); return; } - openProject(projectId, title); + openProject(projectId, row.title.isEmpty() ? "未命名会话" : row.title); })); } } private void renderDevicesRoot() { screenContent.removeAllViews(); - screenContent.addView(BossUi.buildCard( + screenContent.addView(BossUi.buildListRow( this, - "设备首页", - "设备详情、技能清单和配对草稿都改为原生页。", - devicesData == null ? "正在等待数据" : "设备数 " + devicesData.length() + "添加设备", + "通过配对码接入新的生产设备", + null, + null, + v -> startActivity(new Intent(this, DeviceEnrollmentActivity.class)) )); - Button addDeviceButton = BossUi.buildPrimaryButton(this, "添加设备"); - addDeviceButton.setOnClickListener(v -> startActivity(new Intent(this, DeviceEnrollmentActivity.class))); - screenContent.addView(addDeviceButton); - if (devicesData == null || devicesData.length() == 0) { screenContent.addView(BossUi.buildEmptyCard(this, "当前没有接入设备。")); return; @@ -370,18 +362,14 @@ public class MainActivity extends AppCompatActivity { JSONObject item = devicesData.optJSONObject(i); if (item == null) continue; String deviceId = item.optString("id", ""); - String title = item.optString("name", "未命名设备"); - String body = item.optString("note", item.optString("endpoint", "暂无设备说明")); - String meta = "状态 " + item.optString("status", "unknown") - + " · 账号 " + item.optString("account", "-") - + " · 5h " + item.optInt("quota5h", 0) - + " · 7d " + item.optInt("quota7d", 0); - screenContent.addView(BossUi.buildCard(this, title, body, meta, v -> { + WechatSurfaceMapper.DeviceRow row = WechatSurfaceMapper.toDeviceRow(item); + String meta = "5h " + item.optInt("quota5h", 0) + " · 7d " + item.optInt("quota7d", 0); + screenContent.addView(BossUi.buildListRow(this, row.title, row.subtitle, meta, null, v -> { if (deviceId.isEmpty()) { showMessage("缺少 deviceId"); return; } - openDevice(deviceId, title); + openDevice(deviceId, row.title); })); } } @@ -395,55 +383,24 @@ public class MainActivity extends AppCompatActivity { ? apiClient.getAccountLabel() : sessionData.optString("account", apiClient.getAccountLabel()); String expiresAt = sessionData == null ? "-" : sessionData.optString("expiresAt", "-"); - screenContent.addView(BossUi.buildCard( + screenContent.addView(BossUi.buildListRow( this, displayName, - "账号 " + account + "\n当前原生客户端已覆盖会话 / 设备 / 我的一级导航。", - "会话到期 " + expiresAt + "账号 " + account, + "会话到期 " + expiresAt, + null, + null )); - screenContent.addView(BossUi.buildMenuRow( - this, - "账号与安全", - "查看当前会话、登录模式和退出登录。", - null, - v -> startActivity(new Intent(this, SecurityActivity.class)) - )); - screenContent.addView(BossUi.buildMenuRow( - this, - "设置", - "实时刷新、风险徽标和默认首页。", - null, - v -> startActivity(new Intent(this, SettingsActivity.class)) - )); - screenContent.addView(BossUi.buildMenuRow( - this, - "运维与修复", - "查看故障、repair ticket、审计请求和能力注册表。", - null, - v -> startActivity(new Intent(this, OpsCenterActivity.class)) - )); - screenContent.addView(BossUi.buildMenuRow( - this, - "AI 账号", - "管理主 GPT、备用 GPT、Master Codex Node 与 API 容灾。", - null, - v -> startActivity(new Intent(this, AiAccountsActivity.class)) - )); - screenContent.addView(BossUi.buildMenuRow( - this, - "技能", - "按绑定设备查看 Skill,并一键复制调用语句。", - null, - v -> startActivity(new Intent(this, SkillInventoryActivity.class)) - )); - screenContent.addView(BossUi.buildMenuRow( - this, - "关于", - "查看版本、OTA 状态和当前绑定节点。", - otaData == null ? null : otaData.optBoolean("hasOta", false) ? "OTA" : null, - v -> startActivity(new Intent(this, AboutActivity.class)) - )); + for (String title : WechatSurfaceMapper.rootMeMenuTitles()) { + screenContent.addView(BossUi.buildMenuRow( + this, + title, + meDescriptionFor(title), + meBadgeFor(title), + v -> openMeEntry(title) + )); + } if (otaData != null) { JSONObject availableRelease = otaData.optJSONObject("availableRelease"); @@ -502,4 +459,53 @@ public class MainActivity extends AppCompatActivity { private void showMessage(String text) { Toast.makeText(this, text, Toast.LENGTH_SHORT).show(); } + + private void openMeEntry(String title) { + Intent intent; + switch (title) { + case "账号与安全": + intent = new Intent(this, SecurityActivity.class); + break; + case "AI 账号": + intent = new Intent(this, AiAccountsActivity.class); + break; + case "设置": + intent = new Intent(this, SettingsActivity.class); + break; + case "技能": + intent = new Intent(this, SkillInventoryActivity.class); + break; + case "关于": + intent = new Intent(this, AboutActivity.class); + break; + default: + showMessage("暂未接入:" + title); + return; + } + startActivity(intent); + } + + private String meDescriptionFor(String title) { + switch (title) { + case "账号与安全": + return "查看当前会话、登录模式和退出登录"; + case "AI 账号": + return "管理主 GPT、备用 GPT 与 API 容灾"; + case "设置": + return "调整默认首页和刷新偏好"; + case "技能": + return "按绑定设备查看 Skill 清单"; + case "关于": + return "查看版本、OTA 状态和当前绑定节点"; + default: + return ""; + } + } + + private @Nullable String meBadgeFor(String title) { + if ("关于".equals(title) && otaData != null && otaData.optBoolean("hasOta", false)) { + return "OTA"; + } + return null; + } } diff --git a/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java b/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java index da26239..0c85385 100644 --- a/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java +++ b/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java @@ -6,6 +6,12 @@ import java.util.Arrays; import java.util.List; public final class WechatSurfaceMapper { + private static final List ROOT_TAB_LABELS = Arrays.asList( + "会话", + "设备", + "我的" + ); + private static final List ROOT_ME_MENU_TITLES = Arrays.asList( "账号与安全", "AI 账号", @@ -40,6 +46,10 @@ public final class WechatSurfaceMapper { ); } + public static String[] rootTabLabels() { + return ROOT_TAB_LABELS.toArray(new String[0]); + } + public static String[] rootMeMenuTitles() { return ROOT_ME_MENU_TITLES.toArray(new String[0]); } diff --git a/android/app/src/main/res/drawable/bg_list_row.xml b/android/app/src/main/res/drawable/bg_list_row.xml new file mode 100644 index 0000000..a120e43 --- /dev/null +++ b/android/app/src/main/res/drawable/bg_list_row.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/bg_tab_active.xml b/android/app/src/main/res/drawable/bg_tab_active.xml new file mode 100644 index 0000000..497fe49 --- /dev/null +++ b/android/app/src/main/res/drawable/bg_tab_active.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/android/app/src/main/res/drawable/bg_tab_inactive.xml b/android/app/src/main/res/drawable/bg_tab_inactive.xml new file mode 100644 index 0000000..e3c44f7 --- /dev/null +++ b/android/app/src/main/res/drawable/bg_tab_inactive.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index f366d37..992aed9 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -2,7 +2,7 @@ + android:background="@color/boss_bg_app"> @@ -109,12 +109,13 @@ + android:paddingBottom="14dp">