From 60e8fb21aae569557a3bb19392b8d89fc35231d9 Mon Sep 17 00:00:00 2001 From: FredTT Date: Wed, 25 Jun 2025 10:55:07 +0800 Subject: [PATCH] add Overlay sample Signed-off-by: FredTT --- examples/Overlay/.gitignore | 12 + examples/Overlay/AppScope/app.json5 | 25 + .../resources/base/element/string.json | 8 + .../resources/base/media/app_icon.png | Bin 0 -> 20093 bytes examples/Overlay/build-profile.json5 | 55 + examples/Overlay/code-linter.json5 | 35 + examples/Overlay/entry/.gitignore | 6 + examples/Overlay/entry/build-profile.json5 | 43 + examples/Overlay/entry/hvigorfile.ts | 21 + examples/Overlay/entry/obfuscation-rules.txt | 23 + examples/Overlay/entry/oh-package.json5 | 25 + .../main/ets/entryability/EntryAbility.ets | 56 + .../ets/extensionAbility/extensionAbility.ets | 45 + .../main/ets/extensionAbility/pages/Index.ets | 42 + .../entry/src/main/ets/pages/Index.ets | 39 + .../ets/pages/components/base/ParamOption.ets | 44 + .../ets/pages/components/dialog/Index.ets | 56 + .../pages/components/dialog/actionSheet.ets | 129 +++ .../pages/components/dialog/alertDialog.ets | 124 +++ .../pages/components/dialog/customDialog.ets | 135 +++ .../components/dialog/dialogController.ets | 382 +++++++ .../components/dialog/dialogFocusable.ets | 576 +++++++++++ .../components/dialog/dialogLevelOrder.ets | 961 ++++++++++++++++++ .../dialog/embeddedDialogPageOne.ets | 74 ++ .../dialog/embeddedDialogPageTwo.ets | 36 + .../pages/components/dialog/promptAction.ets | 71 ++ .../ets/pages/components/overlay/Index.ets | 50 + .../components/overlay/overlayLevelOrder.ets | 123 +++ .../overlay/overlayManagerHitTest.ets | 81 ++ .../main/ets/pages/components/toast/Index.ets | 51 + .../pages/components/toast/showToastTest.ets | 74 ++ .../main/ets/pages/components/toast/toast.ets | 63 ++ .../components/toast/toastPositionTest.ets | 204 ++++ .../src/main/ets/pages/navigationPage.ets | 53 + .../main/ets/pages/utils/navigationList.ets | 39 + examples/Overlay/entry/src/main/module.json5 | 64 ++ .../main/resources/base/element/color.json | 8 + .../main/resources/base/element/string.json | 16 + .../resources/base/profile/main_pages.json | 7 + .../resources/base/profile/route_map.json | 89 ++ .../main/resources/en_US/element/string.json | 16 + .../main/resources/zh_CN/element/string.json | 16 + .../Overlay/entry/src/mock/mock-config.json5 | 17 + .../src/ohosTest/ets/test/Ability.test.ets | 50 + .../entry/src/ohosTest/ets/test/List.test.ets | 20 + .../Overlay/entry/src/ohosTest/module.json5 | 28 + examples/Overlay/entry/src/test/List.test.ets | 20 + .../Overlay/entry/src/test/LocalUnit.test.ets | 48 + examples/Overlay/hvigor/hvigor-config.json5 | 37 + examples/Overlay/hvigorfile.ts | 21 + examples/Overlay/oh-package-lock.json5 | 42 + examples/Overlay/oh-package.json5 | 25 + 52 files changed, 4285 insertions(+) create mode 100644 examples/Overlay/.gitignore create mode 100644 examples/Overlay/AppScope/app.json5 create mode 100644 examples/Overlay/AppScope/resources/base/element/string.json create mode 100644 examples/Overlay/AppScope/resources/base/media/app_icon.png create mode 100644 examples/Overlay/build-profile.json5 create mode 100644 examples/Overlay/code-linter.json5 create mode 100644 examples/Overlay/entry/.gitignore create mode 100644 examples/Overlay/entry/build-profile.json5 create mode 100644 examples/Overlay/entry/hvigorfile.ts create mode 100644 examples/Overlay/entry/obfuscation-rules.txt create mode 100644 examples/Overlay/entry/oh-package.json5 create mode 100644 examples/Overlay/entry/src/main/ets/entryability/EntryAbility.ets create mode 100644 examples/Overlay/entry/src/main/ets/extensionAbility/extensionAbility.ets create mode 100644 examples/Overlay/entry/src/main/ets/extensionAbility/pages/Index.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/Index.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/base/ParamOption.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/dialog/Index.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/dialog/actionSheet.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/dialog/alertDialog.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/dialog/customDialog.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/dialog/dialogController.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/dialog/dialogFocusable.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/dialog/dialogLevelOrder.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/dialog/embeddedDialogPageOne.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/dialog/embeddedDialogPageTwo.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/dialog/promptAction.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/overlay/Index.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/overlay/overlayLevelOrder.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/overlay/overlayManagerHitTest.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/toast/Index.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/toast/showToastTest.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/toast/toast.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/components/toast/toastPositionTest.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/navigationPage.ets create mode 100644 examples/Overlay/entry/src/main/ets/pages/utils/navigationList.ets create mode 100644 examples/Overlay/entry/src/main/module.json5 create mode 100644 examples/Overlay/entry/src/main/resources/base/element/color.json create mode 100644 examples/Overlay/entry/src/main/resources/base/element/string.json create mode 100644 examples/Overlay/entry/src/main/resources/base/profile/main_pages.json create mode 100644 examples/Overlay/entry/src/main/resources/base/profile/route_map.json create mode 100644 examples/Overlay/entry/src/main/resources/en_US/element/string.json create mode 100644 examples/Overlay/entry/src/main/resources/zh_CN/element/string.json create mode 100644 examples/Overlay/entry/src/mock/mock-config.json5 create mode 100644 examples/Overlay/entry/src/ohosTest/ets/test/Ability.test.ets create mode 100644 examples/Overlay/entry/src/ohosTest/ets/test/List.test.ets create mode 100644 examples/Overlay/entry/src/ohosTest/module.json5 create mode 100644 examples/Overlay/entry/src/test/List.test.ets create mode 100644 examples/Overlay/entry/src/test/LocalUnit.test.ets create mode 100644 examples/Overlay/hvigor/hvigor-config.json5 create mode 100644 examples/Overlay/hvigorfile.ts create mode 100644 examples/Overlay/oh-package-lock.json5 create mode 100644 examples/Overlay/oh-package.json5 diff --git a/examples/Overlay/.gitignore b/examples/Overlay/.gitignore new file mode 100644 index 00000000000..d2ff20141ce --- /dev/null +++ b/examples/Overlay/.gitignore @@ -0,0 +1,12 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer \ No newline at end of file diff --git a/examples/Overlay/AppScope/app.json5 b/examples/Overlay/AppScope/app.json5 new file mode 100644 index 00000000000..4b94fd40eee --- /dev/null +++ b/examples/Overlay/AppScope/app.json5 @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "app": { + "bundleName": "com.example.overlay", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/examples/Overlay/AppScope/resources/base/element/string.json b/examples/Overlay/AppScope/resources/base/element/string.json new file mode 100644 index 00000000000..beb6355a388 --- /dev/null +++ b/examples/Overlay/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "Overlay" + } + ] +} diff --git a/examples/Overlay/AppScope/resources/base/media/app_icon.png b/examples/Overlay/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b GIT binary patch literal 20093 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

c)ijgxDm}8EsRXFt3h@+Ufr7@DN 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 literal 0 HcmV?d00001 diff --git a/examples/Overlay/build-profile.json5 b/examples/Overlay/build-profile.json5 new file mode 100644 index 00000000000..065a40f35f7 --- /dev/null +++ b/examples/Overlay/build-profile.json5 @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "app": { + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "5.0.3(15)", + "runtimeOS": "HarmonyOS", + "buildOption": { + "strictMode": { + "caseSensitiveCheck": true, + "useNormalizedOHMUrl": true + } + } + } + ], + "buildModeSet": [ + { + "name": "debug" + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/Overlay/code-linter.json5 b/examples/Overlay/code-linter.json5 new file mode 100644 index 00000000000..28586467ee7 --- /dev/null +++ b/examples/Overlay/code-linter.json5 @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "files": [ + "**/*.ets" + ], + "ignore": [ + "**/src/ohosTest/**/*", + "**/src/test/**/*", + "**/src/mock/**/*", + "**/node_modules/**/*", + "**/oh_modules/**/*", + "**/build/**/*", + "**/.preview/**/*" + ], + "ruleSet": [ + "plugin:@performance/recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/.gitignore b/examples/Overlay/entry/.gitignore new file mode 100644 index 00000000000..e2713a2779c --- /dev/null +++ b/examples/Overlay/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/examples/Overlay/entry/build-profile.json5 b/examples/Overlay/entry/build-profile.json5 new file mode 100644 index 00000000000..e7569e3056e --- /dev/null +++ b/examples/Overlay/entry/build-profile.json5 @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/examples/Overlay/entry/hvigorfile.ts b/examples/Overlay/entry/hvigorfile.ts new file mode 100644 index 00000000000..e4f43d54667 --- /dev/null +++ b/examples/Overlay/entry/hvigorfile.ts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/examples/Overlay/entry/obfuscation-rules.txt b/examples/Overlay/entry/obfuscation-rules.txt new file mode 100644 index 00000000000..272efb6ca3f --- /dev/null +++ b/examples/Overlay/entry/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/examples/Overlay/entry/oh-package.json5 b/examples/Overlay/entry/oh-package.json5 new file mode 100644 index 00000000000..c9cb6c81748 --- /dev/null +++ b/examples/Overlay/entry/oh-package.json5 @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": {} +} + diff --git a/examples/Overlay/entry/src/main/ets/entryability/EntryAbility.ets b/examples/Overlay/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 00000000000..0f2f8b94aa2 --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); + } +} diff --git a/examples/Overlay/entry/src/main/ets/extensionAbility/extensionAbility.ets b/examples/Overlay/entry/src/main/ets/extensionAbility/extensionAbility.ets new file mode 100644 index 00000000000..b05918fe9bd --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/extensionAbility/extensionAbility.ets @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { UIExtensionAbility, UIExtensionContentSession, Want } from '@kit.AbilityKit'; + +const TAG: string = '[ExampleEmbeddedAbility]' +export default class UIExtAbility extends UIExtensionAbility { + + onCreate() { + console.info(TAG, `onCreate`); + } + + onForeground() { + console.info(TAG, `onForeground`); + } + + onBackground() { + console.info(TAG, `onBackground`); + } + + onDestroy() { + console.info(TAG, `onDestroy`); + } + + onSessionCreate(want: Want, session: UIExtensionContentSession) { + console.info(TAG, `onSessionCreate, want: ${JSON.stringify(want)}`); + let param: Record = { + 'session': session + }; + let storage: LocalStorage = new LocalStorage(param); + session.loadContent('extensionAbility/pages/Index', storage); + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/extensionAbility/pages/Index.ets b/examples/Overlay/entry/src/main/ets/extensionAbility/pages/Index.ets new file mode 100644 index 00000000000..aa28361efb9 --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/extensionAbility/pages/Index.ets @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NavList, NavListItem } from '../../pages/utils/navigationList' + +@Entry +@Component +struct UECIndex { + pathStack: NavPathStack = new NavPathStack() + paths: NavListItem[] = [ + { name: '组件', path: 'NavIndex' }, + ] + + build() { + Navigation(this.pathStack) { + NavList({ + pages: this.paths, + onPathChange: (item: NavListItem) => { + this.pathStack.pushPath( { name: item.path } ) + } + }) + } + .onAppear(() => { + this.pathStack.pushPath({ name: 'NavIndex' }) + }) + .title('UEC弹框组件测试') + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/Index.ets b/examples/Overlay/entry/src/main/ets/pages/Index.ets new file mode 100644 index 00000000000..ce7a3492de0 --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NavList, NavListItem } from './utils/navigationList' + +@Entry +@Component +struct Index { + pathStack: NavPathStack = new NavPathStack() + paths: NavListItem[] = [ + { name: '非UIExtension场景', path: 'NavIndex' }, + ] + + build() { + Navigation(this.pathStack) { + NavList({ + pages: this.paths, + onPathChange: (item: NavListItem) => { + this.pathStack.pushPath( { name: item.path } ) + } + }) + } + .title('弹框组件测试') + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/base/ParamOption.ets b/examples/Overlay/entry/src/main/ets/pages/components/base/ParamOption.ets new file mode 100644 index 00000000000..65a4caf8d7d --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/base/ParamOption.ets @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class SetParam { + title?: string + func?: () => void +} + +@Component +export struct ParamOption { + title: string = 'test' + func: () => void = () => {} + + @Styles pressedStyle(){ + .backgroundColor(0x238E23) + } + + @Styles normalStyles() { + .backgroundColor(0x0000ff) + } + + build() { + Text(this.title) + .key(this.title) + .fontSize(10) + .backgroundColor(0x0000ff) + .fontColor(0xffffff) + .padding(5) + .onClick(this.func) + .stateStyles({pressed: this.pressedStyle, normal: this.normalStyles}) + } +} diff --git a/examples/Overlay/entry/src/main/ets/pages/components/dialog/Index.ets b/examples/Overlay/entry/src/main/ets/pages/components/dialog/Index.ets new file mode 100644 index 00000000000..d15aa691e3f --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/dialog/Index.ets @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NavList, NavListItem } from '../../utils/navigationList' + +@Builder +export function DialogIndexBuilder(name: string, param: Object) { + DialogIndex() +} + +@Entry +@Component +export struct DialogIndex { + pathStack: NavPathStack = new NavPathStack() + paths: NavListItem[] = [ + { name: 'AlertDialog', path: 'AlertDialog' }, + { name: 'ActionSheet', path: 'ActionSheet' }, + { name: 'CustomDialog', path: 'CustomDialog' }, + { name: 'PromptAction', path: 'PromptAction' }, + { name: 'DialogController', path: 'DialogController' }, + { name: 'DialogLevelOrder', path: 'DialogLevelOrder' }, + { name: 'DialogFocusable', path: 'DialogFocusable' }, + { name: 'EmbeddedDialog', path: 'EmbeddedDialogPageOne' }, + ] + + build() { + NavDestination() { + NavList({ + pages: this.paths, + onPathChange: (item: NavListItem) => { + this.pathStack.pushPath( { name: item.path } ) + } + }) + } + .title('Dialog') + .onBackPressed(() => { + this.pathStack.pop() + return true + }) + .onReady((context: NavDestinationContext) => { + this.pathStack = context.pathStack; + }) + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/dialog/actionSheet.ets b/examples/Overlay/entry/src/main/ets/pages/components/dialog/actionSheet.ets new file mode 100644 index 00000000000..2089d46774e --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/dialog/actionSheet.ets @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@Builder +export function ActionSheetBuilder(name: string, param: Object) { + ActionSheetExample() +} + +@Entry +@Component +struct ActionSheetExample { + pathStack: NavPathStack = new NavPathStack() + + build() { + NavDestination() { + Column({ space: 5 }) { + Text('ActionSheet.show') + .fontColor(Color.Gray) + .width('100%') + .padding(10) + + Button('Click to Show ActionSheet') + .onClick(() => { + ActionSheet.show({ + title: 'ActionSheet title', + subtitle: 'ActionSheet subtitle', + message: 'message', + autoCancel: true, + confirm: { + defaultFocus: true, + value: 'Confirm button', + action: () => { + console.log('Get Alert Dialog handled') + } + }, + cancel: () => { + console.log('actionSheet canceled') + }, + sheets: [ + { + title: 'apples', + action: () => { + console.log('apples') + } + }, + { + title: 'bananas', + action: () => { + console.log('bananas') + } + }, + { + title: 'pears', + action: () => { + console.log('pears') + } + } + ] + }) + }) + + Text('UIContext.showActionSheet') + .fontColor(Color.Gray) + .width('100%') + .padding(10) + + Button('Click to Show ActionSheet') + .onClick(() => { + this.getUIContext().showActionSheet({ + title: 'ActionSheet title', + subtitle: 'ActionSheet subtitle', + message: 'message', + autoCancel: true, + confirm: { + defaultFocus: true, + value: 'Confirm button', + action: () => { + console.log('Get Alert Dialog handled') + } + }, + cancel: () => { + console.log('actionSheet canceled') + }, + sheets: [ + { + title: 'apples', + action: () => { + console.log('apples') + } + }, + { + title: 'bananas', + action: () => { + console.log('bananas') + } + }, + { + title: 'pears', + action: () => { + console.log('pears') + } + } + ] + }) + }) + }.width('100%').margin({ top: 5 }) + } + .title('ActionSheet') + .onBackPressed(() => { + this.pathStack.pop() + return true + }) + .onReady((context: NavDestinationContext) => { + this.pathStack = context.pathStack; + }) + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/dialog/alertDialog.ets b/examples/Overlay/entry/src/main/ets/pages/components/dialog/alertDialog.ets new file mode 100644 index 00000000000..24e64367bad --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/dialog/alertDialog.ets @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@Builder +export function AlertDialogBuilder(name: string, param: Object) { + AlertDialogExample() +} + +@Entry +@Component +struct AlertDialogExample { + pathStack: NavPathStack = new NavPathStack() + + build() { + NavDestination() { + Column({ space: 5 }) { + Text('AlertDialog.show') + .fontColor(Color.Gray) + .width('100%') + .padding(10) + + Button('three button dialog') + .margin(10) + .onClick(() => { + AlertDialog.show({ + title: 'title', + subtitle: 'subtitle', + message: 'text', + buttonDirection: DialogButtonDirection.HORIZONTAL, + buttons: [ + { + value: '按钮', + action: () => { + console.info('Callback when button1 is clicked') + } + }, + { + value: '按钮', + action: () => { + console.info('Callback when button2 is clicked') + } + }, + { + value: '按钮', + enabled: true, + defaultFocus: true, + style: DialogButtonStyle.HIGHLIGHT, + action: () => { + console.info('Callback when button3 is clicked') + } + }, + ], + cancel: () => { + console.info('Closed callbacks') + } + }) + }) + + Text('UIContext.showAlertDialog') + .fontColor(Color.Gray) + .width('100%') + .padding(10) + + Button('three button dialog') + .margin(10) + .onClick(() => { + this.getUIContext().showAlertDialog({ + title: 'title', + subtitle: 'subtitle', + message: 'text', + buttonDirection: DialogButtonDirection.HORIZONTAL, + buttons: [ + { + value: '按钮', + action: () => { + console.info('Callback when button1 is clicked') + } + }, + { + value: '按钮', + action: () => { + console.info('Callback when button2 is clicked') + } + }, + { + value: '按钮', + enabled: true, + defaultFocus: true, + style: DialogButtonStyle.HIGHLIGHT, + action: () => { + console.info('Callback when button3 is clicked') + } + }, + ], + cancel: () => { + console.info('Closed callbacks') + } + }) + }) + } + .width('100%') + } + .title('AlertDialog') + .onBackPressed(() => { + this.pathStack.pop() + return true + }) + .onReady((context: NavDestinationContext) => { + this.pathStack = context.pathStack; + }) + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/dialog/customDialog.ets b/examples/Overlay/entry/src/main/ets/pages/components/dialog/customDialog.ets new file mode 100644 index 00000000000..32fc819bb54 --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/dialog/customDialog.ets @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@Builder +export function CustomDialogBuilder(name: string, param: Object) { + CustomDialogExample() +} + +@CustomDialog +@Component +struct CustomBuilder { + @Prop textValue: string + @Prop inputValue: string + controller?: CustomDialogController + // 若尝试在CustomDialog中传入多个其他的Controller,以实现在CustomDialog中打开另一个或另一些CustomDialog, + // 那么此处需要将指向自己的controller放在所有controller的后面 + cancel: () => void = () => { + } + confirm: () => void = () => { + } + + build() { + Column() { + Text('Change text').fontSize(20).margin({ top: 10, bottom: 10 }) + TextInput({ placeholder: '', text: this.textValue }).height(60).width('90%') + .onChange((value: string) => { + this.textValue = value + }) + Flex({ justifyContent: FlexAlign.SpaceAround }) { + Button('cancel') + .onClick(() => { + if (this.controller != undefined) { + this.controller.close() + this.cancel() + } + }).backgroundColor(0xffffff).fontColor(Color.Black) + Button('confirm') + .onClick(() => { + if (this.controller != undefined) { + this.inputValue = this.textValue + this.controller.close() + this.confirm() + } + }).backgroundColor(0xffffff).fontColor(Color.Red) + }.margin({ bottom: 10 }) + }.borderRadius(10) + } +} + +@Entry +@Component +struct CustomDialogExample { + pathStack: NavPathStack = new NavPathStack() + + @State textValue: string = '' + @State inputValue: string = 'click me' + dialogController: CustomDialogController | null = new CustomDialogController({ + builder: CustomBuilder({ + cancel: ()=> { this.onCancel() }, + confirm: ()=> { this.onAccept() }, + textValue: this.textValue, + inputValue: this.inputValue + }), + cancel: this.exitApp, + autoCancel: true, + onWillDismiss:(dismissDialogAction: DismissDialogAction)=> { + console.info('reason=' + JSON.stringify(dismissDialogAction.reason)) + console.log('dialog onWillDismiss') + if (dismissDialogAction.reason == DismissReason.PRESS_BACK) { + dismissDialogAction.dismiss() + } + if (dismissDialogAction.reason == DismissReason.TOUCH_OUTSIDE) { + dismissDialogAction.dismiss() + } + }, + alignment: DialogAlignment.Bottom, + customStyle: false, + cornerRadius: 10, + }) + + // 在自定义组件即将析构销毁时将dialogController置空 + aboutToDisappear() { + this.dialogController = null + } + + onCancel() { + console.info('Callback when the first button is clicked') + } + + onAccept() { + console.info('Callback when the second button is clicked') + } + + exitApp() { + console.info('Click the callback in the blank area') + } + + build() { + NavDestination() { + Column({ space: 5 }) { + Text('dialogController.open') + .fontColor(Color.Gray) + .width('100%') + .padding(10) + + Button(this.inputValue) + .onClick(() => { + if (this.dialogController != null) { + this.dialogController.open() + } + }) + }.width('100%').margin({ top: 5 }) + } + .title('CustomDialog') + .onBackPressed(() => { + this.pathStack.pop() + return true + }) + .onReady((context: NavDestinationContext) => { + this.pathStack = context.pathStack; + }) + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/dialog/dialogController.ets b/examples/Overlay/entry/src/main/ets/pages/components/dialog/dialogController.ets new file mode 100644 index 00000000000..cdb8754f56e --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/dialog/dialogController.ets @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentContent, PromptAction, promptAction } from '@kit.ArkUI' +import { BusinessError } from '@kit.BasicServicesKit' + +class Params { + public text: string = '' + public dialogController: promptAction.CommonController = new promptAction.DialogController() + constructor(text: string, dialogController: promptAction.CommonController) { + this.text = text + this.dialogController = dialogController + } +} + +@Component +struct MyComponent { + build() { + Column() { + Button('点我关闭弹窗:通过自定义组件自带的DialogController') + .onClick(() => { + let dialogController: promptAction.DialogController = this.getDialogController() + if (dialogController !== undefined) { + dialogController.close() + } + }) + } + } +} + +@Builder +function buildText(params: Params) { + Column({ space: 5 }) { + Text(params.text) + .fontSize(30) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 36 }) + if (params.dialogController !== undefined) { + Button('点我关闭弹窗:通过外部传递的DialogController') + .onClick(() => { + params.dialogController.close() + }) + } + MyComponent() + } + .width(300) + .height(200) + .backgroundColor('#FFF0F0F0') +} + +@Builder +function buildTextNoParams() { + Column({ space: 5 }) { + Text('弹窗:无外部传递的DialogController') + .fontSize(30) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 36 }) + MyComponent() + }.backgroundColor('#FFF0F0F0') +} + +class FuncParams { + public text: string = '' + public dialogController: promptAction.CommonController = new promptAction.DialogController() + public func: Function + constructor(text: string, dialogController: promptAction.CommonController, func: Function) { + this.text = text + this.dialogController = dialogController + this.func = func + } +} + +@Builder +function buildTextWithFunc(func: Function, dialogController: promptAction.DialogController) { + Text(func()) + .fontSize(50) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 36 }) + if (dialogController !== undefined) { + Button('点我关闭弹窗:嵌套传递的DialogController') + .onClick(() => { + dialogController.close() + }) + } +} + +@Builder +function buildTextFull(params: FuncParams) { + Column({ space: 5 }) { + Text(params.text) + .fontSize(30) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 36 }) + if (params.dialogController !== undefined) { + Button('点我关闭弹窗:通过外部传递的DialogController') + .onClick(() => { + params.dialogController.close() + }) + } + MyComponent() + buildTextWithFunc(params.func, params.dialogController) + }.backgroundColor('#FFF0F0F0') +} + +@CustomDialog +@Component +struct CustomDialogExample { + controller?: CustomDialogController + + build() { + Column() { + Text('我是内容') + .fontSize(20) + .margin({ top: 10, bottom: 10 }) + TextInput() + Button('点我关闭弹窗:通过自定义组件自带的DialogController') + .onClick(() => { + let dialogController: PromptActionDialogController = this.getDialogController() + if (dialogController !== undefined) { + dialogController.close() + } + }) + } + .height(400) + .backgroundColor('#FFF0F0F0') + } +} + +@Builder +export function DialogControllerBuilder(name: string, param: Object) { + DialogController() +} + +@Component +export struct DialogController { + private message = '弹窗' + private ctx: UIContext = this.getUIContext() + private promptAction: PromptAction = this.ctx.getPromptAction() + + @State customDialogIds: number[] = [] + @State dialogIdIndex: number = 0 + @Builder customDialogComponent(idIndex: number, controller: promptAction.DialogController) { + Column() { + Text(this.message) + .fontSize(30) + TextInput() + Row({ space: 5 }) { + if (idIndex !== undefined) { + Button('点击关闭弹窗:通过ID') + .onClick(() => { + this.promptAction.closeCustomDialog(this.customDialogIds[idIndex]) + this.customDialogIds[idIndex] = null! + }) + } + if (controller !== undefined) { + Button('点击关闭弹窗:通过外部传递的DialogController') + .onClick(() => { + controller.close() + }) + } + } + } + .height(200) + .padding(5) + .justifyContent(FlexAlign.SpaceBetween) + .backgroundColor('#FFF0F0F0') + } + + @Builder customDialogComponentWithId(dialogId: number, controller: promptAction.DialogController) { + Column() { + Text(this.message) + .fontSize(30) + TextInput() + Row({ space: 5 }) { + if (dialogId !== undefined) { + Button('点击关闭弹窗:通过ID') + .onClick(() => { + this.promptAction.closeCustomDialog(dialogId) + }) + } + if (controller !== undefined) { + Button('点击关闭弹窗:通过外部传递的DialogController') + .onClick(() => { + controller.close() + }) + } + } + } + } + + private baseDialogOptions: promptAction.BaseDialogOptions = { + isModal: false, + autoCancel: false + } + + private dialogOptions: promptAction.DialogOptions = { + isModal: false, + autoCancel: false + } + + pathStack: NavPathStack = new NavPathStack() + + build() { + NavDestination() { + Column({ space: 5 }) { + Button('openCustomDialog+CustomBuilder弹窗') + .onClick(() => { + this.promptAction.openCustomDialog({ + builder: () => { + this.customDialogComponent(this.dialogIdIndex, undefined!) + } + }).then((dialogId: number) => { + this.customDialogIds[this.dialogIdIndex] = dialogId + this.dialogIdIndex++ + }).catch((err: BusinessError) => { + console.error('openCustomDialog error: ' + err.code + ' ' + err.message) + }) + }) + Button('openCustomDialog+ParamsNode弹窗') + .onClick(() => { + let contentNode: ComponentContent = + new ComponentContent(this.ctx, wrapBuilder(buildText), new Params(this.message, undefined!)) + this.promptAction.openCustomDialog(contentNode, this.baseDialogOptions) + .catch((err: BusinessError) => { + console.error('openCustomDialog error: ' + err.code + ' ' + err.message) + }) + }) + Button('openCustomDialogWithController+全部参数+NoParamsNode弹窗') + .onClick(() => { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + let contentNodeNoParams: ComponentContent = + new ComponentContent(this.ctx, wrapBuilder(buildTextNoParams)) + this.promptAction.openCustomDialogWithController( + contentNodeNoParams, dialogController, this.baseDialogOptions + ).catch((err: BusinessError) => { + console.error('openCustomDialogWithController error: ' + err.code + ' ' + err.message) + }) + }) + Button('openCustomDialogWithController+必填参数+NoParamsNode弹窗') + .onClick(() => { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + let contentNodeNoParams: ComponentContent = + new ComponentContent(this.ctx, wrapBuilder(buildTextNoParams)) + this.promptAction.openCustomDialogWithController(contentNodeNoParams, dialogController) + .catch((err: BusinessError) => { + console.error('openCustomDialogWithController error: ' + err.code + ' ' + err.message) + }) + }) + Button('openCustomDialogWithController+全部参数+ParamsNode弹窗') + .onClick(() => { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + let contentNode: ComponentContent = + new ComponentContent(this.ctx, wrapBuilder(buildText), new Params(this.message, dialogController)) + this.promptAction.openCustomDialogWithController(contentNode, dialogController, this.baseDialogOptions) + .catch((err: BusinessError) => { + console.error('openCustomDialogWithController error: ' + err.code + ' ' + err.message) + }) + }) + Button('openCustomDialogWithController+必填参数+ParamsNode弹窗') + .onClick(() => { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + let contentNode: ComponentContent = + new ComponentContent(this.ctx, wrapBuilder(buildText), new Params(this.message, dialogController)) + this.promptAction.openCustomDialogWithController(contentNode, dialogController) + .catch((err: BusinessError) => { + console.error('openCustomDialogWithController error: ' + err.code + ' ' + err.message) + }) + }) + Button('openCustomDialogWithController+全部参数+FullNode弹窗') + .onClick(() => { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + let contentNodeFull: ComponentContent = new ComponentContent(this.ctx, wrapBuilder(buildTextFull), + new FuncParams(this.message, dialogController, () => { return 'FUNCTION' }), + { nestingBuilderSupported: true }) + this.promptAction.openCustomDialogWithController(contentNodeFull, dialogController, this.baseDialogOptions) + .catch((err: BusinessError) => { + console.error('openCustomDialogWithController error: ' + err.code + ' ' + err.message) + }) + }) + Button('openCustomDialogWithController+必填参数+FullNode弹窗') + .onClick(() => { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + let contentNodeFull: ComponentContent = new ComponentContent(this.ctx, wrapBuilder(buildTextFull), + new FuncParams(this.message, dialogController, () => { return 'FUNCTION' }), + { nestingBuilderSupported: true }) + this.promptAction.openCustomDialogWithController(contentNodeFull, dialogController) + .catch((err: BusinessError) => { + console.error('openCustomDialogWithController error: ' + err.code + ' ' + err.message) + }) + }) + Button('presentCustomDialog+CustomBuilderWithId全部参数弹窗') + .onClick(() => { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + this.promptAction.presentCustomDialog((dialogId: number) => { + this.customDialogComponentWithId(dialogId, dialogController) + }, dialogController, this.dialogOptions).catch((err: BusinessError) => { + console.error('openCustomDialogWithController error: ' + err.code + ' ' + err.message) + }) + }) + Button('presentCustomDialog+CustomBuilderWithId必填参数+Controller弹窗') + .onClick(() => { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + this.promptAction.presentCustomDialog((dialogId: number) => { + this.customDialogComponentWithId(dialogId, dialogController) + }, dialogController).catch((err: BusinessError) => { + console.error('openCustomDialogWithController error: ' + err.code + ' ' + err.message) + }) + }) + Button('presentCustomDialog+CustomBuilderWithId必填参数弹窗') + .onClick(() => { + this.promptAction.presentCustomDialog((dialogId: number) => { + this.customDialogComponentWithId(dialogId, undefined!) + }).catch((err: BusinessError) => { + console.error('openCustomDialogWithController error: ' + err.code + ' ' + err.message) + }) + }) + Button('presentCustomDialog+CustomBuilder全部参数弹窗') + .onClick(() => { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + this.promptAction.presentCustomDialog(() => { + this.customDialogComponent(this.dialogIdIndex, dialogController) + }, dialogController, this.dialogOptions).then((dialogId: number) => { + this.customDialogIds[this.dialogIdIndex] = dialogId + this.dialogIdIndex++ + }).catch((err: BusinessError) => { + console.error('openCustomDialogWithController error: ' + err.code + ' ' + err.message) + }) + }) + Button('presentCustomDialog+CustomBuilder必填参数+Controller弹窗') + .onClick(() => { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + this.promptAction.presentCustomDialog(() => { + this.customDialogComponent(this.dialogIdIndex, dialogController) + }, dialogController).then((dialogId: number) => { + this.customDialogIds[this.dialogIdIndex] = dialogId + this.dialogIdIndex++ + }).catch((err: BusinessError) => { + console.error('openCustomDialogWithController error: ' + err.code + ' ' + err.message) + }) + }) + Button('presentCustomDialog+CustomBuilder必填参数弹窗') + .onClick(() => { + this.promptAction.presentCustomDialog(() => { + this.customDialogComponent(this.dialogIdIndex, undefined!) + }).then((dialogId: number) => { + this.customDialogIds[this.dialogIdIndex] = dialogId + this.dialogIdIndex++ + }).catch((err: BusinessError) => { + console.error('openCustomDialogWithController error: ' + err.code + ' ' + err.message) + }) + }) + Button('CustomDialogController弹窗') + .onClick(() => { + let customDialogController: CustomDialogController = new CustomDialogController({ + builder: CustomDialogExample(), + }) + customDialogController.open() + }) + } + } + .title('DialogController') + .onBackPressed(() => { + this.pathStack.pop() + return true + }) + .onReady((context: NavDestinationContext) => { + this.pathStack = context.pathStack + }) + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/dialog/dialogFocusable.ets b/examples/Overlay/entry/src/main/ets/pages/components/dialog/dialogFocusable.ets new file mode 100644 index 00000000000..c562230f13a --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/dialog/dialogFocusable.ets @@ -0,0 +1,576 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentContent, LengthMetrics, PromptAction, promptAction } from '@kit.ArkUI' +import { ParamOption, SetParam } from '../base/ParamOption' + +class Params { + public dialogController: promptAction.CommonController = new promptAction.DialogController() + public secondInterface: string = 'openCustomDialog' + public secondShowInSubWindow: boolean = false + public secondFocusable: boolean = false + constructor(dialogController: promptAction.CommonController, secondInterface: string, secondShowInSubWindow: boolean, + secondFocusable: boolean) { + this.dialogController = dialogController + this.secondInterface = secondInterface + this.secondShowInSubWindow = secondShowInSubWindow + this.secondFocusable = secondFocusable + } +} + +@Builder +function buildText(params: Params) { + Column({ space: 5 }) { + Text('弹窗') + .fontSize(30) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 36 }) + SecondDialogComponent({ + secondInterface: params.secondInterface, + secondShowInSubWindow: params.secondShowInSubWindow, + secondFocusable: params.secondFocusable + }) + Button('点我关闭弹窗') + .onClick(() => { + if (params.dialogController !== undefined) { + params.dialogController.close() + } + }) + } + .width(300) + .height(200) + .backgroundColor('#FFF0F0F0') +} + +@CustomDialog +@Component +struct CustomDialogExample { + secondInterface: string = 'openCustomDialog' + secondShowInSubWindow: boolean = false + secondFocusable: boolean = false + controller?: CustomDialogController + + private ctx: UIContext = this.getUIContext() + private promptAction: PromptAction = this.ctx.getPromptAction() + + @State customDialogIds: number[] = [] + @State dialogIdIndex: number = 0 + @Builder customDialogComponent(idIndex: number = 0) { + Column() { + Text('弹窗').fontSize(30) + Button('点击关闭弹窗') + .onClick(() => { + this.promptAction.closeCustomDialog(this.customDialogIds[idIndex]) + this.customDialogIds[idIndex] = null! + }) + } + .height(200) + .padding(5) + .justifyContent(FlexAlign.SpaceBetween) + } + + build() { + Column() { + Text('我是内容') + .fontSize(20) + .margin({ top: 10, bottom: 10 }) + TextInput() + .onChange(() => { + switch (this.secondInterface) { + case 'openCustomDialog': { + this.promptAction.openCustomDialog({ + builder: () => { + this.customDialogComponent(this.dialogIdIndex) + }, + showInSubWindow: this.secondShowInSubWindow, + focusable: this.secondFocusable + }).then((dialogId: number) => { + this.customDialogIds[this.dialogIdIndex] = dialogId + this.dialogIdIndex++ + }) + break + } + case 'openCustomDialogWithController': { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + let contentNode: ComponentContent = new ComponentContent(this.ctx, wrapBuilder(buildText), + new Params(dialogController, this.secondInterface, this.secondShowInSubWindow, this.secondFocusable)); + this.promptAction.openCustomDialogWithController(contentNode, dialogController, { + showInSubWindow: this.secondShowInSubWindow, + focusable: this.secondFocusable, + }) + break + } + case 'presentCustomDialog': { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + this.promptAction.presentCustomDialog(() => { + this.customDialogComponent(this.dialogIdIndex) + }, dialogController, { + showInSubWindow: this.secondShowInSubWindow, + focusable: this.secondFocusable, + }).then((dialogId: number) => { + this.customDialogIds[this.dialogIdIndex] = dialogId + }) + } + case 'CustomDialogController': { + let customDialogController: CustomDialogController = new CustomDialogController({ + builder: CustomDialogExample({ + secondInterface: this.secondInterface, + secondShowInSubWindow: this.secondShowInSubWindow, + secondFocusable: this.secondFocusable + }), + showInSubWindow: this.secondShowInSubWindow, + focusable: this.secondFocusable, + }) + customDialogController.open() + } + } + }) + Button('点我关闭弹窗') + .onClick(() => { + let ctrl: PromptActionDialogController = this.getDialogController() + if (ctrl !== undefined) { + ctrl.close() + } + }) + } + .height(400) + } +} + +@Component +struct SecondDialogComponent { + secondInterface: string = 'openCustomDialog' + secondShowInSubWindow: boolean = false + secondFocusable: boolean = false + + private ctx: UIContext = this.getUIContext() + private promptAction: PromptAction = this.ctx.getPromptAction() + + @State customDialogIds: number[] = [] + @State dialogIdIndex: number = 0 + @Builder customDialogComponent(idIndex: number = 0) { + Column() { + Text('弹窗').fontSize(30) + Button('点击关闭弹窗') + .onClick(() => { + this.promptAction.closeCustomDialog(this.customDialogIds[idIndex]) + this.customDialogIds[idIndex] = null! + }) + } + .height(200) + .padding(5) + .justifyContent(FlexAlign.SpaceBetween) + } + + build() { + Column() { + TextInput() + .onChange(() => { + switch (this.secondInterface) { + case 'openCustomDialog': { + this.promptAction.openCustomDialog({ + builder: () => { + this.customDialogComponent(this.dialogIdIndex) + }, + showInSubWindow: this.secondShowInSubWindow, + focusable: this.secondFocusable + }).then((dialogId: number) => { + this.customDialogIds[this.dialogIdIndex] = dialogId + this.dialogIdIndex++ + }) + break + } + case 'openCustomDialogWithController': { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + let contentNode: ComponentContent = new ComponentContent(this.ctx, wrapBuilder(buildText), + new Params(dialogController, this.secondInterface, this.secondShowInSubWindow, this.secondFocusable)); + this.promptAction.openCustomDialogWithController(contentNode, dialogController, { + showInSubWindow: this.secondShowInSubWindow, + focusable: this.secondFocusable, + }) + break + } + case 'presentCustomDialog': { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + this.promptAction.presentCustomDialog(() => { + this.customDialogComponent(this.dialogIdIndex) + }, dialogController, { + showInSubWindow: this.secondShowInSubWindow, + focusable: this.secondFocusable, + }).then((dialogId: number) => { + this.customDialogIds[this.dialogIdIndex] = dialogId + }) + break + } + case 'CustomDialogController': { + let customDialogController: CustomDialogController = new CustomDialogController({ + builder: CustomDialogExample({ + secondInterface: this.secondInterface, + secondShowInSubWindow: this.secondShowInSubWindow, + secondFocusable: this.secondFocusable + }), + showInSubWindow: this.secondShowInSubWindow, + focusable: this.secondFocusable, + }) + customDialogController.open() + break + } + } + }) + } + } +} + +@Builder +export function DialogFocusableBuilder(name: string, param: Object) { + DialogFocusable() +} + +@Component +export struct DialogFocusable { + private ctx: UIContext = this.getUIContext() + private promptAction: PromptAction = this.ctx.getPromptAction() + + @State customDialogIds: number[] = [] + @State dialogIdIndex: number = 0 + @Builder customDialogComponent(idIndex: number = 0) { + Column() { + Text('弹窗').fontSize(30) + TextInput() + .onChange(() => { + this.openSecondDialog() + }) + Button('点击关闭弹窗') + .onClick(() => { + this.promptAction.closeCustomDialog(this.customDialogIds[idIndex]) + this.customDialogIds[this.dialogIdIndex] = null! + }) + } + .height(200) + .padding(5) + .justifyContent(FlexAlign.SpaceBetween) + } + + @State firstInterface: string = 'openCustomDialog' + private setFirstInterface: SetParam[] = [{ + title: 'open', + func: () => { + this.firstInterface = 'openCustomDialog' + } + }, { + title: 'openWithController', + func: () => { + this.firstInterface = 'openCustomDialogWithController' + } + }, { + title: 'present', + func: () => { + this.firstInterface = 'presentCustomDialog' + } + }, { + title: 'Controller', + func: () => { + this.firstInterface = 'CustomDialogController' + } + }] + + @State firstShowInSubWindow: boolean = false + private setFirstShowInSubWindow: SetParam[] = [{ + title: 'false', + func: () => { + this.firstShowInSubWindow = false + } + }, { + title: 'true', + func: () => { + this.firstShowInSubWindow = true + } + }] + + @State firstFocusable: boolean = false + private setFirstFocusable: SetParam[] = [{ + title: 'false', + func: () => { + this.firstFocusable = false + } + }, { + title: 'true', + func: () => { + this.firstFocusable = true + } + }] + + @State secondInterface: string = 'openCustomDialog' + private setSecondInterface: SetParam[] = [{ + title: 'open', + func: () => { + this.secondInterface = 'openCustomDialog' + } + }, { + title: 'openWithController', + func: () => { + this.secondInterface = 'openCustomDialogWithController' + } + }, { + title: 'present', + func: () => { + this.secondInterface = 'presentCustomDialog' + } + }, { + title: 'Controller', + func: () => { + this.secondInterface = 'CustomDialogController' + } + }] + + @State secondShowInSubWindow: boolean = false + private setSecondShowInSubWindow: SetParam[] = [{ + title: 'false', + func: () => { + this.secondShowInSubWindow = false + } + }, { + title: 'true', + func: () => { + this.secondShowInSubWindow = true + } + }] + + @State secondFocusable: boolean = false + private setSecondFocusable: SetParam[] = [{ + title: 'false', + func: () => { + this.secondFocusable = false + } + }, { + title: 'true', + func: () => { + this.secondFocusable = true + } + }] + + openFirstDialog(enableAsync: boolean = false) { + switch (this.firstInterface) { + case 'openCustomDialog': { + this.promptAction.openCustomDialog({ + builder: () => { + this.customDialogComponent(this.dialogIdIndex) + }, + showInSubWindow: this.firstShowInSubWindow, + focusable: this.firstFocusable + }).then((dialogId: number) => { + this.customDialogIds[this.dialogIdIndex] = dialogId + this.dialogIdIndex++ + if (!enableAsync) { + return + } + setTimeout(() => { + this.openSecondDialog() + }, 5000) + }) + break + } + case 'openCustomDialogWithController': { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + let contentNode: ComponentContent = new ComponentContent(this.ctx, wrapBuilder(buildText), + new Params(dialogController, this.secondInterface, this.secondShowInSubWindow, this.secondFocusable)); + this.promptAction.openCustomDialogWithController(contentNode, dialogController, { + showInSubWindow: this.secondShowInSubWindow, + focusable: this.secondFocusable, + }).then(() => { + if (!enableAsync) { + return + } + setTimeout(() => { + this.openSecondDialog() + }, 5000) + }) + break + } + case 'presentCustomDialog': { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + this.promptAction.presentCustomDialog(() => { + this.customDialogComponent(this.dialogIdIndex) + }, dialogController, { + showInSubWindow: this.secondShowInSubWindow, + focusable: this.secondFocusable, + }).then((dialogId: number) => { + this.customDialogIds[this.dialogIdIndex] = dialogId + this.dialogIdIndex++ + if (!enableAsync) { + return + } + setTimeout(() => { + this.openSecondDialog() + }, 5000) + }) + break + } + case 'CustomDialogController': { + let customDialogController: CustomDialogController = new CustomDialogController({ + builder: CustomDialogExample({ + secondInterface: this.secondInterface, + secondShowInSubWindow: this.secondShowInSubWindow, + secondFocusable: this.secondFocusable + }), + showInSubWindow: this.secondShowInSubWindow, + focusable: this.secondFocusable, + keyboardAvoidDistance: LengthMetrics.vp(0) + }) + customDialogController.open() + if (!enableAsync) { + return + } + setTimeout(() => { + this.openSecondDialog() + }, 5000) + break + } + } + } + + openSecondDialog() { + switch (this.secondInterface) { + case 'openCustomDialog': { + this.promptAction.openCustomDialog({ + builder: () => { + this.customDialogComponent(this.dialogIdIndex) + }, + showInSubWindow: this.firstShowInSubWindow, + focusable: this.firstFocusable + }).then((dialogId: number) => { + this.customDialogIds[this.dialogIdIndex] = dialogId + this.dialogIdIndex++ + }) + break + } + case 'openCustomDialogWithController': { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + let contentNode: ComponentContent = new ComponentContent(this.ctx, wrapBuilder(buildText), + new Params(dialogController, this.secondInterface, this.secondShowInSubWindow, this.secondFocusable)); + this.promptAction.openCustomDialogWithController(contentNode, dialogController, { + showInSubWindow: this.secondShowInSubWindow, + focusable: this.secondFocusable, + }) + break + } + case 'presentCustomDialog': { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + this.promptAction.presentCustomDialog(() => { + this.customDialogComponent(this.dialogIdIndex) + }, dialogController, { + showInSubWindow: this.secondShowInSubWindow, + focusable: this.secondFocusable, + }).then((dialogId: number) => { + this.customDialogIds[this.dialogIdIndex] = dialogId + this.dialogIdIndex++ + }) + break + } + case 'CustomDialogController': { + let customDialogController: CustomDialogController = new CustomDialogController({ + builder: CustomDialogExample({ + secondInterface: this.secondInterface, + secondShowInSubWindow: this.secondShowInSubWindow, + secondFocusable: this.secondFocusable + }), + showInSubWindow: this.secondShowInSubWindow, + focusable: this.secondFocusable, + }) + customDialogController.open() + break + } + } + } + + pathStack: NavPathStack = new NavPathStack() + + build() { + NavDestination() { + Column({ space: 5 }) { + Text('1st Interface: ' + this.firstInterface) + .fontSize(12) + Row({ space: 2 }) { + ForEach(this.setFirstInterface, (item: SetParam) => { + ParamOption({ title: item.title, func: item.func }) + }, (item: SetParam) => item.title) + } + + Text('1st ShowInSubWindow: ' + this.firstShowInSubWindow) + .fontSize(12) + Row({ space: 2 }) { + ForEach(this.setFirstShowInSubWindow, (item: SetParam) => { + ParamOption({ title: item.title, func: item.func }) + }, (item: SetParam) => item.title) + } + + Text('1st Focusable: ' + this.firstFocusable) + .fontSize(12) + Row({ space: 2 }) { + ForEach(this.setFirstFocusable, (item: SetParam) => { + ParamOption({ title: item.title, func: item.func }) + }, (item: SetParam) => item.title) + } + + TextInput({ placeholder: '输入任意字符弹出弹窗' }) + .onChange(() => { + this.openFirstDialog() + }) + + Text('2nd Interface: ' + this.secondInterface) + .fontSize(12) + Row({ space: 2 }) { + ForEach(this.setSecondInterface, (item: SetParam) => { + ParamOption({ title: item.title, func: item.func }) + }, (item: SetParam) => item.title) + } + + Text('2nd ShowInSubWindow: ' + this.secondShowInSubWindow) + .fontSize(12) + Row({ space: 2 }) { + ForEach(this.setSecondShowInSubWindow, (item: SetParam) => { + ParamOption({ title: item.title, func: item.func }) + }, (item: SetParam) => item.title) + } + + Text('2nd Focusable: ' + this.secondFocusable) + .fontSize(12) + Row({ space: 2 }) { + ForEach(this.setSecondFocusable, (item: SetParam) => { + ParamOption({ title: item.title, func: item.func }) + }, (item: SetParam) => item.title) + } + + Button('弹出多级弹窗') + .onClick(() => { + this.openFirstDialog() + }) + + Button('弹出异步弹窗') + .onClick(() => { + this.openFirstDialog() + }) + } + } + .title('DialogFocusable') + .onBackPressed(() => { + this.pathStack.pop() + return true + }) + .onReady((context: NavDestinationContext) => { + this.pathStack = context.pathStack + }) + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/dialog/dialogLevelOrder.ets b/examples/Overlay/entry/src/main/ets/pages/components/dialog/dialogLevelOrder.ets new file mode 100644 index 00000000000..b5894228b66 --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/dialog/dialogLevelOrder.ets @@ -0,0 +1,961 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentContent, LevelOrder, PromptAction, promptAction } from '@kit.ArkUI' +import { ParamOption, SetParam } from '../base/ParamOption' + +class Params { + public text: string = '' + public dialogController: promptAction.CommonController = new promptAction.DialogController() + constructor(text: string, dialogController: promptAction.CommonController) { + this.text = text + this.dialogController = dialogController + } +} + +@Builder +function buildText(params: Params) { + Column({ space: 5 }) { + Text(params.text) + .fontSize(30) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 36 }) + Button('点我关闭弹窗') + .onClick(() => { + if (params.dialogController !== undefined) { + params.dialogController.close() + } + }) + } + .width(300) + .height(200) + .backgroundColor('#FFF0F0F0') +} + +@CustomDialog +@Component +struct CustomDialogExample { + title: string = '弹窗' + controller?: CustomDialogController + private ctx: UIContext = this.getUIContext() + private promptAction: PromptAction = this.ctx.getPromptAction() + + textInputCtrl: TextInputController = new TextInputController() + @State inputValue: string = '' + @Builder CustomKeyboardBuilder() { + Column() { + Button('X') + .onClick(() => { + this.textInputCtrl.stopEditing() + }) + Grid() { + ForEach([ 1, 2, 3, 4, 5, 6, 7, 8, 9, '*', 0, '#' ], (item: number | string) => { + GridItem() { + Button(item + '') + .width(110).onClick(() => { + this.inputValue += item + }) + } + }) + } + .maxCount(3) + .columnsGap(10) + .rowsGap(10) + .padding(5) + } + .backgroundColor(Color.Gray) + .height(400) + } + + build() { + Column() { + Text(this.title) + .fontSize(20) + .margin({ top: 10, bottom: 10 }) + TextInput({ controller: this.textInputCtrl, text: this.inputValue }) + .customKeyboard(this.CustomKeyboardBuilder(), { supportAvoidance: true }) + .margin(10) + .border({ width: 1 }) + .height('48vp') + .onChange(() => { + let topOrder: LevelOrder = this.promptAction.getTopOrder(); + if (topOrder === undefined) { + console.error('topOrder: ' + topOrder); + } else { + console.error('topOrder: ' + topOrder.getOrder()); + } + + let bottomOrder: LevelOrder = this.promptAction.getBottomOrder(); + if (bottomOrder === undefined) { + console.error('bottomOrder: ' + bottomOrder); + } else { + console.error('bottomOrder: ' + bottomOrder.getOrder()); + } + }) + Button('点我关闭弹窗') + .onClick(() => { + let ctrl: PromptActionDialogController = this.getDialogController() + if (ctrl !== undefined) { + ctrl.close() + } + }) + } + .height(200) + .backgroundColor('#FFF0F0F0') + } +} + +@Builder +export function DialogLevelOrderBuilder(name: string, param: Object) { + DialogLevelOrder() +} + +@Component +export struct DialogLevelOrder { + private message1 = '弹窗1' + private message2 = '弹窗2' + private ctx: UIContext = this.getUIContext() + private promptAction: PromptAction = this.ctx.getPromptAction() + + textInputCtrl: TextInputController = new TextInputController() + @State inputValue: string = '' + @Builder CustomKeyboardBuilder() { + Column() { + Button('X') + .onClick(() => { + this.textInputCtrl.stopEditing() + }) + Grid() { + ForEach([ 1, 2, 3, 4, 5, 6, 7, 8, 9, '*', 0, '#' ], (item: number | string) => { + GridItem() { + Button(item + '') + .width(110).onClick(() => { + this.inputValue += item + }) + } + }) + } + .maxCount(3) + .columnsGap(10) + .rowsGap(10) + .padding(5) + } + .backgroundColor(Color.Gray) + .height(400) + } + + @State customDialogIds: number[] = [] + @State dialogIdIndex: number = 0 + @Builder customDialogComponent(title: string, idIndex: number = 0) { + Column() { + Text(title).fontSize(30) + TextInput({ controller: this.textInputCtrl, text: this.inputValue }) + .customKeyboard(this.CustomKeyboardBuilder(), { supportAvoidance: true }) + .margin(10) + .border({ width: 1 }) + .height('48vp') + .onChange(() => { + let topOrder: LevelOrder = this.promptAction.getTopOrder(); + if (topOrder === undefined) { + console.error('topOrder: ' + topOrder); + } else { + console.error('topOrder: ' + topOrder.getOrder()); + } + + let bottomOrder: LevelOrder = this.promptAction.getBottomOrder(); + if (bottomOrder === undefined) { + console.error('bottomOrder: ' + bottomOrder); + } else { + console.error('bottomOrder: ' + bottomOrder.getOrder()); + } + }) + Button('点击关闭弹窗') + .onClick(() => { + this.promptAction.closeCustomDialog(this.customDialogIds[idIndex]) + this.customDialogIds[idIndex] = null! + }) + } + .height(200) + .padding(5) + .justifyContent(FlexAlign.SpaceBetween) + .backgroundColor('#FFF0F0F0') + } + + @State openInterface1: string = 'openCustomDialog' + private setOpenInterface1: SetParam[] = [{ + title: 'open', + func: () => { + this.openInterface1 = 'openCustomDialog' + } + }, { + title: 'openCtrl', + func: () => { + this.openInterface1 = 'openCustomDialogWithController' + } + }, { + title: 'present', + func: () => { + this.openInterface1 = 'presentCustomDialog' + } + }, { + title: 'ctrlOpen', + func: () => { + this.openInterface1 = 'CustomDialogController' + } + }, { + title: 'show', + func: () => { + this.openInterface1 = 'showDialog' + } + }, { + title: 'actionSheet', + func: () => { + this.openInterface1 = 'showActionSheet' + } + }, { + title: 'alertDialog', + func: () => { + this.openInterface1 = 'showAlertDialog' + } + }] + + @State levelOrder1: LevelOrder = LevelOrder.clamp(undefined) + private setLevelOrder1: SetParam[] = [{ + title: '-100000', + func: () => { + this.levelOrder1 = LevelOrder.clamp(-100000) + } + }, { + title: '-5', + func: () => { + this.levelOrder1 = LevelOrder.clamp(-5) + } + }, { + title: 'undefined', + func: () => { + this.levelOrder1 = LevelOrder.clamp(undefined) + } + }, { + title: '1.41', + func: () => { + this.levelOrder1 = LevelOrder.clamp(1.41) + } + }, { + title: '100000', + func: () => { + this.levelOrder1 = LevelOrder.clamp(100000) + } + }] + + @State openInterface2: string = 'openCustomDialog' + private setOpenInterface2: SetParam[] = [{ + title: 'open', + func: () => { + this.openInterface2 = 'openCustomDialog' + } + }, { + title: 'openCtrl', + func: () => { + this.openInterface2 = 'openCustomDialogWithController' + } + }, { + title: 'present', + func: () => { + this.openInterface2 = 'presentCustomDialog' + } + }, { + title: 'ctrlOpen', + func: () => { + this.openInterface2 = 'CustomDialogController' + } + }, { + title: 'show', + func: () => { + this.openInterface2 = 'showDialog' + } + }, { + title: 'actionSheet', + func: () => { + this.openInterface2 = 'showActionSheet' + } + }, { + title: 'alertDialog', + func: () => { + this.openInterface2 = 'showAlertDialog' + } + }] + + @State levelOrder2: LevelOrder = LevelOrder.clamp(undefined) + private setLevelOrder2: SetParam[] = [{ + title: '-100000', + func: () => { + this.levelOrder2 = LevelOrder.clamp(-100000) + } + }, { + title: '-5', + func: () => { + this.levelOrder2 = LevelOrder.clamp(-5) + } + }, { + title: 'undefined', + func: () => { + this.levelOrder2 = LevelOrder.clamp(undefined) + } + }, { + title: '1.41', + func: () => { + this.levelOrder2 = LevelOrder.clamp(1.41) + } + }, { + title: '100000', + func: () => { + this.levelOrder2 = LevelOrder.clamp(100000) + } + }] + + openDialog(title: string, openInterface: string, options: promptAction.BaseDialogOptions) { + switch (openInterface) { + case 'openCustomDialog': { + this.promptAction.openCustomDialog({ + builder: () => { + this.customDialogComponent(title, this.dialogIdIndex) + }, + isModal: options.isModal, + levelOrder: options.levelOrder, + focusable: options.focusable, + showInSubWindow: options.showInSubWindow, + }).then((dialogId: number) => { + this.customDialogIds[this.dialogIdIndex] = dialogId + this.dialogIdIndex++ + }) + break + } + case 'openCustomDialogWithController': { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + let contentNode: ComponentContent = new ComponentContent(this.ctx, wrapBuilder(buildText), + new Params(title, dialogController)); + this.promptAction.openCustomDialogWithController(contentNode, dialogController, options) + break + } + case 'presentCustomDialog': { + let dialogController: promptAction.CommonController = new promptAction.DialogController() + this.promptAction.presentCustomDialog(() => { + this.customDialogComponent(title, this.dialogIdIndex) + }, dialogController, options).then((dialogId: number) => { + this.customDialogIds[this.dialogIdIndex] = dialogId + this.dialogIdIndex++ + }) + break + } + case 'CustomDialogController': { + let customDialogController: CustomDialogController = new CustomDialogController({ + builder: CustomDialogExample({ title: title }), + isModal: options.isModal, + levelOrder: options.levelOrder, + focusable: options.focusable, + showInSubWindow: options.showInSubWindow, + }) + customDialogController.open() + break + } + case 'showDialog': { + this.promptAction.showDialog({ + title: title, + message: '层级: ' + options.levelOrder?.getOrder(), + isModal: options.isModal, + levelOrder: options.levelOrder, + showInSubWindow: options.showInSubWindow, + backgroundColor: '#FFF0F0F0', + buttons: [{ + text: 'button1', + color: '#000000' + }, { + text: 'button2', + color: '#000000' + }] + }) + break + } + case 'showActionSheet': { + this.ctx.showActionSheet({ + title: title, + message: '层级: ' + options.levelOrder?.getOrder(), + isModal: options.isModal, + levelOrder: options.levelOrder, + showInSubWindow: options.showInSubWindow, + backgroundColor: '#FFF0F0F0', + confirm: { + defaultFocus: true, + value: 'Confirm button', + action: () => { + console.info('ActionSheet action') + } + }, + cancel: () => { + console.info('ActionSheet cancel') + }, + sheets: [{ + title: 'apples', + action: () => { + console.info('apples') + } + }, { + title: 'bananas', + action: () => { + console.info('bananas') + } + }] + }) + break + } + case 'showAlertDialog': { + this.ctx.showAlertDialog({ + title: title, + message: '层级: ' + options.levelOrder?.getOrder(), + isModal: options.isModal, + levelOrder: options.levelOrder, + showInSubWindow: options.showInSubWindow, + backgroundColor: '#FFF0F0F0', + confirm: { + defaultFocus: true, + value: 'Confirm button', + action: () => { + console.info('AlertDialog action') + } + }, + cancel: () => { + console.info('AlertDialog cancel') + } + }) + break + } + } + } + + @State isShowMenu1: boolean = false + @State isShowMenu2: boolean = false + @State isShowMenu3: boolean = false + private iconStr: ResourceStr = $r('app.media.app_icon') + private iconStr2: ResourceStr = $r('app.media.app_icon') + @Builder MenuBuilder() { + Menu() { + MenuItem({ startIcon: $r('app.media.app_icon'), content: '菜单选项' }) + MenuItem({ startIcon: $r('app.media.app_icon'), content: '菜单选项' }) + .enabled(false) + MenuItem({ + startIcon: this.iconStr, + content: '菜单选项', + endIcon: this.iconStr2, + }) + MenuItemGroup({ header: '小标题' }) { + MenuItem({ + startIcon: this.iconStr, + content: '菜单选项', + endIcon: this.iconStr2, + }) + MenuItem({ + startIcon: $r('app.media.app_icon'), + content: '菜单选项', + endIcon: this.iconStr2, + }) + } + MenuItem({ + startIcon: this.iconStr, + content: '菜单选项', + }) + } + .onDisAppear(() => { + this.isShowMenu1 = false + this.isShowMenu2 = false + this.isShowMenu3 = false + }) + } + + @State isShowPopup1: boolean = false + @State isShowPopup2: boolean = false + @State isShowPopup3: boolean = false + private popupOptions: PopupOptions = { + message: 'This is a popup with PopupOptions', + placementOnTop: true, + showInSubWindow: false, + primaryButton: { + value: 'confirm', + action: () => { + console.info('confirm Button click') + this.isShowPopup1 = false + this.isShowPopup2 = false + this.isShowPopup3 = false + } + }, + // 第二个按钮 + secondaryButton: { + value: 'cancel', + action: () => { + console.info('cancel Button click') + this.isShowPopup1 = false + this.isShowPopup2 = false + this.isShowPopup3 = false + } + }, + onStateChange: (e) => { + if (!e.isVisible) { + this.isShowPopup1 = false + this.isShowPopup2 = false + this.isShowPopup3 = false + } + } + } + + @State isShowSheet1: boolean = false + @State isShowSheet2: boolean = false + @State isShowSheet3: boolean = false + private items: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + @Builder SheetBuilder() { + Column() { + // 第一步:自定义滚动容器 + List({ space: '10vp' }) { + ForEach(this.items, (item: number) => { + ListItem() { + Text(String(item)).fontSize(16).fontWeight(FontWeight.Bold) + }.width('90%').height('80vp').backgroundColor('#ff53ecd9').borderRadius(10) + }) + } + .alignListItem(ListItemAlign.Center) + .margin({ top: '10vp' }) + .width('100%') + .height('900px') + // 第二步:设置滚动组件的嵌套滚动属性 + .nestedScroll({ + scrollForward: NestedScrollMode.PARENT_FIRST, + scrollBackward: NestedScrollMode.SELF_FIRST, + }) + + Text('非滚动区域') + .width('100%') + .backgroundColor(Color.Gray) + .layoutWeight(1) + .textAlign(TextAlign.Center) + .align(Alignment.Top) + }.width('100%').height('100%') + } + + @State isShowSheetCover1: boolean = false + @State isShowSheetCover2: boolean = false + @State isShowSheetCover3: boolean = false + @Builder SheetCoverBuilder() { + Column() { + Text('Custom SheetCover').fontSize(16) + Button('点击关闭弹窗') + .onClick(() => { + this.isShowSheetCover1 = false + this.isShowSheetCover2 = false + this.isShowSheetCover3 = false + }) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + } + + toastId: number = 0; + + pathStack: NavPathStack = new NavPathStack() + + build() { + NavDestination() { + Column({ space: 5 }) { + Text('1st Interface: ' + this.openInterface1) + .fontSize(12) + Row({ space: 5 }) { + ForEach(this.setOpenInterface1, (item: SetParam) => { + ParamOption({ title: item.title, func: item.func }) + }, (item: SetParam) => item.title) + } + + Text('1st LevelOrder: ' + this.levelOrder1.getOrder()) + .fontSize(12) + Row({ space: 5 }) { + ForEach(this.setLevelOrder1, (item: SetParam) => { + ParamOption({ title: item.title, func: item.func }) + }, (item: SetParam) => item.title) + } + + Text('2nd Interface: ' + this.openInterface2) + .fontSize(12) + Row({ space: 5 }) { + ForEach(this.setOpenInterface2, (item: SetParam) => { + ParamOption({ title: item.title, func: item.func }) + }, (item: SetParam) => item.title) + } + + Text('2nd LevelOrder: ' + this.levelOrder2.getOrder()) + .fontSize(12) + Row({ space: 5 }) { + ForEach(this.setLevelOrder2, (item: SetParam) => { + ParamOption({ title: item.title, func: item.func }) + }, (item: SetParam) => item.title) + } + + Row() { + Button('弹出弹窗1') + .onClick(() => { + this.openDialog(this.message1, this.openInterface1, { + levelOrder: this.levelOrder1, + isModal: false, + }) + }) + Button('获取层级') + .onClick(() => { + let topOrder: LevelOrder = this.promptAction.getTopOrder(); + if (topOrder === undefined) { + console.error('topOrder: ' + topOrder); + } else { + console.error('topOrder: ' + topOrder.getOrder()); + } + + let bottomOrder: LevelOrder = this.promptAction.getBottomOrder(); + if (bottomOrder === undefined) { + console.error('bottomOrder: ' + bottomOrder); + } else { + console.error('bottomOrder: ' + bottomOrder.getOrder()); + } + }) + Button('弹出弹窗2') + .onClick(() => { + this.openDialog(this.message2, this.openInterface2, { + levelOrder: this.levelOrder2, + isModal: true, + }) + }) + } + + Row() { + Button('Menu+弹窗1+弹窗2') + .fontSize(10) + .bindMenu(this.isShowMenu1, this.MenuBuilder()) + .onClick(() => { + this.isShowMenu1 = !this.isShowMenu1 + setTimeout(() => { + this.openDialog(this.message1, this.openInterface1, { + levelOrder: this.levelOrder1 + }) + }, 3000) + setTimeout(() => { + this.openDialog(this.message2, this.openInterface2, { + levelOrder: this.levelOrder2 + }) + }, 6000) + }) + Button('弹窗1+Menu+弹窗2') + .fontSize(10) + .bindMenu(this.isShowMenu2, this.MenuBuilder()) + .onClick(() => { + this.openDialog(this.message1, this.openInterface1, { + levelOrder: this.levelOrder1 + }) + setTimeout(() => { + this.isShowMenu2 = !this.isShowMenu2 + }, 3000) + setTimeout(() => { + this.openDialog(this.message2, this.openInterface2, { + levelOrder: this.levelOrder2 + }) + }, 6000) + }) + Button('弹窗1+弹窗2+Menu') + .fontSize(10) + .bindMenu(this.isShowMenu3, this.MenuBuilder()) + .onClick(() => { + this.openDialog(this.message1, this.openInterface1, { + levelOrder: this.levelOrder1 + }) + setTimeout(() => { + this.openDialog(this.message2, this.openInterface2, { + levelOrder: this.levelOrder2 + }) + }, 3000) + setTimeout(() => { + this.isShowMenu3 = !this.isShowMenu3 + }, 6000) + }) + } + + Row() { + Button('Popup+弹窗1+弹窗2') + .fontSize(10) + .bindPopup(this.isShowPopup1, this.popupOptions) + .onClick(() => { + this.isShowPopup1 = !this.isShowPopup1 + setTimeout(() => { + this.openDialog(this.message1, this.openInterface1, { + levelOrder: this.levelOrder1 + }) + }, 3000) + setTimeout(() => { + this.openDialog(this.message2, this.openInterface2, { + levelOrder: this.levelOrder2 + }) + }, 6000) + }) + Button('弹窗1+Popup+弹窗2') + .fontSize(10) + .bindPopup(this.isShowPopup2, this.popupOptions) + .onClick(() => { + this.openDialog(this.message1, this.openInterface1, { + levelOrder: this.levelOrder1 + }) + setTimeout(() => { + this.isShowPopup2 = !this.isShowPopup2 + }, 3000) + setTimeout(() => { + this.openDialog(this.message2, this.openInterface2, { + levelOrder: this.levelOrder2 + }) + }, 6000) + }) + Button('弹窗1+弹窗2+Popup') + .fontSize(10) + .bindPopup(this.isShowPopup3, this.popupOptions) + .onClick(() => { + this.openDialog(this.message1, this.openInterface1, { + levelOrder: this.levelOrder1 + }) + setTimeout(() => { + this.openDialog(this.message2, this.openInterface2, { + levelOrder: this.levelOrder2 + }) + }, 3000) + setTimeout(() => { + this.isShowPopup3 = !this.isShowPopup3 + }, 6000) + }) + } + + Row() { + Button('Toast+弹窗1+弹窗2') + .fontSize(10) + .onClick(() => { + this.promptAction.showToast({ + message: 'ok,我是DEFAULT toast', + duration: 10000, + showMode: promptAction.ToastShowMode.DEFAULT, + bottom: 80 + }) + setTimeout(() => { + this.openDialog(this.message1, this.openInterface1, { + levelOrder: this.levelOrder1 + }) + }, 3000) + setTimeout(() => { + this.openDialog(this.message2, this.openInterface2, { + levelOrder: this.levelOrder2 + }) + }, 6000) + }) + Button('弹窗1+Toast+弹窗2') + .fontSize(10) + .onClick(() => { + this.openDialog(this.message1, this.openInterface1, { + levelOrder: this.levelOrder1 + }) + setTimeout(() => { + this.promptAction.showToast({ + message: 'ok,我是DEFAULT toast', + duration: 10000, + showMode: promptAction.ToastShowMode.DEFAULT, + bottom: 80 + }) + }, 3000) + setTimeout(() => { + this.openDialog(this.message2, this.openInterface2, { + levelOrder: this.levelOrder2 + }) + }, 6000) + }) + Button('弹窗1+弹窗2+Toast') + .fontSize(10) + .onClick(() => { + this.openDialog(this.message1, this.openInterface1, { + levelOrder: this.levelOrder1 + }) + setTimeout(() => { + this.openDialog(this.message2, this.openInterface2, { + levelOrder: this.levelOrder2 + }) + }, 3000) + setTimeout(() => { + this.promptAction.showToast({ + message: 'ok,我是DEFAULT toast', + duration: 10000, + showMode: promptAction.ToastShowMode.DEFAULT, + bottom: 80 + }) + }, 6000) + }) + } + + Row() { + Button('Sheet+弹窗1+弹窗2') + .fontSize(10) + .bindSheet(this.isShowSheet1, this.SheetBuilder(), { + detents: [SheetSize.MEDIUM, SheetSize.LARGE, 600], + preferType: SheetType.BOTTOM, + title: { title: '嵌套滚动场景' }, + onDisappear: () => { + this.isShowSheet1 = false + } + }) + .onClick(() => { + this.isShowSheet1 = !this.isShowSheet1 + setTimeout(() => { + this.openDialog(this.message1, this.openInterface1, { + levelOrder: this.levelOrder1 + }) + }, 3000) + setTimeout(() => { + this.openDialog(this.message2, this.openInterface2, { + levelOrder: this.levelOrder2 + }) + }, 6000) + }) + Button('弹窗1+Sheet+弹窗2') + .fontSize(10) + .bindSheet(this.isShowSheet2, this.SheetBuilder(), { + detents: [SheetSize.MEDIUM, SheetSize.LARGE, 600], + preferType: SheetType.BOTTOM, + title: { title: '嵌套滚动场景' }, + onDisappear: () => { + this.isShowSheet2 = false + } + }) + .onClick(() => { + this.openDialog(this.message1, this.openInterface1, { + levelOrder: this.levelOrder1 + }) + setTimeout(() => { + this.isShowSheet2 = !this.isShowSheet2 + }, 3000) + setTimeout(() => { + this.openDialog(this.message2, this.openInterface2, { + levelOrder: this.levelOrder2 + }) + }, 6000) + }) + Button('弹窗1+弹窗2+Sheet' + + '') + .fontSize(10) + .bindSheet(this.isShowSheet3, this.SheetBuilder(), { + detents: [SheetSize.MEDIUM, SheetSize.LARGE, 600], + preferType: SheetType.BOTTOM, + title: { title: '嵌套滚动场景' }, + onDisappear: () => { + this.isShowSheet3 = false + } + }) + .onClick(() => { + this.openDialog(this.message1, this.openInterface1, { + levelOrder: this.levelOrder1 + }) + setTimeout(() => { + this.openDialog(this.message2, this.openInterface2, { + levelOrder: this.levelOrder2 + }) + }, 3000) + setTimeout(() => { + this.isShowSheet3 = !this.isShowSheet3 + }, 6000) + }) + } + + Row() { + Button('SheetCover+弹窗1+弹窗2') + .fontSize(8) + .bindContentCover(this.isShowSheetCover1, this.SheetCoverBuilder(), { + modalTransition: ModalTransition.DEFAULT, + backgroundColor: Color.Pink, + onDisappear: () => { + this.isShowSheetCover1 = false + } + }) + .onClick(() => { + this.isShowSheetCover1 = !this.isShowSheetCover1; + setTimeout(() => { + this.openDialog(this.message1, this.openInterface1, { + levelOrder: this.levelOrder1 + }) + }, 3000) + setTimeout(() => { + this.openDialog(this.message2, this.openInterface2, { + levelOrder: this.levelOrder2 + }) + }, 6000) + }) + Button('弹窗1+SheetCover+弹窗2') + .fontSize(8) + .bindContentCover(this.isShowSheetCover2, this.SheetCoverBuilder(), { + modalTransition: ModalTransition.DEFAULT, + backgroundColor: Color.Pink, + onDisappear: () => { + this.isShowSheetCover2 = false + } + }) + .onClick(() => { + this.openDialog(this.message1, this.openInterface1, { + levelOrder: this.levelOrder1 + }) + setTimeout(() => { + this.isShowSheetCover2 = !this.isShowSheetCover2; + }, 3000) + setTimeout(() => { + this.openDialog(this.message2, this.openInterface2, { + levelOrder: this.levelOrder2 + }) + }, 6000) + }) + Button('弹窗1+弹窗2+SheetCover') + .fontSize(8) + .bindContentCover(this.isShowSheetCover3, this.SheetCoverBuilder(), { + modalTransition: ModalTransition.DEFAULT, + backgroundColor: Color.Pink, + onDisappear: () => { + this.isShowSheetCover3 = false + } + }) + .onClick(() => { + this.openDialog(this.message1, this.openInterface1, { + levelOrder: this.levelOrder1 + }) + setTimeout(() => { + this.openDialog(this.message2, this.openInterface2, { + levelOrder: this.levelOrder2 + }) + }, 3000) + setTimeout(() => { + this.isShowSheetCover3 = !this.isShowSheetCover3; + }, 6000) + }) + } + } + } + .title('DialogLevelOrder') + .onBackPressed(() => { + this.pathStack.pop() + return true + }) + .onReady((context: NavDestinationContext) => { + this.pathStack = context.pathStack + }) + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/dialog/embeddedDialogPageOne.ets b/examples/Overlay/entry/src/main/ets/pages/components/dialog/embeddedDialogPageOne.ets new file mode 100644 index 00000000000..81c9c445927 --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/dialog/embeddedDialogPageOne.ets @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { promptAction, LevelMode, ImmersiveMode, router } from '@kit.ArkUI' + +let customDialogId: number = 0 + +@Builder +export function EmbeddedDialogPageOneBuilder(name: string, param: Object) { + EmbeddedDialogPageOne() +} + +@Builder +function customDialogBuilder() { + Column() { + Text('Custom dialog Message').fontSize(20).height(100) + Row() { + Button('Next').onClick(() => { + // 在弹窗内部进行路由跳转。 + router.pushUrl({url: 'pages/components/dialog/embeddedDialogPageTwo'}) + }) + Blank().width(50) + Button('Close').onClick(() => { + promptAction.closeCustomDialog(customDialogId) + }) + } + }.padding(20) +} + +@Component +export struct EmbeddedDialogPageOne { + @State message: string = 'Hello World' + + @Builder + customDialogComponent() { + customDialogBuilder() + } + + build() { + NavDestination() { + Column() { + Text(this.message).id('test_text') + .fontSize(50) + .fontWeight(FontWeight.Bold) + .onClick(() => { + const node: FrameNode | null = this.getUIContext().getFrameNodeById('test_text') || null; + promptAction.openCustomDialog({ + builder: () => { + this.customDialogComponent() + }, + levelMode: LevelMode.EMBEDDED, // 启用页面级弹出框 + levelUniqueId: node?.getUniqueId(), // 设置页面级弹出框所在页面的任意节点ID + immersiveMode: ImmersiveMode.EXTEND, // 设置页面级弹出框蒙层的显示模式 + }).then((dialogId: number) => { + customDialogId = dialogId + }) + }) + } + .width('100%') + } + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/dialog/embeddedDialogPageTwo.ets b/examples/Overlay/entry/src/main/ets/pages/components/dialog/embeddedDialogPageTwo.ets new file mode 100644 index 00000000000..6e8eb457cbc --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/dialog/embeddedDialogPageTwo.ets @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { router } from '@kit.ArkUI' + +@Entry +@Component +export struct EmbeddedDialogPageTwo { + @State message: string = 'Back' + + build() { + Row() { + Column() { + Button(this.message) + .fontSize(20) + .fontWeight(FontWeight.Bold) + .onClick(() => { + router.back() + }) + } + .width('100%') + }.height('100%') + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/dialog/promptAction.ets b/examples/Overlay/entry/src/main/ets/pages/components/dialog/promptAction.ets new file mode 100644 index 00000000000..17ee27503f7 --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/dialog/promptAction.ets @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { promptAction } from '@kit.ArkUI' + +@Builder +export function PromptActionBuilder(name: string, param: Object) { + PromptActionExample() +} + +@Entry +@Component +struct PromptActionExample { + pathStack: NavPathStack = new NavPathStack() + + build() { + NavDestination() { + Column({ space: 5 }) { + Text('promptAction.showDialog') + .fontColor(Color.Gray) + .width('100%') + .padding(10) + + Button('showDialog') + .onClick(() => { + promptAction.showDialog({ + title: 'Title Info', + message: 'Message Info', + buttons: [ + { + text: 'button1', + color: '#000000' + }, + { + text: 'button2', + color: '#000000' + } + ], + }) + .then(data => { + console.info('showDialog success, click button: ' + data.index); + }) + .catch((err: Error) => { + console.info('showDialog error: ' + err); + }) + }) + + }.width('100%').margin({ top: 5 }) + } + .title('PromptAction') + .onBackPressed(() => { + this.pathStack.pop() + return true + }) + .onReady((context: NavDestinationContext) => { + this.pathStack = context.pathStack; + }) + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/overlay/Index.ets b/examples/Overlay/entry/src/main/ets/pages/components/overlay/Index.ets new file mode 100644 index 00000000000..e13434b7966 --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/overlay/Index.ets @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NavList, NavListItem } from '../../utils/navigationList' + +@Builder +export function OverlayIndexBuilder(name: string, param: Object) { + OverlayIndex() +} + +@Entry +@Component +export struct OverlayIndex { + pathStack: NavPathStack = new NavPathStack() + paths: NavListItem[] = [ + { name: 'OverlayLevelOrder', path: 'OverlayLevelOrder' }, + { name: 'OverlayManagerHitTest', path: 'OverlayManagerHitTest' }, + ] + + build() { + NavDestination() { + NavList({ + pages: this.paths, + onPathChange: (item: NavListItem) => { + this.pathStack.pushPath( { name: item.path } ) + } + }) + } + .title('Overlay') + .onBackPressed(() => { + this.pathStack.pop() + return true + }) + .onReady((context: NavDestinationContext) => { + this.pathStack = context.pathStack; + }) + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/overlay/overlayLevelOrder.ets b/examples/Overlay/entry/src/main/ets/pages/components/overlay/overlayLevelOrder.ets new file mode 100644 index 00000000000..034d6fbd230 --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/overlay/overlayLevelOrder.ets @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentContent, LevelOrder, OverlayManager } from '@kit.ArkUI' +import { ParamOption, SetParam } from '../base/ParamOption' + +class Params { + public text: string = '' + public overlayManager: OverlayManager = new OverlayManager() + public overlayIdx: number = 0 + constructor(text: string, overlayManager: OverlayManager, overlayIdx: number) { + this.text = text + this.overlayManager = overlayManager + this.overlayIdx = overlayIdx + } +} + +let overlayArray: ComponentContent[] = [] +let overlayIndex: number = 0 + +@Builder +function buildText(params: Params) { + Column({ space: 5 }) { + Text(params.text) + .fontSize(30) + .fontWeight(FontWeight.Bold) + .margin({ bottom: 36 }) + Button('点我关闭弹窗') + .onClick(() => { + let componentContent = overlayArray[params.overlayIdx] + params.overlayManager.removeComponentContent(componentContent) + overlayArray[params.overlayIdx] = null! + }) + } + .width(300) + .height(200) + .backgroundColor('#FFF0F0F0') +} + +@Builder +export function OverlayLevelOrderBuilder(name: string, param: Object) { + OverlayLevelOrder() +} + +@Component +export struct OverlayLevelOrder { + private message = '弹窗' + private ctx: UIContext = this.getUIContext() + private overlayManager: OverlayManager = this.ctx.getOverlayManager() + + @State levelOrder: LevelOrder = LevelOrder.clamp(undefined) + private setLevelOrder: SetParam[] = [{ + title: '-100000', + func: () => { + this.levelOrder = LevelOrder.clamp(-100000) + } + }, { + title: '-5', + func: () => { + this.levelOrder = LevelOrder.clamp(-5) + } + }, { + title: 'undefined', + func: () => { + this.levelOrder = LevelOrder.clamp(undefined) + } + }, { + title: '1.41', + func: () => { + this.levelOrder = LevelOrder.clamp(1.41) + } + }, { + title: '100000', + func: () => { + this.levelOrder = LevelOrder.clamp(100000) + } + }] + + pathStack: NavPathStack = new NavPathStack() + + build() { + NavDestination() { + Column({ space: 5 }) { + Text('LevelOrder: ' + this.levelOrder.getOrder()) + .fontSize(12) + Row({ space: 5 }) { + ForEach(this.setLevelOrder, (item: SetParam) => { + ParamOption({ title: item.title, func: item.func }) + }, (item: SetParam) => item.title) + } + + Button('弹出弹窗') + .onClick(() => { + let componentContent = new ComponentContent(this.ctx, wrapBuilder<[Params]>(buildText), + new Params(this.message + ', 层级: ' + this.levelOrder.getOrder(), this.overlayManager, overlayIndex)) + overlayArray[overlayIndex] = componentContent + overlayIndex++ + this.overlayManager.addComponentContentWithOrder(componentContent, this.levelOrder) + }) + } + } + .title('OverlayLevelOrder') + .onBackPressed(() => { + this.pathStack.pop() + return true + }) + .onReady((context: NavDestinationContext) => { + this.pathStack = context.pathStack + }) + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/overlay/overlayManagerHitTest.ets b/examples/Overlay/entry/src/main/ets/pages/components/overlay/overlayManagerHitTest.ets new file mode 100644 index 00000000000..8925e94d329 --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/overlay/overlayManagerHitTest.ets @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentContent, OverlayManager } from '@kit.ArkUI' + +class Params { + public context: UIContext + public offset: Position + public message: string + constructor(context: UIContext, offset: Position, message: string) { + this.context = context + this.offset = offset + this.message = message + } +} +@Builder +function builderOverlay(params: Params) { + Column() { + Stack(){ + }.width(50).height(50).backgroundColor(Color.Yellow).position(params.offset).borderRadius(50) + .onClick(() => {}) + }.focusable(false).width('100%').height('100%').hitTestBehavior(HitTestMode.Transparent) +} + +@Builder +export function OverlayManagerHitBuilder(name: string, param: Object) { + OverlayManageraHitTest() +} + +@Component +export struct OverlayManageraHitTest { + @State message: string = 'ComponentContent'; + private uiContext: UIContext = this.getUIContext() + private overlayNode: OverlayManager = this.uiContext.getOverlayManager() + private overlayContent:ComponentContent[] = [] + controller: TextInputController = new TextInputController() + @State btnColor: Color = Color.Red; + private changeColor: boolean = false; + + + aboutToDisappear(): void { + if (this.overlayContent.length) { + let componentContent = this.overlayContent.pop() + this.overlayNode.removeComponentContent(componentContent) + } + } + + build() { + NavDestination() { + Column() { + Button('show overlay').onClick(() => { + let uiContext = this.getUIContext(); + let componentContent = new ComponentContent( + this.uiContext, wrapBuilder<[Params]>(builderOverlay), + new Params(uiContext, { x: 180, y: 100 }, 'component1') + ) + this.overlayNode.addComponentContent(componentContent, 0) + this.overlayContent.push(componentContent) + }) + Button('click change').onClick(() => { + this.btnColor = this.changeColor ? Color.Red : Color.Blue; + this.changeColor = !this.changeColor; + }).backgroundColor(this.btnColor) + } + .width('100%') + .height('100%') + } + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/toast/Index.ets b/examples/Overlay/entry/src/main/ets/pages/components/toast/Index.ets new file mode 100644 index 00000000000..a00af4b7f90 --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/toast/Index.ets @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NavList, NavListItem } from '../../utils/navigationList' + +@Builder +export function ToastIndexBuilder(name: string, param: Object) { + ToastIndex() +} + +@Entry +@Component +export struct ToastIndex { + pathStack: NavPathStack = new NavPathStack() + paths: NavListItem[] = [ + { name: 'Toast', path: 'Toast' }, + { name: 'showToastTest', path: 'showToastTest' }, + { name: 'toastPositionTest', path: 'toastPositionTest' }, + ] + + build() { + NavDestination() { + NavList({ + pages: this.paths, + onPathChange: (item: NavListItem) => { + this.pathStack.pushPath( { name: item.path } ) + } + }) + } + .title('Toast') + .onBackPressed(() => { + this.pathStack.pop() + return true + }) + .onReady((context: NavDestinationContext) => { + this.pathStack = context.pathStack; + }) + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/toast/showToastTest.ets b/examples/Overlay/entry/src/main/ets/pages/components/toast/showToastTest.ets new file mode 100644 index 00000000000..abb3c6e34fd --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/toast/showToastTest.ets @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import { promptAction } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; + +@Builder +export function ShowToastBuilder(name: string, param: Object) { + toastExample02() +} +@Entry +@Component +struct toastExample02 { + pathStack: NavPathStack = new NavPathStack() + @State toastId: number = 0; + + build() { + NavDestination() { + Column() { + Button('Open Toast') + .height(100) + .type(ButtonType.Capsule) + .onClick(() => { + promptAction.openToast({ + message: 'Toast Massage', + duration: 10000, + }).then((toastId: number) => { + this.toastId = toastId; + }) + .catch((error: BusinessError) => { + console.error(`openToast error code is ${error.code}, message is ${error.message}`) + }) + }) + Blank().height(50); + Button('Close Toast') + .height(100) + .type(ButtonType.Capsule) + .onClick(() => { + try { + promptAction.closeToast(this.toastId); + } catch (error) { + let message = (error as BusinessError).message; + let code = (error as BusinessError).code; + console.error(`CloseToast error code is ${code}, message is ${message}`); + }; + }) + } + .height('100%') + .width('100%') + .justifyContent(FlexAlign.Center) + } + .title('showToastTest') + .onBackPressed(() => { + this.pathStack.pop() + return true + }) + .onReady((context: NavDestinationContext) => { + this.pathStack = context.pathStack; + }) + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/toast/toast.ets b/examples/Overlay/entry/src/main/ets/pages/components/toast/toast.ets new file mode 100644 index 00000000000..0c75707d622 --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/toast/toast.ets @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { promptAction } from '@kit.ArkUI' +import { BusinessError } from '@kit.BasicServicesKit'; + +@Builder +export function ToastBuilder(name: string, param: Object) { + ToastExample() +} + +@Entry +@Component +struct ToastExample { + pathStack: NavPathStack = new NavPathStack() + + build() { + NavDestination() { + Column({ space: 5 }) { + Text('promptAction.showToast') + .fontColor(Color.Gray) + .width('100%') + .padding(10) + + Button('Show toast') + .fontSize(20) + .onClick(() => { + try { + this.getUIContext().getPromptAction().showToast({ + message: 'Hello World', + duration: 2000, + showMode: promptAction.ToastShowMode.TOP_MOST + }); + } catch (error) { + let message = (error as BusinessError).message + let code = (error as BusinessError).code + console.error(`showToast args error code is ${code}, message is ${message}`); + } + }) + }.width('100%').margin({ top: 5 }) + } + .title('Toast') + .onBackPressed(() => { + this.pathStack.pop() + return true + }) + .onReady((context: NavDestinationContext) => { + this.pathStack = context.pathStack; + }) + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/components/toast/toastPositionTest.ets b/examples/Overlay/entry/src/main/ets/pages/components/toast/toastPositionTest.ets new file mode 100644 index 00000000000..cd63b172206 --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/components/toast/toastPositionTest.ets @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { promptAction } from '@kit.ArkUI' +import { BusinessError } from '@kit.BasicServicesKit'; + +@Builder +export function ToastPositionBuilder(name: string, param: Object) { + ToastExample01() +} + +@Extend(Button) function myButtonStyle() { + .fontSize(10) + .padding(5) + .margin(2) + .borderRadius(5) +} + +@Extend(TextPicker) function myTextPicker() { + .width(70) + .height(50) + .selectedTextStyle({font: {size: 12}}) + .canLoop(false) + .textStyle({font: {size: 12}}) + .borderWidth(2) +} + +@Entry +@Component +struct ToastExample01 { + pathStack: NavPathStack = new NavPathStack() + + @State alignment: Alignment | undefined = Alignment.TopStart + alignmentKeys: string[] = ['undefined'].concat(...Object.keys(Alignment).slice(9)) + alignments: (Alignment | undefined)[] = [ + undefined, + Alignment.TopStart, + Alignment.Top, + Alignment.TopEnd, + Alignment.Start, + Alignment.Center, + Alignment.End, + Alignment.BottomStart, + Alignment.Bottom, + Alignment.BottomEnd + ] + + @State alignIdx: number = 0 + @State showMode: promptAction.ToastShowMode = promptAction.ToastShowMode.DEFAULT + @State toastBackgroundColor: Color | undefined = undefined + @State toastTextColor: Color | undefined = undefined + + aboutToAppear(): void { + console.log('toast: ', JSON.stringify(this.alignmentKeys)) + } + + build() { + NavDestination() { + Scroll() { + Column() { + Row() { + Text('TextColor: ') + TextPicker({ + range: [ + 'undefined', + 'Green', + 'Yellow', + 'White', + 'Orange' + ], + selected: 0 + }) + .myTextPicker() + .onChange((value: string | string[]) => { + switch (value) { + case 'Green': + this.toastTextColor = Color.Green + break + case 'Yellow': + this.toastTextColor = Color.Yellow + break + case 'White': + this.toastTextColor = Color.White + break + case 'Orange': + this.toastTextColor = Color.Orange + break + case 'undefined': + this.toastTextColor = undefined + break + } + }) + } + Row() { + Text('backgroundColor: ') + TextPicker({ + range: [ + 'undefined', + 'Red', + 'Blue', + 'Pink', + 'Black', + ], + selected: 0 + }) + .myTextPicker() + .onChange((value: string | string[]) => { + switch (value) { + case 'Red': + this.toastBackgroundColor = Color.Red + break + case 'Blue': + this.toastBackgroundColor = Color.Blue + break + case 'Pink': + this.toastBackgroundColor = Color.Pink + break + case 'Black': + this.toastBackgroundColor = Color.Black + break + case 'undefined': + this.toastBackgroundColor = undefined + break + } + }) + } + Row() { + Text('showMode: ') + Button('default') + .myButtonStyle() + .backgroundColor(this.showMode == promptAction.ToastShowMode.DEFAULT ? Color.Brown : undefined) + .onClick(()=>{ + this.showMode = promptAction.ToastShowMode.DEFAULT + }) + Button('top_most') + .myButtonStyle() + .backgroundColor(this.showMode == promptAction.ToastShowMode.TOP_MOST ? Color.Brown : undefined) + .onClick(()=>{ + this.showMode = promptAction.ToastShowMode.TOP_MOST + }) + } + .margin(5) + + Row() { + Text(`对齐方式:${this.alignmentKeys[this.alignIdx]}`) + Button('++1') + .myButtonStyle() + .onClick(()=>{ + this.alignIdx = (this.alignIdx + 1) % this.alignments.length + }) + Button('--1') + .myButtonStyle() + .onClick(()=>{ + this.alignIdx = (this.alignIdx - 1) % this.alignments.length + }) + } + + Button('showToast') + .onClick(()=>{ + try { + promptAction.showToast({ + message: 'Hello World', + duration: 2000, + showMode: this.showMode, + alignment: this.alignments[this.alignIdx], + backgroundColor: this.toastBackgroundColor, + textColor: this.toastTextColor, + backgroundBlurStyle: BlurStyle.NONE + }); + } catch (error) { + let message = (error as BusinessError).message + let code = (error as BusinessError).code + console.error(`showToast args error code is ${code}, message is ${message}`); + }; + }) + } + .width('100%') + .margin({top: 5}) + } + .height('100%') + .border({width: 1, color: Color.Red}) + } + .title('toastPositionTest') + .onBackPressed(() => { + this.pathStack.pop() + return true + }) + .onReady((context: NavDestinationContext) => { + this.pathStack = context.pathStack; + }) + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/navigationPage.ets b/examples/Overlay/entry/src/main/ets/pages/navigationPage.ets new file mode 100644 index 00000000000..cd1e5fcfda2 --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/navigationPage.ets @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NavList, NavListItem } from './utils/navigationList' + +@Builder +export function NavIndexBuilder(name: string, param: Object) { + NavIndex() +} + +@Entry +@Component +struct NavIndex { + pathStack: NavPathStack = new NavPathStack() + paths: NavListItem[] = [ + { name: 'Dialog', path: 'DialogIndex' }, + { name: 'Toast', path: 'ToastIndex' }, + { name: 'Overlay', path: 'OverlayIndex' }, + ] + + build() { + NavDestination() { + NavList({ + pages: this.paths, + onPathChange: (item: NavListItem) => { + this.pathStack.pushPath( { name: item.path } ) + } + }) + } + .title('Overlay') + .height('100%') + .width('100%') + .onBackPressed(() => { + this.pathStack.pop() + return true + }) + .onReady((context: NavDestinationContext) => { + this.pathStack = context.pathStack; + }) + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/ets/pages/utils/navigationList.ets b/examples/Overlay/entry/src/main/ets/pages/utils/navigationList.ets new file mode 100644 index 00000000000..efc65256af2 --- /dev/null +++ b/examples/Overlay/entry/src/main/ets/pages/utils/navigationList.ets @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface NavListItem { + name: string; + path: string; +} + +@Component +export struct NavList { + pages: NavListItem[] = [] + onPathChange: (item: NavListItem) => void = () => {}; + + build() { + Column() { + ForEach(this.pages, (item: NavListItem) => { + Button(item.name) + .stateEffect(true) + .type(ButtonType.Capsule) + .width('80%') + .height(40) + .margin(20) + .onClick(() => this.onPathChange(item)) + }) + } + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/module.json5 b/examples/Overlay/entry/src/main/module.json5 new file mode 100644 index 00000000000..893bd6fe199 --- /dev/null +++ b/examples/Overlay/entry/src/main/module.json5 @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:app_icon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "orientation": "auto_rotation", + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "UIExtensionProvider", + "srcEntry": "./ets/extensionAbility/extensionAbility.ets", + "description": "UIExtensionProvider", + "exported": false, + "type": "sys/commonUI", + "icon": "$media:app_icon" + } + ], + "routerMap": "$profile:route_map" + } +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/resources/base/element/color.json b/examples/Overlay/entry/src/main/resources/base/element/color.json new file mode 100644 index 00000000000..3c712962da3 --- /dev/null +++ b/examples/Overlay/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/resources/base/element/string.json b/examples/Overlay/entry/src/main/resources/base/element/string.json new file mode 100644 index 00000000000..f94595515a9 --- /dev/null +++ b/examples/Overlay/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/resources/base/profile/main_pages.json b/examples/Overlay/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 00000000000..9e81d680722 --- /dev/null +++ b/examples/Overlay/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,7 @@ +{ + "src": [ + "pages/Index", + "extensionAbility/pages/Index", + "pages/components/dialog/embeddedDialogPageTwo" + ] +} diff --git a/examples/Overlay/entry/src/main/resources/base/profile/route_map.json b/examples/Overlay/entry/src/main/resources/base/profile/route_map.json new file mode 100644 index 00000000000..22aa1fc7b9e --- /dev/null +++ b/examples/Overlay/entry/src/main/resources/base/profile/route_map.json @@ -0,0 +1,89 @@ +{ + "routerMap": [ + { + "name": "NavIndex", + "pageSourceFile": "src/main/ets/pages/navigationPage.ets", + "buildFunction": "NavIndexBuilder" + }, + { + "name": "DialogIndex", + "pageSourceFile": "src/main/ets/pages/components/dialog/Index.ets", + "buildFunction": "DialogIndexBuilder" + }, + { + "name": "AlertDialog", + "pageSourceFile": "src/main/ets/pages/components/dialog/alertDialog.ets", + "buildFunction": "AlertDialogBuilder" + }, + { + "name": "ActionSheet", + "pageSourceFile": "src/main/ets/pages/components/dialog/actionSheet.ets", + "buildFunction": "ActionSheetBuilder" + }, + { + "name": "CustomDialog", + "pageSourceFile": "src/main/ets/pages/components/dialog/customDialog.ets", + "buildFunction": "CustomDialogBuilder" + }, + { + "name": "PromptAction", + "pageSourceFile": "src/main/ets/pages/components/dialog/promptAction.ets", + "buildFunction": "PromptActionBuilder" + }, + { + "name": "DialogController", + "pageSourceFile": "src/main/ets/pages/components/dialog/dialogController.ets", + "buildFunction": "DialogControllerBuilder" + }, + { + "name": "DialogLevelOrder", + "pageSourceFile": "src/main/ets/pages/components/dialog/dialogLevelOrder.ets", + "buildFunction": "DialogLevelOrderBuilder" + }, + { + "name": "DialogFocusable", + "pageSourceFile": "src/main/ets/pages/components/dialog/dialogFocusable.ets", + "buildFunction": "DialogFocusableBuilder" + }, + { + "name": "EmbeddedDialogPageOne", + "pageSourceFile": "src/main/ets/pages/components/dialog/embeddedDialogPageOne.ets", + "buildFunction": "EmbeddedDialogPageOneBuilder" + }, + { + "name": "OverlayIndex", + "pageSourceFile": "src/main/ets/pages/components/overlay/Index.ets", + "buildFunction": "OverlayIndexBuilder" + }, + { + "name": "OverlayLevelOrder", + "pageSourceFile": "src/main/ets/pages/components/overlay/overlayLevelOrder.ets", + "buildFunction": "OverlayLevelOrderBuilder" + }, + { + "name": "OverlayManagerHitTest", + "pageSourceFile": "src/main/ets/pages/components/overlay/overlayManagerHitTest.ets", + "buildFunction": "OverlayManagerHitBuilder" + }, + { + "name": "ToastIndex", + "pageSourceFile": "src/main/ets/pages/components/toast/Index.ets", + "buildFunction": "ToastIndexBuilder" + }, + { + "name": "Toast", + "pageSourceFile": "src/main/ets/pages/components/toast/toast.ets", + "buildFunction": "ToastBuilder" + }, + { + "name": "showToastTest", + "pageSourceFile": "src/main/ets/pages/components/toast/showToastTest.ets", + "buildFunction": "ShowToastBuilder" + }, + { + "name": "toastPositionTest", + "pageSourceFile": "src/main/ets/pages/components/toast/toastPositionTest.ets", + "buildFunction": "ToastPositionBuilder" + } + ] +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/resources/en_US/element/string.json b/examples/Overlay/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 00000000000..f94595515a9 --- /dev/null +++ b/examples/Overlay/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/main/resources/zh_CN/element/string.json b/examples/Overlay/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 00000000000..597ecf95e61 --- /dev/null +++ b/examples/Overlay/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/mock/mock-config.json5 b/examples/Overlay/entry/src/mock/mock-config.json5 new file mode 100644 index 00000000000..b9a78e20153 --- /dev/null +++ b/examples/Overlay/entry/src/mock/mock-config.json5 @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/ohosTest/ets/test/Ability.test.ets b/examples/Overlay/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 00000000000..0f8ce9a2c01 --- /dev/null +++ b/examples/Overlay/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/ohosTest/ets/test/List.test.ets b/examples/Overlay/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 00000000000..1eac52fcebe --- /dev/null +++ b/examples/Overlay/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/ohosTest/module.json5 b/examples/Overlay/entry/src/ohosTest/module.json5 new file mode 100644 index 00000000000..6b9889e8ccb --- /dev/null +++ b/examples/Overlay/entry/src/ohosTest/module.json5 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/examples/Overlay/entry/src/test/List.test.ets b/examples/Overlay/entry/src/test/List.test.ets new file mode 100644 index 00000000000..f1186b1f53c --- /dev/null +++ b/examples/Overlay/entry/src/test/List.test.ets @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/examples/Overlay/entry/src/test/LocalUnit.test.ets b/examples/Overlay/entry/src/test/LocalUnit.test.ets new file mode 100644 index 00000000000..7fc57c77dbf --- /dev/null +++ b/examples/Overlay/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/examples/Overlay/hvigor/hvigor-config.json5 b/examples/Overlay/hvigor/hvigor-config.json5 new file mode 100644 index 00000000000..43beb743cbd --- /dev/null +++ b/examples/Overlay/hvigor/hvigor-config.json5 @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "modelVersion": "5.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ + } +} diff --git a/examples/Overlay/hvigorfile.ts b/examples/Overlay/hvigorfile.ts new file mode 100644 index 00000000000..2a5e543f190 --- /dev/null +++ b/examples/Overlay/hvigorfile.ts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/examples/Overlay/oh-package-lock.json5 b/examples/Overlay/oh-package-lock.json5 new file mode 100644 index 00000000000..aa23300ef31 --- /dev/null +++ b/examples/Overlay/oh-package-lock.json5 @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "meta": { + "stableOrder": true + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", + "@ohos/hypium@1.0.19": "@ohos/hypium@1.0.19" + }, + "packages": { + "@ohos/hamock@1.0.0": { + "name": "@ohos/hamock", + "version": "1.0.0", + "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", + "resolved": "https://repo.harmonyos.com/ohpm/@ohos/hamock/-/hamock-1.0.0.har", + "registryType": "ohpm" + }, + "@ohos/hypium@1.0.19": { + "name": "@ohos/hypium", + "version": "1.0.19", + "integrity": "sha512-cEjDgLFCm3cWZDeRXk7agBUkPqjWxUo6AQeiu0gEkb3J8ESqlduQLSIXeo3cCsm8U/asL7iKjF85ZyOuufAGSQ==", + "resolved": "https://repo.harmonyos.com/ohpm/@ohos/hypium/-/hypium-1.0.19.har", + "registryType": "ohpm" + } + } +} \ No newline at end of file diff --git a/examples/Overlay/oh-package.json5 b/examples/Overlay/oh-package.json5 new file mode 100644 index 00000000000..93f097993a4 --- /dev/null +++ b/examples/Overlay/oh-package.json5 @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "modelVersion": "5.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.19", + "@ohos/hamock": "1.0.0" + } +} -- Gitee