chore: polish native wechat ui release v2.5.1
This commit is contained in:
@@ -90,7 +90,7 @@ Android APK:
|
||||
- 已生成 Android debug APK:`android/app/build/outputs/apk/debug/app-debug.apk`
|
||||
- 已生成 Android signed release APK:`android/app/build/outputs/apk/release/app-release.apk`
|
||||
- `npm run apk:release` 还会额外产出带版本号的文件:`android/app/build/outputs/apk/release/boss-android-v{versionName}-release.apk`
|
||||
- 当前最新 release 构建版本:`2.5.0`(`versionCode=13`)
|
||||
- 当前最新 release 构建版本:`2.5.1`(`versionCode=14`)
|
||||
- 当前 APK 已切到原生 Android 客户端:`MainActivity + BossApiClient + 原生 XML 布局`
|
||||
- 当前原生活动页已经覆盖:会话首页、项目详情、项目目标、版本记录、会话信息、群资料、发起群聊、消息转发、线程详情、设备详情、添加设备、账号与安全、设置、AI 账号、技能、运维中心、关于
|
||||
- 当前原生一级体验已回退到微信式交互:`会话 / 设备 / 我的` 固定底部 tab,会话首页是简单聊天列表,`主 Agent / 审计对话` 以普通置顶会话样式排在最前;项目详情页是聊天优先,只保留 `项目目标 / 版本记录` 两个轻入口
|
||||
@@ -110,6 +110,7 @@ Android APK:
|
||||
- `2.3.0` 已把会话模型切到“线程 = 聊天窗口”,补上文件夹名副信息、后台活跃数量动态图标、微信式会话信息页、线程改名、独立群聊创建、群资料页,以及 `主 Agent / 审计对话` 普通置顶会话化
|
||||
- `2.4.0` 已把消息转发切到微信式原生链路:聊天页支持长按消息操作、多选合并转发、统一目标会话选择页;单条消息转发显示为普通转发消息,多条消息转发显示为“聊天记录”卡片
|
||||
- `2.5.0` 已补齐聊天附件主链:原生聊天框左侧 `+` 会打开底部抽屉,支持图片 / 视频 / 文件发送;默认走服务器文件存储,`我的 > 附件与存储` 可切到阿里 OSS 私有桶;附件消息已支持下载 / 打开、手动分析、自动分析状态,以及带 task token 的主 Agent 附件分析链接
|
||||
- `2.5.1` 继续收口微信式原生 UI:聊天页普通态顶部已隐藏刷新按钮,只保留右上角“信息”;发起群聊页顶部说明和选择区已压成更轻的会话式密度,候选线程继续复用微信式会话卡片
|
||||
|
||||
## 本地启动
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ android {
|
||||
applicationId "com.hyzq.boss"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 13
|
||||
versionName "2.5.0"
|
||||
versionCode 14
|
||||
versionName "2.5.1"
|
||||
buildConfigField "String", "BOSS_API_BASE_URL", "\"https://boss.hyzq.net\""
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -84,27 +84,15 @@ public class GroupCreateActivity extends BossScreenActivity {
|
||||
sourceProjectName = threadMeta == null
|
||||
? sourceProjectName
|
||||
: threadMeta.optString("threadDisplayName", sourceProjectName == null ? "当前会话" : sourceProjectName);
|
||||
|
||||
appendContent(BossUi.buildCard(
|
||||
this,
|
||||
"新建独立群聊",
|
||||
"群聊不是升级原会话,而是以当前会话为源,新建一个独立线程。",
|
||||
buildSourceMeta(threadMeta, participants)
|
||||
));
|
||||
|
||||
appendContent(BossUi.buildCard(
|
||||
this,
|
||||
sourceProjectName,
|
||||
buildSourceBody(threadMeta, participants),
|
||||
sourceProjectId + (sourceFolderName.isEmpty() ? "" : " · " + sourceFolderName)
|
||||
));
|
||||
} else {
|
||||
appendContent(BossUi.buildCard(
|
||||
this,
|
||||
"从会话首页发起群聊",
|
||||
"你可以直接把任意线程拉进一个新的独立群聊,原来的单线程会话会保留不变。",
|
||||
"至少选择 2 个线程"
|
||||
));
|
||||
}
|
||||
for (SummaryCardSpec cardSpec : buildHeaderCardSpecs(
|
||||
hasSourceProject(),
|
||||
sourceProjectId,
|
||||
sourceProjectName,
|
||||
threadMeta,
|
||||
participants
|
||||
)) {
|
||||
appendContent(BossUi.buildCard(this, cardSpec.title, cardSpec.body, cardSpec.meta));
|
||||
}
|
||||
|
||||
if (rebuildCandidates) {
|
||||
@@ -133,15 +121,16 @@ public class GroupCreateActivity extends BossScreenActivity {
|
||||
lastCandidateProjectIds.addAll(nextCandidateProjectIds);
|
||||
}
|
||||
|
||||
SummaryCardSpec selectionCard = buildSelectionCardSpec(
|
||||
candidates.size(),
|
||||
selectedProjectIds.size(),
|
||||
hasSourceProject()
|
||||
);
|
||||
appendContent(BossUi.buildCard(
|
||||
this,
|
||||
"选择其他线程",
|
||||
candidates.isEmpty()
|
||||
? "当前没有可加入的其他线程。"
|
||||
: selectedProjectIds.isEmpty()
|
||||
? "你已取消全部勾选,可继续手动选择。"
|
||||
: "已保留你当前的勾选状态。",
|
||||
"已选 " + selectedProjectIds.size() + " 个线程"
|
||||
selectionCard.title,
|
||||
selectionCard.body,
|
||||
selectionCard.meta
|
||||
));
|
||||
|
||||
candidateListLayout = new LinearLayout(this);
|
||||
@@ -203,6 +192,59 @@ public class GroupCreateActivity extends BossScreenActivity {
|
||||
);
|
||||
}
|
||||
|
||||
static List<SummaryCardSpec> buildHeaderCardSpecs(
|
||||
boolean hasSourceProject,
|
||||
@Nullable String sourceProjectId,
|
||||
@Nullable String sourceProjectName,
|
||||
@Nullable JSONObject threadMeta,
|
||||
@Nullable JSONArray participants
|
||||
) {
|
||||
List<SummaryCardSpec> cards = new ArrayList<>(1);
|
||||
if (hasSourceProject) {
|
||||
String resolvedSourceProjectName = sourceProjectName == null || sourceProjectName.isEmpty()
|
||||
? "当前会话"
|
||||
: sourceProjectName;
|
||||
cards.add(new SummaryCardSpec(
|
||||
resolvedSourceProjectName,
|
||||
buildSourceBody(sourceProjectId, threadMeta, participants),
|
||||
"新群会单独创建,原会话保留"
|
||||
));
|
||||
return cards;
|
||||
}
|
||||
cards.add(new SummaryCardSpec(
|
||||
"从会话中发起群聊",
|
||||
"至少选择 2 个线程,新群会单独创建。",
|
||||
"原有单线程会话会保留"
|
||||
));
|
||||
return cards;
|
||||
}
|
||||
|
||||
static SummaryCardSpec buildSelectionCardSpec(
|
||||
int candidateCount,
|
||||
int selectedCount,
|
||||
boolean hasSourceProject
|
||||
) {
|
||||
if (candidateCount <= 0) {
|
||||
return new SummaryCardSpec(
|
||||
"选择其他线程",
|
||||
"当前没有可加入的其他线程",
|
||||
"候选 0 个 · 已选 0 个"
|
||||
);
|
||||
}
|
||||
if (selectedCount <= 0) {
|
||||
return new SummaryCardSpec(
|
||||
"选择其他线程",
|
||||
hasSourceProject ? "至少选择 1 个其他线程" : "至少选择 2 个线程",
|
||||
"候选 " + candidateCount + " 个 · 已选 0 个"
|
||||
);
|
||||
}
|
||||
return new SummaryCardSpec(
|
||||
"选择其他线程",
|
||||
"继续点选可调整成员",
|
||||
"候选 " + candidateCount + " 个 · 已选 " + selectedCount + " 个"
|
||||
);
|
||||
}
|
||||
|
||||
private LinearLayout buildCandidateRow(CandidateConversation candidate) {
|
||||
return BossUi.buildConversationRow(
|
||||
this,
|
||||
@@ -348,25 +390,33 @@ public class GroupCreateActivity extends BossScreenActivity {
|
||||
return sourceProjectId != null && !sourceProjectId.isEmpty();
|
||||
}
|
||||
|
||||
private String buildSourceMeta(@Nullable JSONObject threadMeta, @Nullable JSONArray participants) {
|
||||
String folderName = threadMeta == null ? "" : threadMeta.optString("folderName", "");
|
||||
int count = participants == null ? 0 : participants.length();
|
||||
String memberLabel = count <= 0 ? "暂无参与线程" : count + " 个参与线程";
|
||||
if (folderName.isEmpty()) {
|
||||
return memberLabel;
|
||||
}
|
||||
return folderName + " · " + memberLabel;
|
||||
}
|
||||
|
||||
private String buildSourceBody(@Nullable JSONObject threadMeta, @Nullable JSONArray participants) {
|
||||
private static String buildSourceBody(
|
||||
@Nullable String sourceProjectId,
|
||||
@Nullable JSONObject threadMeta,
|
||||
@Nullable JSONArray participants
|
||||
) {
|
||||
String threadId = threadMeta == null ? sourceProjectId : threadMeta.optString("threadId", sourceProjectId);
|
||||
String folderName = threadMeta == null ? "" : threadMeta.optString("folderName", "");
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("来源线程:").append(threadId);
|
||||
builder.append("\n文件夹:").append(folderName.isEmpty() ? "未命名文件夹" : folderName);
|
||||
builder.append("\n参与线程:").append(participants == null ? 0 : participants.length());
|
||||
builder.append("\n默认规则:会自动勾选当前会话之外的其他线程");
|
||||
return builder.toString();
|
||||
String resolvedFolderName = folderName.isEmpty() ? "未命名文件夹" : folderName;
|
||||
String resolvedThreadId = threadId == null || threadId.isEmpty() ? "未命名线程" : threadId;
|
||||
return "来源线程 " + resolvedThreadId
|
||||
+ " · "
|
||||
+ resolvedFolderName
|
||||
+ " · "
|
||||
+ (participants == null ? 0 : participants.length())
|
||||
+ " 个参与线程";
|
||||
}
|
||||
|
||||
static final class SummaryCardSpec {
|
||||
final String title;
|
||||
final String body;
|
||||
final String meta;
|
||||
|
||||
SummaryCardSpec(String title, String body, String meta) {
|
||||
this.title = title;
|
||||
this.body = body;
|
||||
this.meta = meta;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CandidateConversation {
|
||||
|
||||
@@ -136,7 +136,7 @@ public final class ProjectChatUiState {
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
!conversationInfoReady,
|
||||
conversationInfoReady,
|
||||
false,
|
||||
"返回",
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -12,6 +13,41 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class GroupCreateActivityTest {
|
||||
@Test
|
||||
public void buildHeaderCardSpecs_collapsesSourceFlowIntoSingleCompactCard() {
|
||||
JSONObject threadMeta = new StubJSONObject()
|
||||
.withString("threadDisplayName", "北区试产线回归")
|
||||
.withString("threadId", "thread-7")
|
||||
.withString("folderName", "Mac Studio");
|
||||
JSONArray participants = new StubJSONArray(
|
||||
new StubJSONObject(),
|
||||
new StubJSONObject(),
|
||||
new StubJSONObject()
|
||||
);
|
||||
|
||||
List<GroupCreateActivity.SummaryCardSpec> cards = GroupCreateActivity.buildHeaderCardSpecs(
|
||||
true,
|
||||
"source-1",
|
||||
"北区试产线回归",
|
||||
threadMeta,
|
||||
participants
|
||||
);
|
||||
|
||||
assertEquals(1, cards.size());
|
||||
assertEquals("北区试产线回归", cards.get(0).title);
|
||||
assertEquals("来源线程 thread-7 · Mac Studio · 3 个参与线程", cards.get(0).body);
|
||||
assertEquals("新群会单独创建,原会话保留", cards.get(0).meta);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildSelectionCardSpec_usesCompactWechatStyleHint() {
|
||||
GroupCreateActivity.SummaryCardSpec card = GroupCreateActivity.buildSelectionCardSpec(5, 0, true);
|
||||
|
||||
assertEquals("选择其他线程", card.title);
|
||||
assertEquals("至少选择 1 个其他线程", card.body);
|
||||
assertEquals("候选 5 个 · 已选 0 个", card.meta);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void collectSelectableConversationItems_filtersOutExistingGroupChats() {
|
||||
JSONObject threadConversation = new StubJSONObject()
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.hyzq.boss;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(sdk = 34)
|
||||
public class GroupCreateActivityUiTest {
|
||||
@Test
|
||||
public void renderCreatePageUsesCompactSummaryCardsForSourceFlow() throws Exception {
|
||||
Intent intent = new Intent()
|
||||
.putExtra(GroupCreateActivity.EXTRA_SOURCE_PROJECT_ID, "source-1")
|
||||
.putExtra(GroupCreateActivity.EXTRA_SOURCE_PROJECT_NAME, "北区试产线回归");
|
||||
TestGroupCreateActivity activity = Robolectric
|
||||
.buildActivity(TestGroupCreateActivity.class, intent)
|
||||
.setup()
|
||||
.get();
|
||||
|
||||
ReflectionHelpers.callInstanceMethod(
|
||||
activity,
|
||||
"renderCreatePage",
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildParticipantsPayload()),
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildConversationsPayload()),
|
||||
ReflectionHelpers.ClassParameter.from(boolean.class, true)
|
||||
);
|
||||
|
||||
LinearLayout content = activity.findViewById(R.id.screen_content);
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(0), "北区试产线回归"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(0), "来源线程 thread-7 · Mac Studio · 3 个参与线程"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(0), "新群会单独创建,原会话保留"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(1), "选择其他线程"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(1), "继续点选可调整成员"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(1), "候选 2 个 · 已选 2 个"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toggleSelectionRefreshesRenderedSelectionSummary() throws Exception {
|
||||
Intent intent = new Intent()
|
||||
.putExtra(GroupCreateActivity.EXTRA_SOURCE_PROJECT_ID, "source-1")
|
||||
.putExtra(GroupCreateActivity.EXTRA_SOURCE_PROJECT_NAME, "北区试产线回归");
|
||||
TestGroupCreateActivity activity = Robolectric
|
||||
.buildActivity(TestGroupCreateActivity.class, intent)
|
||||
.setup()
|
||||
.get();
|
||||
|
||||
ReflectionHelpers.callInstanceMethod(
|
||||
activity,
|
||||
"renderCreatePage",
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildParticipantsPayload()),
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildConversationsPayload()),
|
||||
ReflectionHelpers.ClassParameter.from(boolean.class, true)
|
||||
);
|
||||
|
||||
ReflectionHelpers.callInstanceMethod(
|
||||
activity,
|
||||
"toggleSelection",
|
||||
ReflectionHelpers.ClassParameter.from(String.class, "thread-2")
|
||||
);
|
||||
ReflectionHelpers.callInstanceMethod(
|
||||
activity,
|
||||
"toggleSelection",
|
||||
ReflectionHelpers.ClassParameter.from(String.class, "thread-3")
|
||||
);
|
||||
|
||||
LinearLayout content = activity.findViewById(R.id.screen_content);
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(1), "至少选择 1 个其他线程"));
|
||||
assertTrue(viewTreeContainsText(content.getChildAt(1), "候选 2 个 · 已选 0 个"));
|
||||
}
|
||||
|
||||
private static JSONObject buildParticipantsPayload() throws Exception {
|
||||
JSONObject threadMeta = new JSONObject()
|
||||
.put("threadDisplayName", "北区试产线回归")
|
||||
.put("threadId", "thread-7")
|
||||
.put("folderName", "Mac Studio");
|
||||
JSONArray participants = new JSONArray()
|
||||
.put(new JSONObject().put("projectId", "source-1"))
|
||||
.put(new JSONObject().put("projectId", "thread-2"))
|
||||
.put(new JSONObject().put("projectId", "thread-3"));
|
||||
return new JSONObject()
|
||||
.put("threadMeta", threadMeta)
|
||||
.put("participants", participants);
|
||||
}
|
||||
|
||||
private static JSONObject buildConversationsPayload() throws Exception {
|
||||
JSONArray conversations = new JSONArray()
|
||||
.put(new JSONObject()
|
||||
.put("projectId", "thread-2")
|
||||
.put("projectTitle", "硬件审计协作")
|
||||
.put("folderLabel", "Mac Studio")
|
||||
.put("lastMessagePreview", "检查摄像头供电链路")
|
||||
.put("latestReplyLabel", "09:28")
|
||||
.put("isGroup", false))
|
||||
.put(new JSONObject()
|
||||
.put("projectId", "thread-3")
|
||||
.put("projectTitle", "Boss 移动控制台")
|
||||
.put("folderLabel", "Boss")
|
||||
.put("lastMessagePreview", "统一顶部按钮样式")
|
||||
.put("latestReplyLabel", "09:31")
|
||||
.put("isGroup", false));
|
||||
return new JSONObject().put("conversations", conversations);
|
||||
}
|
||||
|
||||
private static boolean viewTreeContainsText(View root, String expectedText) {
|
||||
if (root instanceof TextView) {
|
||||
CharSequence text = ((TextView) root).getText();
|
||||
if (expectedText.contentEquals(text)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!(root instanceof ViewGroup)) {
|
||||
return false;
|
||||
}
|
||||
ViewGroup group = (ViewGroup) root;
|
||||
for (int index = 0; index < group.getChildCount(); index += 1) {
|
||||
if (viewTreeContainsText(group.getChildAt(index), expectedText)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class TestGroupCreateActivity extends GroupCreateActivity {
|
||||
@Override
|
||||
protected void reload() {
|
||||
// Tests drive renderCreatePage manually to avoid network work.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@ public class ProjectChatUiStateTest {
|
||||
assertFalse(chromeState.multiSelecting);
|
||||
assertTrue(chromeState.showComposer);
|
||||
assertFalse(chromeState.showMultiSelectBar);
|
||||
assertTrue(chromeState.showRefresh);
|
||||
assertFalse(chromeState.showRefresh);
|
||||
assertTrue(chromeState.showHeaderAction);
|
||||
assertFalse(chromeState.forwardEnabled);
|
||||
assertEquals("返回", chromeState.backLabel);
|
||||
@@ -119,6 +119,22 @@ public class ProjectChatUiStateTest {
|
||||
assertEquals("归档确认", chromeState.subtitle);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chromeStateRestoresRefreshBeforeConversationInfoIsReady() {
|
||||
ProjectChatUiState.ChromeState chromeState =
|
||||
ProjectChatUiState.resolveChromeState(ProjectChatUiState.emptySelection(), false, "北区试产线回归", "归档确认");
|
||||
|
||||
assertFalse(chromeState.multiSelecting);
|
||||
assertTrue(chromeState.showComposer);
|
||||
assertFalse(chromeState.showMultiSelectBar);
|
||||
assertTrue(chromeState.showRefresh);
|
||||
assertFalse(chromeState.showHeaderAction);
|
||||
assertFalse(chromeState.forwardEnabled);
|
||||
assertEquals("返回", chromeState.backLabel);
|
||||
assertEquals("北区试产线回归", chromeState.title);
|
||||
assertEquals("归档确认", chromeState.subtitle);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reconcileSelectionDropsMessagesMissingFromRenderSet() {
|
||||
ProjectChatUiState.SelectionState state = ProjectChatUiState.toggleSelection(null, "m1");
|
||||
|
||||
@@ -46,7 +46,7 @@ public class ProjectDetailActivityChromeBindingsTest {
|
||||
assertFalse(bindings.multiSelecting);
|
||||
assertTrue(bindings.showComposer);
|
||||
assertFalse(bindings.showMultiSelectBar);
|
||||
assertTrue(bindings.showRefresh);
|
||||
assertFalse(bindings.showRefresh);
|
||||
assertTrue(bindings.showHeaderAction);
|
||||
assertFalse(bindings.enableForwardButton);
|
||||
assertTrue(bindings.enablePullRefresh);
|
||||
|
||||
@@ -75,7 +75,7 @@ public class ProjectDetailActivityUiTest {
|
||||
assertEquals(View.VISIBLE, composerRow.getVisibility());
|
||||
assertEquals(View.GONE, multiSelectActions.getVisibility());
|
||||
assertEquals("返回", backButton.getText().toString());
|
||||
assertEquals(View.VISIBLE, refreshButton.getVisibility());
|
||||
assertEquals(View.GONE, refreshButton.getVisibility());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -153,7 +153,7 @@
|
||||
- 邮件:`Postfix + Dovecot`
|
||||
- Android:`AppCompatActivity + 原生 XML 布局 + HttpURLConnection`
|
||||
- 原生登录恢复:`SharedPreferences + restore token`
|
||||
- 当前最新原生 APK:`2.5.0`(`versionCode=13`)
|
||||
- 当前最新原生 APK:`2.5.1`(`versionCode=14`)
|
||||
|
||||
当前不要误判成已经用了:
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ cd /Users/kris/code/boss
|
||||
- 当前已生成 Android debug APK:`android/app/build/outputs/apk/debug/app-debug.apk`
|
||||
- 当前已生成 Android signed release APK:`android/app/build/outputs/apk/release/app-release.apk`
|
||||
- 当前 release 构建还会额外生成带版本号的 APK:`android/app/build/outputs/apk/release/boss-android-v{versionName}-release.apk`
|
||||
- 当前最新 release 构建版本:`2.5.0`(`versionCode=13`)
|
||||
- 当前最新 release 构建版本:`2.5.1`(`versionCode=14`)
|
||||
- 当前 release keystore 位于本机 `android/keystores/boss-release.keystore`,签名参数位于 `android/signing/release-signing.properties`
|
||||
- `2.0.1` 已在本机连接的华为真机上复核通过,修复了 `Theme.SplashScreen` 导致的 `AppCompatActivity` 启动闪退
|
||||
- `2.1.0` 已把 Web 一级页和主要二级页全部补成原生活动页:`MainActivity / ProjectDetailActivity / ProjectGoalsActivity / ProjectVersionsActivity / ProjectForwardActivity / ThreadDetailActivity / DeviceDetailActivity / DeviceEnrollmentActivity / SkillInventoryActivity / SecurityActivity / SettingsActivity / AiAccountsActivity / OpsCenterActivity / AboutActivity`
|
||||
@@ -128,6 +128,8 @@ cd /Users/kris/code/boss
|
||||
- `2.4.0` 已把原生消息转发切到微信式链路:单条消息支持长按直接转发,多选消息支持合并转发成聊天记录卡片,统一使用原生会话选择页替换旧的备注转发页
|
||||
- `2.5.0` 已补齐聊天附件主链:原生聊天框左侧 `+` 已改成底部抽屉,支持图片 / 视频 / 文件发送;图片 / PDF / 文本会自动排队给主 Agent 分析,视频 / Office / 大文件改成手动触发
|
||||
- `2.5.0` 已上线 `我的 > 附件与存储`:默认使用服务器文件存储,用户可切到阿里 OSS 私有桶并填写最小配置;下载链会使用附件上传时固化的 OSS 快照,避免后续改配置后旧附件失效
|
||||
- `2.5.1` 已继续回退聊天详情页顶部交互:普通聊天态不再显示“刷新”,只保留微信式右上角“信息”入口;多选态的“取消 / 转发”保持不变
|
||||
- `2.5.1` 已压缩“发起群聊”页首信息密度:来源会话场景只保留一张紧凑摘要卡,选择区改成更短的微信式提示,同时保留会话卡片式候选列表
|
||||
- 当前附件分析任务已带受控 `task token` 下载链接和文本摘录:本地开发环境会跟随请求 origin 生成链接,生产环境默认走 `https://boss.hyzq.net`
|
||||
- `2.5.x` 当前已补上会话首页独立建群入口:可以不从单线程聊天内部出发,直接在会话首页右上角 `+` 建立新群聊;同时已把多个原生自定义 top bar 页面统一纳入状态栏安全区处理
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"artifactType": "aab",
|
||||
"fileName": "boss-android-v2.5.0-release.aab",
|
||||
"urlPath": "/downloads/boss-android-v2.5.0-release.aab",
|
||||
"sizeBytes": 2906325,
|
||||
"updatedAt": "2026-03-29T09:19:37Z",
|
||||
"sha256": "e230a1e0eae8e1e6d264f11feb3125ff40661dc2e049e18d8f683c3571e3a568",
|
||||
"versionName": "2.5.0",
|
||||
"versionCode": 13,
|
||||
"fileName": "boss-android-v2.5.1-release.aab",
|
||||
"urlPath": "/downloads/boss-android-v2.5.1-release.aab",
|
||||
"sizeBytes": 2910555,
|
||||
"updatedAt": "2026-03-29T11:58:09Z",
|
||||
"sha256": "a1db186f0bd8ac9439f9cd7b9116d273ddaa2d169eb46613a29b848b1824458b",
|
||||
"versionName": "2.5.1",
|
||||
"versionCode": 14,
|
||||
"buildFlavor": "release"
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fileName": "boss-android-v2.5.0-release.apk",
|
||||
"fileName": "boss-android-v2.5.1-release.apk",
|
||||
"urlPath": "/api/v1/user/ota/package",
|
||||
"sizeBytes": 3083087,
|
||||
"updatedAt": "2026-03-29T09:20:05Z",
|
||||
"sha256": "92183a0ebed80da5e363ffcd8e41a4acfd650aac76072ac5a4e432af5902f59f",
|
||||
"versionName": "2.5.0",
|
||||
"versionCode": 13,
|
||||
"sizeBytes": 3087319,
|
||||
"updatedAt": "2026-03-29T11:58:01Z",
|
||||
"sha256": "c97c17d2989066027a68f291c830d007696403c9f484fc4278310d8303cb8d99",
|
||||
"versionName": "2.5.1",
|
||||
"versionCode": 14,
|
||||
"buildFlavor": "release"
|
||||
}
|
||||
|
||||
BIN
public/downloads/boss-android-v2.5.1-release.aab
Normal file
BIN
public/downloads/boss-android-v2.5.1-release.aab
Normal file
Binary file not shown.
BIN
public/downloads/boss-android-v2.5.1-release.apk
Normal file
BIN
public/downloads/boss-android-v2.5.1-release.apk
Normal file
Binary file not shown.
Reference in New Issue
Block a user