Skip to content

Commit 2ef884e

Browse files
authored
Merge pull request #2376 from wgqqqqq/feat/harmonyos-app-i18n
feat(harmonyos): place overlay sheets from fold geometry
2 parents bae0deb + 265c54c commit 2ef884e

34 files changed

Lines changed: 2508 additions & 550 deletions

‎src/apps/mobile/harmonyos/docs/adaptive-sheet-placement-design.md‎

Lines changed: 366 additions & 0 deletions
Large diffs are not rendered by default.

‎src/apps/mobile/harmonyos/docs/mobile-control-console-design.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ presence(见「4. 拉取策略」后两条),以及 `ForEach` 键撞车导
6060
## 目标
6161

6262
- 侧栏工作区分区呈现账号下**所有**桌面设备的工作区与会话,层级为「设备 > 工作区 > 会话」。
63+
- 展开一层只预览 3 条:点开设备不列出全量工作区,点开工作区不列出全量会话;「还有 N 个」每次再展开 3 条,不一次铺开。
6364
- 浏览另一台设备的内容不需要任何显式的"切换控制设备"动作。
6465
- 浏览和创建都不改变被观察那台桌面自己正在打开的工作区。
6566
- 账号桌面无论几台都画设备头,用 `listDevices` 的真名;没有账号设备列表的二维码配对才摊平。
@@ -198,6 +199,10 @@ flowchart LR
198199
- 其他在线设备默认折叠。首次展开触发 `list_recent_workspaces` + `list_assistants`,再对该
199200
设备的工作区路径批量 `list_sessions`。磁盘回填是 `cached`:先画出来,展开后仍要被 RPC
200201
覆盖,不能把缓存当成已经加载完。
202+
- 数据可以一次拉全,画面不能一次铺开。点开设备只预览 3 个工作区(工作区行默认收起),
203+
点开工作区只预览 3 条会话;「还有 N 个」每次再展开 3 条。没有设备头的摊平行保持工作区默认展开。
204+
磁盘缓存仍存该设备的完整工作区/会话快照,不按预览窗口裁切;「还有 N 个」和继续展开
205+
都靠这份全量,预览游标只活在组件 `@Local` 里,冷启动回到第一批 3 条。
201206
- 批量并发复用 `RemoteWorkspaceCoordinator` 已有的形状(一次 4 个,按给定顺序合并,因此
202207
调宽批量只改耗时不改结果)。把那段循环提成共享 helper 由两边调用,而不是复制一份。
203208
- 容错分两级,与既有实现一致:一个工作区读不出来只丢那一个工作区(既有注释:一个读不了的
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# 普通对话模型设置:bindSheet 与模型选择
2+
3+
Date: 2026-08-19
4+
5+
Scope: `src/apps/mobile/harmonyos` 设置里的「普通对话 / 模型」面板,以及同一套手搓蒙层的语言面板。不改 ComposerBar 选择器、relay、桌面端。
6+
7+
## 问题
8+
9+
`ModelServiceSettingsPanel` 不是系统 Sheet,而是塞进 `SettingsSheet` Stack 的手搓蒙层(`78%` 死高度 + 自绘圆角 + 自绘 ×)。设置页那个 × 是同一个 Stack 的 overlay,所以两个 × 同时出现;内容只有三行,底下是大片空白。
10+
11+
`checkmark_circle_fill` 在「当前使用 / 账号同步 / 本机自定义」上各出现一次。后两个表达的是来源归属,和「当前使用」那一行重复。
12+
13+
账号同步行没有 `onClick`。用户看见「已同步 12 个」却进不去、也换不了模型。真正的切换只在 ComposerBar 的 `bindSheet` 里。语言面板是同一套手搓蒙层,同样会和设置页 × 叠在一起。
14+
15+
## 目标
16+
17+
1. 设置子面板收敛到和 `ComposerBar` 一样的 `bindSheet`(`showClose: false`、`dragBar: true`、高度按内容算)。一次去掉双 ×、死高度空白、手搓蒙层。
18+
2. `✓` 只表示「当前选中的模型」。来源行不再带 ✓。
19+
3. 账号同步行可点,带 `>`,打开真实的账号模型列表,点一项即 `SettingsController.selectModel`。
20+
4. 信息架构收成两组:「当前使用」+「模型来源」。账号同步和本机自定义是同级入口,放在同一分组容器里,中间一条 divider;分组标题和行文字共用 16vp 水平边距。
21+
22+
语言面板做同一套 `bindSheet` 收敛,不再单独留一手搓模态。
23+
24+
## 非目标
25+
26+
- 不改 ComposerBar 里那套对话中模型选择器。
27+
- 不改账号模型的同步/拉取合同。
28+
- 不把设置面板做成 ComposerBar 选择器的第二份拷贝;设置侧分「状态 / 来源 / 本机配置」三层,选择器只负责挑一个 id。
29+
30+
## 交互
31+
32+
设置页「模型」行 → `bindSheet` 打开概览。
33+
34+
```
35+
[普通对话模型 ×]
36+
37+
当前使用
38+
[ ✓ deepseek-v4-pro ]
39+
[ 云端账号 ]
40+
41+
模型来源
42+
[ ☁ 云端账号模型 > ]
43+
[ 已同步 12 个 ]
44+
[ ------------------------------- ]
45+
[ 🔧 本机模型名 > ]
46+
[ 本机 ]
47+
```
48+
49+
- 账号行 → 再挂一层 `bindSheet`,列表高度按 `min(条数, 7)` 算,和 ComposerBar 同一公式。点一项调用 `selectModel`,关掉列表,概览上的「当前使用」跟着变。0 条也进得去,显示空态。
50+
- 本机行 `>` → 再挂一层 `bindSheet` 打开本机 API 编辑器(自适应/固定表单高度,保留键盘避让)。已配置时,点行主体会选中本机模型;未配置时点整行进编辑器。保存成功后沿用现有 `save` 路径。
51+
- 概览「当前使用」只读。`✓` 只出现在这里,以及账号列表里当前那一项。
52+
53+
设置页「语言」行同样改 `bindSheet`,高度按语言条数算。
54+
55+
## 结构
56+
57+
| 单元 | 职责 |
58+
|---|---|
59+
| `ModelServiceSettingsPolicy` | 当前模型解析、账号模型过滤、来源分组、三页 Sheet 高度。组件不内嵌这些规则。 |
60+
| `SettingsSheet` | 在根节点上 `bindSheet` 语言 / 概览 / 账号列表 / 本机编辑器。不再把子面板叠进 Stack。 |
61+
| `ModelServiceSettingsPanel` | 只画当前页:`overview` / `account` / `local`。发出 `onOpenAccountModels` / `onOpenLocalEditor` / `onSelectModel`。 |
62+
| `LanguageSettingsPanel` | 只画语言列表,不再自绘蒙层。 |
63+
| `SettingsPresentationActions.selectGeneralModel` | 接到 `SettingsController.selectModel`。 |
64+
65+
设置页本身已经是 `AppShell` 上的 `bindSheet`。子面板再 `bindSheet` 一次,真机上点击会改状态但第二张 Sheet 不会出现(`5ZU0226202001116` 上点「模型」后仍只有一张 `SheetPage`)。因此语言 / 概览 / 账号列表 / 本机编辑器改为**替换设置列表**,不嵌套系统 Sheet。设置页的 × 只在主列表显示,避免双 ×。
66+
67+
## 高度
68+
69+
公式放在 policy 里,组件只读数字:
70+
71+
- 概览:header + 「当前使用」一行 + 「模型来源」两行分组,约 345vp。
72+
- 账号列表:`min(480, chrome + min(n, 7) * row + 行距)`,空态用一行占位高。
73+
- 本机编辑器:560vp,够放下三字段、测试/保存和反馈;聚焦时仍走现有 `KeyboardAvoidMode.RESIZE`。
74+
- 语言:`chrome + 条数 * 64`。
75+
76+
三页各绑一张 Sheet,打开时按当时内容算高度,避免同一张 Sheet 切页后高度不刷新。
77+
78+
## 测试
79+
80+
`ModelServiceSettingsPolicy` 覆盖:当前模型解析顺序、只把 `cloud:` 且 `enabled` 的项算进账号列表、来源行不承担选中语义、三页高度随内容变且账号列表封顶 7 行。`ArchitectureUnit` 不应被这次改动破坏。i18n 中英 key 锁步。
81+
82+
## 远程场景
83+
84+
这次只动本机设置壳。账号模型数据仍来自已有云端同步;选择写进 `GeneralChatConfigStore`,和 ComposerBar 走同一条 `selectModel`。不新增远程命令,也不把本机 API Key 泄漏到远端控制器。

‎src/apps/mobile/harmonyos/docs/wide-conversation-navigation-design.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ MasterDetail -> 双屏和三屏共同使用的 master-detail
264264

265265
1. 双屏优先让 master/detail 分界与第一道折痕对齐。
266266
2. 三屏同样让 master pane 占据第一屏,并与第一道折痕对齐。
267-
3. 必须为折痕自身保留安全间距,避免列表行、来源选择器或点击热区跨越折痕。
267+
3. 必须为折痕自身保留安全间距,避免列表行、来源选择器或点击热区跨越折痕。该占位使用页面底色,不把折痕宽度画成分隔条。
268268
4. 折痕坐标为像素,ArkUI 布局宽度使用布局单位;进入布局策略前必须转换到同一坐标系。
269269

270270
折痕信息不可用、坐标不在当前应用内容区域内或转换失败时,继续使用当前固定 master 宽度和弹性 detail,不得阻断页面渲染。
@@ -276,7 +276,7 @@ MasterDetail -> 双屏和三屏共同使用的 master-detail
276276
- 页面头部和背景可以占满整个 detail pane。
277277
- 消息阅读区、状态内容和 Composer 应设置合理最大宽度,并在 detail pane 内稳定对齐。
278278
- 存在第二道折痕时,会话详情仍占据第一道折痕之后的整段剩余宽度(中间+右侧),不得只留在最宽的那一屏。
279-
- 收起 master 后:双折一道折痕且两侧接近对半时,内容可以铺满整窗;三折时内容必须留在第一道折痕之后的整段剩余宽度,不能整屏重新居中,也不能被系统分栏裁到左半屏。恢复按钮钉在空出来的第一屏左上角。
279+
- 收起 master 后:双折和三折都把会话铺满整窗,恢复按钮钉在窗口左上角。展开态三折 detail 仍从第一道折痕铺到右缘;只有收起左栏时才占用第一屏。
280280
- 最大宽度不能按视口宽度缩放字体,只限制内容容器。
281281
- 第二道折痕位于 detail 内部时,不因此再切出第三会话栏;关键点击控件随内容带一起落在剩余两屏内。
282282
- 第一阶段不利用第二道折痕拆出文件、任务或其他辅助面板。

‎src/apps/mobile/harmonyos/entry/src/main/ets/i18n/EnUsMessages.ets‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,8 @@ export const EN_US_MESSAGES: [string, string][] = [
5858
['generalChat.emptyResponse', 'The model returned no text.'],
5959
['generalChat.requestFailed', 'Model request failed (HTTP {0}).'],
6060

61-
['settings.modelService.section', 'General chat'],
61+
['settings.general.section', 'General'],
6262
['settings.title', 'Settings'],
63-
['settings.language.section', 'Language'],
6463
['settings.language.title', 'Language'],
6564
['settings.language.choose', 'Choose language'],
6665
['settings.language.zhCN', '简体中文'],
@@ -72,6 +71,8 @@ export const EN_US_MESSAGES: [string, string][] = [
7271
['settings.modelService.manageTitle', 'General chat model'],
7372
['settings.modelService.localTitle', 'Custom local model'],
7473
['settings.modelService.currentModel', 'In use'],
74+
['settings.modelService.modelSources', 'Model sources'],
75+
['settings.modelService.chooseAccountModel', 'Choose account model'],
7576
['settings.modelService.accountModels', 'Account sync'],
7677
['settings.modelService.accountModelSummary', 'Cloud account models'],
7778
['settings.modelService.localModel', 'Custom local'],

‎src/apps/mobile/harmonyos/entry/src/main/ets/i18n/ZhCnMessages.ets‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,8 @@ export const ZH_CN_MESSAGES: [string, string][] = [
5858
['generalChat.emptyResponse', '模型没有返回文字内容。'],
5959
['generalChat.requestFailed', '模型请求失败(HTTP {0})。'],
6060

61-
['settings.modelService.section', '普通对话'],
61+
['settings.general.section', '通用'],
6262
['settings.title', '设置'],
63-
['settings.language.section', '语言'],
6463
['settings.language.title', '语言'],
6564
['settings.language.choose', '选择语言'],
6665
['settings.language.zhCN', '简体中文'],
@@ -72,6 +71,8 @@ export const ZH_CN_MESSAGES: [string, string][] = [
7271
['settings.modelService.manageTitle', '普通对话模型'],
7372
['settings.modelService.localTitle', '本机自定义模型'],
7473
['settings.modelService.currentModel', '当前使用'],
74+
['settings.modelService.modelSources', '模型来源'],
75+
['settings.modelService.chooseAccountModel', '选择账号模型'],
7576
['settings.modelService.accountModels', '账号同步'],
7677
['settings.modelService.accountModelSummary', '云端账号模型'],
7778
['settings.modelService.localModel', '本机自定义'],

‎src/apps/mobile/harmonyos/entry/src/main/ets/pages/actions/AppRootPresentationActions.ets‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export interface SettingsPresentationActions {
100100
readonly setPermissionMode: (mode: RemotePermissionMode) => Promise<RemotePermissionMode>;
101101
readonly testGeneral: (url: string, key: string, model: string, clear: boolean) => Promise<string>;
102102
readonly saveGeneral: (url: string, key: string, model: string, clear: boolean) => Promise<string>;
103+
readonly selectGeneralModel: (modelId: string) => Promise<boolean>;
103104
readonly setLanguage: (language: string) => Promise<void>;
104105
}
105106

@@ -149,6 +150,7 @@ export function emptyAppRootPresentationActions(): AppRootPresentationActions {
149150
getPermissionMode: async () => 'ask',
150151
setPermissionMode: async (mode: RemotePermissionMode) => mode,
151152
testGeneral: async () => '', saveGeneral: async () => '',
153+
selectGeneralModel: async () => false,
152154
setLanguage: async () => {}
153155
},
154156
onConnect: {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {
2+
SettingsPlacement,
3+
SettingsPlacementMode
4+
} from '../policy/SettingsPlacementPolicy';
5+
6+
/**
7+
* Shared SheetOptions for the four overlay sheets. Policy stays ArkUI-free;
8+
* this is the only place preferType is chosen.
9+
*/
10+
export class AdaptiveSheetOptions {
11+
static fromPlacement(placement: SettingsPlacement, dragBar: boolean = false): SheetOptions {
12+
if (placement.mode === SettingsPlacementMode.FoldOperate) {
13+
return {
14+
height: placement.height,
15+
backgroundColor: '#00000000',
16+
maskColor: '#44000000',
17+
showClose: false,
18+
dragBar
19+
};
20+
}
21+
if (placement.mode === SettingsPlacementMode.Side) {
22+
return {
23+
height: placement.height,
24+
width: placement.width,
25+
preferType: SheetType.SIDE,
26+
backgroundColor: '#00000000',
27+
maskColor: '#44000000',
28+
showClose: false,
29+
dragBar: false
30+
};
31+
}
32+
if (placement.height > 0) {
33+
return {
34+
height: placement.height,
35+
backgroundColor: '#00000000',
36+
maskColor: '#44000000',
37+
showClose: false,
38+
dragBar
39+
};
40+
}
41+
return {
42+
height: SheetSize.LARGE,
43+
backgroundColor: '#00000000',
44+
maskColor: '#44000000',
45+
showClose: false,
46+
dragBar
47+
};
48+
}
49+
}

‎src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/AppRootOverlaySurfaces.ets‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import {
44
} from '../actions/AppRootPresentationActions';
55
import { AppRouteContract } from '../navigation/AppRouteContract';
66
import { AppShellState, CONNECT_INTENT_AUTO } from '../state/AppShellState';
7+
import {
8+
SettingsPlacement,
9+
SettingsPlacementPolicy,
10+
SettingsSheetKind
11+
} from '../policy/SettingsPlacementPolicy';
712
import { GeneralChatPageState } from '../state/GeneralChatPageState';
813
import { DeviceDirectoryState } from '../state/DeviceDirectoryState';
914
import { RemotePageState } from '../state/RemotePageState';
@@ -17,6 +22,8 @@ import { SidebarWorkspaceSection } from './SidebarWorkspaceSection';
1722
@ComponentV2
1823
export struct AppSidebarSurface {
1924
@Param shellState: AppShellState = new AppShellState();
25+
@Param sessionDetailsPlacement: SettingsPlacement =
26+
SettingsPlacementPolicy.compactBottom(SettingsSheetKind.SessionDetails);
2027
@Param remotePageState: RemotePageState = new RemotePageState();
2128
@Param deviceDirectoryState: DeviceDirectoryState = new DeviceDirectoryState();
2229
@Param generalPageState: GeneralChatPageState = new GeneralChatPageState();
@@ -27,6 +34,7 @@ export struct AppSidebarSurface {
2734
build() {
2835
AppSidebar({
2936
sessions: this.generalPageState.recentSessions(),
37+
sessionDetailsPlacement: this.sessionDetailsPlacement,
3038
pinnedSessionId: this.generalPageState.pinnedSessionId(),
3139
selectedSessionId: AppRouteContract.isGeneralComposerRoute(this.shellState.activeRoute) ?
3240
this.generalPageState.conversation.activeSession.sessionId : '',
@@ -134,6 +142,7 @@ export struct AppSettingsSurface {
134142
})
135143
} else {
136144
SettingsSheet({
145+
sheetState: this.shellState.settingsSheet,
137146
generalChatApiUrl: this.generalPageState.apiUrl,
138147
generalChatModelName: this.generalPageState.modelName,
139148
hasGeneralChatApiKey: this.generalPageState.hasApiKey,
@@ -145,6 +154,7 @@ export struct AppSettingsSurface {
145154
onOpenAccount: this.actions.onSettings.openAccount,
146155
onTestGeneralChatConfig: this.actions.onSettings.testGeneral,
147156
onSaveGeneralChatConfig: this.actions.onSettings.saveGeneral,
157+
onSelectGeneralChatModel: this.actions.onSettings.selectGeneralModel,
148158
onSetLanguage: this.actions.onSettings.setLanguage,
149159
onClose: this.actions.onSettings.close
150160
})

0 commit comments

Comments
 (0)