Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/apps/mobile/harmonyos/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@

# Generated from shared desktop MiniApp sources by entry/hvigorfile.ts
/entry/src/main/resources/rawfile/miniapps/

# Generated from en_US/zh_CN element string catalogs by entry/hvigorfile.ts
/entry/src/main/resources/rawfile/string.json
10 changes: 10 additions & 0 deletions src/apps/mobile/harmonyos/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ source scripts/ohos-env.sh
"$HVIGORW" --mode module -p module=entry@default -p ohos.test.type=LocalTest test --no-daemon
```

For window inset subscription lifecycle and late completion regressions, run
`node --test tools/tests/window-insets-lifecycle.test.cjs tools/tests/immersive-bottom.test.cjs`.
The lifecycle tests run the production service with deferred platform window
reads; device rotation and folding still require native verification.

For language-refresh and literal-parameter regressions, run
`node --test tools/tests/host-text-retranslator.test.cjs`. This exercises the
production retranslator and RemoteI18n with real element catalogs; platform
resource access is mocked and does not replace native resource/device checks.

For workspace/session catalog rendering, install the debug HAP and run
`python3 tools/check-catalog-refresh.py --hdc "$HDC"` (add `--dark` for dark
mode). Run in compact and wide postures. The isolated fixture replaces objects
Expand Down
2 changes: 1 addition & 1 deletion src/apps/mobile/harmonyos/build-profile.json5
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{
"name": "default",
"signingConfig": "default",
"targetSdkVersion": "6.1.1(24)",
"targetSdkVersion": "26.0.0",
"compatibleSdkVersion": "6.0.1(21)",
"runtimeOS": "HarmonyOS",
"buildOption": {
Expand Down
2 changes: 2 additions & 0 deletions src/apps/mobile/harmonyos/entry/hvigorfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { hapTasks } from '@ohos/hvigor-ohos-plugin';

// Include the current shared sources on every IDE and CLI build.
require('../miniapps/generate.cjs').generate();
// Regenerate the HostTextRetranslator i18n rawfile catalog on every build.
require('../i18n/generate.cjs').generate();

export default {
system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,23 @@ export default class EntryAbility extends UIAbility {

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
const scenarioId = want.parameters?.['openbitfunDesignPreview'];
if (scenarioId === 'history-scroll' || scenarioId === 'durable-timeline' || scenarioId === 'composer-submit' || scenarioId === 'catalog-refresh' || scenarioId === 'catalog-refresh-dark' || scenarioId === 'model-role-selector' || scenarioId === 'model-role-selector-dark' || scenarioId === 'model-role-selector-wide' || scenarioId === 'subagent-process' || scenarioId === 'subagent-process-dark' || scenarioId === 'file-visibility' || scenarioId === 'file-visibility-dark' || scenarioId === 'immersive-bottom' || scenarioId === 'immersive-bottom-dark' || scenarioId === 'immersive-bottom-wide' || scenarioId === 'immersive-bottom-drawer' || scenarioId === 'immersive-bottom-settings' || scenarioId === 'immersive-bottom-settings-dark' || scenarioId === 'immersive-bottom-cover' || scenarioId === 'immersive-bottom-miniapps' || scenarioId === 'immersive-bottom-wide-shell' || scenarioId === 'immersive-bottom-tools' || scenarioId === 'immersive-bottom-tools-editor' || scenarioId === 'immersive-bottom-tools-file-action' || scenarioId === 'immersive-bottom-view-settings' || scenarioId === 'immersive-bottom-connect' || scenarioId === 'composer-overflow' || scenarioId === 'composer-overflow-empty' || scenarioId === 'composer-overflow-dark' || scenarioId === 'composer-overflow-wide' || scenarioId === 'composer-overflow-expanded' || scenarioId === 'composer-overflow-expanded-dark' || scenarioId === 'device-selector' || scenarioId === 'device-selector-dark' || scenarioId === 'welcome-home' || scenarioId === 'connected-conversation' ||
if (scenarioId === 'history-scroll' || scenarioId === 'durable-timeline' || scenarioId === 'composer-submit' || scenarioId === 'catalog-refresh' || scenarioId === 'catalog-refresh-dark' || scenarioId === 'model-role-selector' || scenarioId === 'model-role-selector-dark' || scenarioId === 'model-role-selector-wide' || scenarioId === 'subagent-process' || scenarioId === 'subagent-process-dark' || scenarioId === 'file-visibility' || scenarioId === 'file-visibility-dark' || scenarioId === 'immersive-bottom' || scenarioId === 'immersive-bottom-dark' || scenarioId === 'immersive-bottom-wide' || scenarioId === 'immersive-bottom-drawer' || scenarioId === 'immersive-bottom-drawer-dark' || scenarioId === 'immersive-bottom-settings' || scenarioId === 'immersive-bottom-settings-dark' || scenarioId === 'immersive-bottom-cover' || scenarioId === 'immersive-bottom-miniapps' || scenarioId === 'immersive-bottom-wide-shell' || scenarioId === 'immersive-bottom-tools' || scenarioId === 'immersive-bottom-tools-editor' || scenarioId === 'immersive-bottom-tools-file-action' || scenarioId === 'immersive-bottom-view-settings' || scenarioId === 'immersive-bottom-connect' || scenarioId === 'composer-overflow' || scenarioId === 'composer-overflow-empty' || scenarioId === 'composer-overflow-dark' || scenarioId === 'composer-overflow-wide' || scenarioId === 'composer-overflow-expanded' || scenarioId === 'composer-overflow-expanded-dark' || scenarioId === 'device-selector' || scenarioId === 'device-selector-dark' || scenarioId === 'welcome-home' || scenarioId === 'connected-conversation' ||
scenarioId === 'streaming-dark' ||
scenarioId === 'reconnecting-wide' || scenarioId === 'narrow-multiline' ||
scenarioId === 'fold-context' || scenarioId === 'long-reading' ||
scenarioId === 'cold-start-home' || scenarioId === 'cold-start-home-dark' ||
scenarioId === 'copy-selection' || scenarioId === 'copy-selection-dark' ||
scenarioId === 'copy-selection-nav' || scenarioId === 'copy-selection-wide' ||
scenarioId === 'interaction-mailbox' || scenarioId === 'interaction-mailbox-dark') {
this.initialPage = 'pages/preview/MobileDesignGallery';
this.previewStorage = new LocalStorage();
this.previewStorage.setOrCreate('scenarioId', scenarioId);
AppStorage.setOrCreate('scenarioId', scenarioId);
}
try {
const colorMode = scenarioId === 'cold-start-home-dark' || scenarioId === 'catalog-refresh-dark' || scenarioId === 'model-role-selector-dark' || scenarioId === 'subagent-process-dark' || scenarioId === 'file-visibility-dark' || scenarioId === 'immersive-bottom-dark' || scenarioId === 'immersive-bottom-settings-dark' || scenarioId === 'composer-overflow-dark' || scenarioId === 'composer-overflow-expanded-dark' || scenarioId === 'device-selector-dark' || scenarioId === 'streaming-dark' || scenarioId === 'interaction-mailbox-dark'
const colorMode = scenarioId === 'cold-start-home-dark' || scenarioId === 'catalog-refresh-dark' || scenarioId === 'model-role-selector-dark' || scenarioId === 'subagent-process-dark' || scenarioId === 'file-visibility-dark' || scenarioId === 'immersive-bottom-dark' || scenarioId === 'immersive-bottom-drawer-dark' || scenarioId === 'immersive-bottom-settings-dark' || scenarioId === 'composer-overflow-dark' || scenarioId === 'composer-overflow-expanded-dark' || scenarioId === 'device-selector-dark' || scenarioId === 'streaming-dark' || scenarioId === 'interaction-mailbox-dark' || scenarioId === 'copy-selection-dark'
? ConfigurationConstant.ColorMode.COLOR_MODE_DARK
: scenarioId === 'cold-start-home' || scenarioId === 'catalog-refresh' || scenarioId === 'model-role-selector' || scenarioId === 'model-role-selector-wide' || scenarioId === 'subagent-process' || scenarioId === 'file-visibility' || scenarioId === 'immersive-bottom' || scenarioId === 'immersive-bottom-wide' || scenarioId === 'immersive-bottom-drawer' || scenarioId === 'immersive-bottom-settings' || scenarioId === 'immersive-bottom-cover' || scenarioId === 'immersive-bottom-miniapps' || scenarioId === 'immersive-bottom-wide-shell' || scenarioId === 'immersive-bottom-tools' || scenarioId === 'immersive-bottom-tools-editor' || scenarioId === 'immersive-bottom-tools-file-action' || scenarioId === 'immersive-bottom-view-settings' || scenarioId === 'immersive-bottom-connect' || scenarioId === 'composer-overflow' || scenarioId === 'composer-overflow-empty' || scenarioId === 'composer-overflow-wide' || scenarioId === 'composer-overflow-expanded' || scenarioId === 'device-selector' || scenarioId === 'connected-conversation' || scenarioId === 'reconnecting-wide'
: scenarioId === 'cold-start-home' || scenarioId === 'catalog-refresh' || scenarioId === 'model-role-selector' || scenarioId === 'model-role-selector-wide' || scenarioId === 'subagent-process' || scenarioId === 'file-visibility' || scenarioId === 'immersive-bottom' || scenarioId === 'immersive-bottom-wide' || scenarioId === 'immersive-bottom-drawer' || scenarioId === 'immersive-bottom-settings' || scenarioId === 'immersive-bottom-cover' || scenarioId === 'immersive-bottom-miniapps' || scenarioId === 'immersive-bottom-wide-shell' || scenarioId === 'immersive-bottom-tools' || scenarioId === 'immersive-bottom-tools-editor' || scenarioId === 'immersive-bottom-tools-file-action' || scenarioId === 'immersive-bottom-view-settings' || scenarioId === 'immersive-bottom-connect' || scenarioId === 'composer-overflow' || scenarioId === 'composer-overflow-empty' || scenarioId === 'composer-overflow-wide' || scenarioId === 'composer-overflow-expanded' || scenarioId === 'device-selector' || scenarioId === 'connected-conversation' || scenarioId === 'reconnecting-wide' || scenarioId === 'copy-selection' || scenarioId === 'copy-selection-nav' || scenarioId === 'copy-selection-wide'
? ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT
: ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET;
this.context.getApplicationContext().setColorMode(colorMode);
Expand Down Expand Up @@ -109,7 +111,10 @@ export default class EntryAbility extends UIAbility {
/**
* The immersive shell is not verifiable from a screenshot alone: whether the
* window covers the status bar and how tall the bottom strip is are device
* facts, so state them once per launch.
* facts, so state them once per launch. The camera cutout is stated in full —
* visibility and all four rectangles — because it is the one avoid area that
* moves to a different edge when the device rotates, and a device without a
* cutout reports zeroes here rather than staying silent about it.
*/
private logWindowInsets(mainWindow: window.Window): void {
try {
Expand All @@ -119,11 +124,37 @@ export default class EntryAbility extends UIAbility {
'Window insets statusBar=%{public}s navigation=%{public}s windowRect=%{public}s',
String(system.topRect.height), String(navigation.bottomRect.height),
JSON.stringify(mainWindow.getWindowProperties().windowRect));
const cutout = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_CUTOUT);
hilog.info(DOMAIN, TAG,
'Window cutout visible=%{public}s top=%{public}s bottom=%{public}s left=%{public}s right=%{public}s',
String(cutout.visible), String(cutout.topRect.height), String(cutout.bottomRect.height),
String(cutout.leftRect.width), String(cutout.rightRect.width));
} catch (err) {
hilog.error(DOMAIN, TAG, 'Failed to read window insets. Cause: %{public}s', JSON.stringify(err));
}
}

/**
* The content colour of both system bars, for the whole window and the whole
* app.
*
* This is one value for one window, and it is derived from one thing only: the
* colour mode. There is no per-page override here, so any page whose top band
* is dark — a media viewer, an immersive title, a hero the design decides to
* paint dark — puts the light-mode ink on a dark band and the clock and the
* status icons stop being readable against it. Every top band the app ships
* today is a theme-following page colour (`PAGE_BG`, `SIDEBAR_BG`,
* `PAGE_BG_OVERLAY`, `WELCOME_PAGE_BG`, `CARD`), which is why the single value
* is correct as it stands; `immersive-bottom.test.cjs` guards that list as a
* tripwire.
*
* So: a new page that paints its own dark top band is not finished here. It has
* to come back to this method — either by declaring its band against the
* colour mode the way the others do, or by giving the app a per-page override
* (the platform's `HdsNavigation.systemBarStyle` is the page-level route, and
* the platform also warns against mixing it with the window-level calls used
* here). Either way the band and the content colour are one decision, not two.
*/
private updateSystemBars(colorMode?: ConfigurationConstant.ColorMode): void {
if (!this.mainWindow) {
return;
Expand All @@ -132,16 +163,14 @@ export default class EntryAbility extends UIAbility {
const content = dark ? MobileDesignColors.ink.dark : MobileDesignColors.ink.light;
try {
this.mainWindow.setWindowSystemBarProperties({
// Both bars are transparent, because the window is laid out full-screen
// and the strips they occupy are the page's own edges: the drawer's fill
// and the chat page's blurred header band are what should show there.
// An opaque status bar painted the page colour over both, which clipped
// the sidebar's title, and it also sat over the pull-down notification
// centre's frosted background as a white strip instead of blending into
// it. `statusBarContentColor` is what keeps the clock and the icons
// legible against whatever the page puts behind them.
statusBarColor: MobileDesignColors.transparent.light,
navigationBarColor: MobileDesignColors.transparent.light,
// The window is laid out full-screen above (`setWindowLayoutFullScreen(true)`),
// so it already runs in the immersive layout, and the status bar and the
// navigation area are transparent by platform default: both strips carry
// the page's own background through. The page therefore owns what shows
// there — the drawer's fill and the chat page's blurred header band — and
// any background colour set here would repaint the page's edge instead.
// The content colours are the only thing left to state: they keep the
// clock and the icons legible against whatever the page puts behind them.
statusBarContentColor: content,
navigationBarContentColor: content
});
Expand Down
Loading
Loading