diff --git a/.gitignore b/.gitignore index a1c2a238a965f004ff76978ac1086aa6fe95caea..ed4ba0410d3999b6eb609c3c419c89892f62a4ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,14 @@ -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer +**/oh-package-lock.json5 +**/BuildProfile.ets \ No newline at end of file diff --git a/AppScope/app.json5 b/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7cbe865fd934fd53c068c53791ee9c754d44d4cd --- /dev/null +++ b/AppScope/app.json5 @@ -0,0 +1,11 @@ +{ + "app": { + "bundleName": "com.huawei.hmos.world", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:layered_image", + "label": "$string:app_name", + "debug": false + } +} \ No newline at end of file diff --git a/AppScope/resources/base/element/string.json b/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..28b97069fc2da4fde48f0b5bea0ba3ac99c30284 --- /dev/null +++ b/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "HMOS代码工坊" + } + ] +} diff --git a/AppScope/resources/base/media/ic_background.png b/AppScope/resources/base/media/ic_background.png new file mode 100644 index 0000000000000000000000000000000000000000..2c4ebb884d18fe3f81e83fbbe850e7ddee4dfee9 Binary files /dev/null and b/AppScope/resources/base/media/ic_background.png differ diff --git a/AppScope/resources/base/media/ic_foreground.png b/AppScope/resources/base/media/ic_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..633d7243b55c07163b416bef06b83b630a6696f4 Binary files /dev/null and b/AppScope/resources/base/media/ic_foreground.png differ diff --git a/AppScope/resources/base/media/layered_image.json b/AppScope/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..ab180cdfef55bcd5248e3aef53bad8d621a6443a --- /dev/null +++ b/AppScope/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:ic_background", + "foreground" : "$media:ic_foreground" + } +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index 261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64..338e5b0bc22082e0ffcc7121c2ed3897a3ddccb0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,78 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] + Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved. 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 + 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. + +Apache License, Version 2.0 +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: +1.You must give any other recipients of the Work or Derivative Works a copy of this License; and +2.You must cause any modified files to carry prominent notices stating that You changed the files; and +3.You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +4.If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/README.en.md b/README.en.md index 0436d49e67fec0823cf54cb72e5b4de20eacfe98..d49a2c5612bc8d64ab22d35ab2e880e527c5c6aa 100644 --- a/README.en.md +++ b/README.en.md @@ -1,36 +1 @@ # HMOSWorld_SamplesCollection - -#### Description -「HMOS世界」汇聚官网优质代码案例,覆盖多场景开发需求,通过标准化、模块化的代码实践,帮助开发者快速掌握鸿蒙应用开发技巧,加速项目落地进程,开启鸿蒙开发新征程! - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index 31eadc0193216d8841a03a189a8937f07a39ffe5..c82813278d0caccfeee7991143bcdf4347bccac6 100644 --- a/README.md +++ b/README.md @@ -1,37 +1 @@ -# HMOSWorld_SamplesCollection - -#### 介绍 -「HMOS世界」汇聚官网优质代码案例,覆盖多场景开发需求,通过标准化、模块化的代码实践,帮助开发者快速掌握鸿蒙应用开发技巧,加速项目落地进程,开启鸿蒙开发新征程! - -#### 软件架构 -软件架构说明 - - -#### 安装教程 - -1. xxxx -2. xxxx -3. xxxx - -#### 使用说明 - -1. xxxx -2. xxxx -3. xxxx - -#### 参与贡献 - -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request - - -#### 特技 - -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +# HarmonyOS代码工坊 diff --git a/build-profile.json5 b/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..69efc6a234bd6ba116a5af21823c5b07aa4aaeb6 --- /dev/null +++ b/build-profile.json5 @@ -0,0 +1,85 @@ +{ + "app": { + "signingConfigs": [ + { + "name": "default", + "type": "HarmonyOS", + "material": { + "certpath": "C:\\Users\\dangdang\\.ohos\\config\\default_HMOSWorldClient_New_51U2O-Q0KdEP6D8YDa1qInGjLEz3HXJlQZnNHTFpJ7c=.cer", + "keyAlias": "debugKey", + "keyPassword": "00000019A0D8D0A232BB277D09FBB9C403A55EFE338250E33A3F130B290F76BC5DCBB9C5774B2243D4", + "profile": "C:\\Users\\dangdang\\.ohos\\config\\default_HMOSWorldClient_New_51U2O-Q0KdEP6D8YDa1qInGjLEz3HXJlQZnNHTFpJ7c=.p7b", + "signAlg": "SHA256withECDSA", + "storeFile": "C:\\Users\\dangdang\\.ohos\\config\\default_HMOSWorldClient_New_51U2O-Q0KdEP6D8YDa1qInGjLEz3HXJlQZnNHTFpJ7c=.p12", + "storePassword": "0000001995FE761F7E8E94A31A3BB4B439FF6848EE52D4ECA73020B596826C601EBD47077F9D36184B" + } + } + ], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "5.0.2(14)", + "runtimeOS": "HarmonyOS", + } + ], + "buildModeSet": [ + { + "name": "debug", + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "phone", + "srcPath": "./products/phone", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + }, + { + "name": "mine", + "srcPath": "./features/mine", + }, + { + "name": "exploration", + "srcPath": "./features/exploration", + }, + { + "name": "common", + "srcPath": "./common", + }, + { + "name": "componentlibrary", + "srcPath": "./features/componentlibrary", + }, + { + "name": "devpractices", + "srcPath": "./features/devpractices", + }, + { + "name": "commonbusiness", + "srcPath": "./features/commonbusiness", + }, + { + "name": "wearable", + "srcPath": "./products/wearable", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/common/.gitignore b/common/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/common/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/common/Index.ets b/common/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..4fad1144722febffd12ad3951663206005030156 --- /dev/null +++ b/common/Index.ets @@ -0,0 +1,86 @@ +export { WindowUtil, StatusBarColorType, ScreenOrientation } from './src/main/ets/util/WindowUtil'; + +export { BundleManagerUtil } from './src/main/ets/util/BundleManagerUtil'; + +export { ColorUtil } from './src/main/ets/util/ColorUtil'; + +export { ImageUtil } from './src/main/ets/util/ImageUtil'; + +export { default as Logger } from './src/main/ets/util/Logger'; + +export { GlobalContext } from './src/main/ets/util/GlobalContext'; + +export { ProcessUtil } from './src/main/ets/util/ProcessUtil'; + +export { BreakpointSystem, BreakpointType } from './src/main/ets/util/BreakpointSystem'; + +export { BreakpointTypeEnum, GlobalInfoModel } from './src/main/ets/model/GlobalInfoModel'; + +export { BundleInfoData } from './src/main/ets/model/BundleInfoData'; + +export { ResponseData } from './src/main/ets/model/ResponseData'; + +export { LoadingModel } from './src/main/ets/model/LoadingModel'; + +export { default as MockRequest } from './src/main/ets/storagemanager/MockRequest'; + +export { PreferenceManager } from './src/main/ets/storagemanager/PreferenceManager'; + +export { TopNavigationData, TopNavigationView } from './src/main/ets/component/TopNavigationView'; + +export { WebSheetBuilder, WebUrlType } from './src/main/ets/component/WebSheet'; + +export { LoadingMore } from './src/main/ets/component/LoadingMore'; + +export { NoMore } from './src/main/ets/component/NoMore'; + +export { CommonConstants } from './src/main/ets/constant/CommonConstants'; + +export { + ColumnEnum, + LoadingStatus, + ModuleNameEnum, + ScrollDirectionEnum, + ProductSeriesEnum, +} from './src/main/ets/constant/CommonEnums'; + +export { IPageContext, PageContext } from './src/main/ets/routermanager/PageContext'; + +export { + NativeActionData, + WebUtil, + WebNodeController, + javascriptProxyPermission, +} from './src/main/ets/util/WebUtil'; + +export { VideoComponent } from './src/main/ets/component/VideoComponent'; + +export { LoadingView } from './src/main/ets/view/LoadingView'; + +export { LoadingFailedView } from './src/main/ets/view/LoadingFailedView'; + +export { EmptyContentView } from './src/main/ets/view/EmptyContentView'; + +export { NoNetworkView } from './src/main/ets/view/NoNetworkView'; + +export { ObservedArray } from './src/main/ets/util/ObservedArray'; + +export { RequestErrorCode } from './src/main/ets/constant/ErrorCodeConstants'; + +export { BaseState } from './src/main/ets/viewmodel/BaseState'; + +export { BaseVM } from './src/main/ets/viewmodel/BaseVM'; + +export { BaseVMEvent } from './src/main/ets/viewmodel/BaseVMEvent'; + +export { VibratorUtils } from './src/main/ets/util/VibratorUtils'; + +export { UpdateService } from './src/main/ets/updateservice/UpdateService'; + +export { ConfigMapKey, ResourceUtil } from './src/main/ets/util/ResourceUtil'; + +export { DynamicInstallManager } from './src/main/ets/util/DynamicInstallManager'; + +export { ColorPickerUtil } from './src/main/ets/util/ColorPickerUtil'; + +export { NetworkUtil } from './src/main/ets/util/NetworkUtil'; \ No newline at end of file diff --git a/common/build-profile.json5 b/common/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..697dff23e224373edb713dc2b8a08ed7341d5b4c --- /dev/null +++ b/common/build-profile.json5 @@ -0,0 +1,31 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest" + } + ] +} diff --git a/common/consumer-rules.txt b/common/consumer-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/common/hvigorfile.ts b/common/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..42187071482d292588ad40babeda74f7b8d97a23 --- /dev/null +++ b/common/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/common/obfuscation-rules.txt b/common/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..fdbb5b9852d7dd5f39bddaeb21ab5ee1f3346749 --- /dev/null +++ b/common/obfuscation-rules.txt @@ -0,0 +1,22 @@ +# 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/common/oh-package.json5 b/common/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..538a06bede356ebbbd0036c7645f9bd920d11bff --- /dev/null +++ b/common/oh-package.json5 @@ -0,0 +1,9 @@ +{ + "name": "common", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": {} +} diff --git a/common/src/main/ets/component/LoadingMore.ets b/common/src/main/ets/component/LoadingMore.ets new file mode 100644 index 0000000000000000000000000000000000000000..3b2267d66aaccf71a8f53e1a0aacf0c5769a338a --- /dev/null +++ b/common/src/main/ets/component/LoadingMore.ets @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 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 LoadingMore() { + Row() { + LoadingProgress() + .color($r('sys.color.font_primary')) + .size({ width: $r('sys.float.padding_level12'), height: $r('sys.float.padding_level12') }) + + Text($r('app.string.loading')) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Subtitle_S')) + .margin({ left: $r('sys.float.padding_level4') }) + } + .width('100%') + .height($r('app.float.loading_more_height')) + .justifyContent(FlexAlign.Center) + .margin({ bottom: $r('sys.float.padding_level6') }) +} \ No newline at end of file diff --git a/common/src/main/ets/component/NoMore.ets b/common/src/main/ets/component/NoMore.ets new file mode 100644 index 0000000000000000000000000000000000000000..d467e3b03fe3908aa2604f9297effa7f4082fbc5 --- /dev/null +++ b/common/src/main/ets/component/NoMore.ets @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 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 NoMore() { + Row() { + Divider() + .color($r('sys.color.ohos_id_color_text_field_sub_bg')) + .height(1) + .layoutWeight(1) + Text($r('app.string.no_more')) + .fontColor($r('sys.color.font_secondary')) + .fontSize($r('sys.float.Caption_M')) + .margin($r('sys.float.padding_level6')) + Divider() + .color($r('sys.color.ohos_id_color_text_field_sub_bg')) + .height(1) + .layoutWeight(1) + } + .alignItems(VerticalAlign.Center) + .width('100%') +} \ No newline at end of file diff --git a/common/src/main/ets/component/TopNavigationView.ets b/common/src/main/ets/component/TopNavigationView.ets new file mode 100644 index 0000000000000000000000000000000000000000..205a475484eb10372a9bc9ae8f58e4c0bda54fb4 --- /dev/null +++ b/common/src/main/ets/component/TopNavigationView.ets @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2024 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 { MeasureText } from '@kit.ArkUI'; +import { CommonConstants } from '../constant/CommonConstants'; +import { BreakpointTypeEnum, type GlobalInfoModel } from '../model/GlobalInfoModel'; +import { BreakpointType } from '../util/BreakpointSystem'; + +const BACK_ICON_WIDTH: number = 40; +const TOTAL_PADDING: number = 40; + +/** + * Custom Title Block + */ + +@Component +export struct TopNavigationView { + @StorageProp('GlobalInfoModel') @Watch('calculateTitleSize') globalInfoModel: GlobalInfoModel = + AppStorage.get('GlobalInfoModel')!; + @StorageProp('BlurRenderGroup') blurRenderGroup: boolean = false; + @Prop topNavigationData: TopNavigationData = new TopNavigationData(); + @BuilderParam menuView?: () => void; + @State fontSize: number = 20; + @State backIconBgColor: ResourceColor = + this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? Color.Transparent : + $r('sys.color.comp_background_tertiary'); + + aboutToAppear(): void { + if (this.topNavigationData.fontSize === undefined) { + this.calculateTitleSize(); + } + } + + calculateTitleSize(): void { + const maxWidth = this.globalInfoModel.deviceWidth - BACK_ICON_WIDTH - TOTAL_PADDING; + let currentFontSize: number = this.fontSize; + let titleWidth: number = Math.ceil(px2vp(MeasureText.measureText({ + textContent: this.topNavigationData.title, + fontWeight: FontWeight.Bold, + fontSize: currentFontSize, + }))); + while (currentFontSize > 14 && titleWidth > maxWidth) { + currentFontSize--; + titleWidth = Math.ceil(px2vp(MeasureText.measureText({ + textContent: this.topNavigationData.title, + fontWeight: FontWeight.Bold, + fontSize: currentFontSize, + }))); + } + this.fontSize = currentFontSize; + } + + build() { + Column() { + Row() { + if (this.topNavigationData.onBackClick) { + Button({ type: ButtonType.Circle }) { + SymbolGlyph($r('sys.symbol.chevron_backward')) + .fontColor([$r('sys.color.icon_primary')]) + .fontSize($r('sys.float.Title_M')) + } + .height($r('app.float.back_button_height')) + .aspectRatio(1) + .margin({ right: $r('sys.float.padding_level4') }) + .backgroundColor(this.backIconBgColor) + .onClick(() => this.topNavigationData.onBackClick?.()) + .onHover((isHover: boolean) => { + this.backIconBgColor = isHover ? $r('sys.color.comp_background_tertiary') : Color.Transparent; + }) + } + + Text(this.topNavigationData.title) + .fontSize(this.topNavigationData.fontSize ? this.topNavigationData.fontSize : this.fontSize) + .fontColor(this.topNavigationData.titleColor) + .fontWeight(FontWeight.Bold) + .textAlign(TextAlign.Start) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .layoutWeight(1) + + Row() { + this.menuView?.() + } + } + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.SpaceBetween) + .height(CommonConstants.NAVIGATION_HEIGHT) + .padding(new BreakpointType({ + sm: { + left: $r('sys.float.padding_level8'), + right: $r('sys.float.padding_level8'), + }, + md: { + left: $r('sys.float.padding_level12'), + right: $r('sys.float.padding_level12'), + }, + lg: { + left: $r('sys.float.padding_level16'), + right: $r('sys.float.padding_level16'), + }, + }).getValue(this.globalInfoModel.currentBreakpoint)) + + Divider() + .color($r('sys.color.comp_divider')) + .visibility(this.topNavigationData.isBlur ? Visibility.Visible : Visibility.Hidden) + } + .backgroundBlurStyle(this.topNavigationData.isBlur ? BlurStyle.COMPONENT_THICK : undefined) + .renderGroup(!this.blurRenderGroup && this.globalInfoModel.currentBreakpoint !== BreakpointTypeEnum.XL) + .padding({ top: this.topNavigationData.isFullScreen ? this.globalInfoModel.statusBarHeight : 0 }) + .width('100%') + .backgroundColor(this.topNavigationData.bgColor) + } +} + +@Observed +export class TopNavigationData { + public title: ResourceStr = ''; + public fontSize?: ResourceStr; + public isFullScreen?: boolean = true; + public titleColor?: ResourceStr = $r('sys.color.font_primary'); + public isBlur?: boolean = false; + public bgColor?: ResourceStr | Color = Color.Transparent; + public onBackClick?: Function; +} \ No newline at end of file diff --git a/common/src/main/ets/component/VideoComponent.ets b/common/src/main/ets/component/VideoComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..885f2d00bd2a1903d7667a400d0a0dea64975dd0 --- /dev/null +++ b/common/src/main/ets/component/VideoComponent.ets @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2024 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. + */ + +@Component +export struct VideoComponent { + @Prop mediaSrc: ResourceStr; + @Prop autoPlay: boolean = true; + @Prop loopPlay: boolean = true; + @Prop clickPause: boolean = false; + @Prop startVisibleRatio: number = 0.5; + @State voiceControl: boolean = true; + @State videoPauseShow: boolean = false; + @State voiceShow: boolean = false; + @State triggerValueReplace: number = 0; + private exposureRatio: number[] = [0.0, this.startVisibleRatio, 1.0]; + private videoController: VideoController = new VideoController(); + private timeoutID = setTimeout(() => { + this.voiceShow = false; + }, 3000); + + private visibleAreaJudge(currentRatio: number) { + if (currentRatio >= this.startVisibleRatio) { + if (this.autoPlay) { + this.showVoiceButton(); + this.videoPauseShow = false; + this.videoController.start(); + } + } else { + this.voiceShow = true; + this.videoPauseShow = true; + this.videoController.pause(); + } + } + + private showVoiceButton() { + this.voiceShow = true; + clearTimeout(this.timeoutID); + this.timeoutID = setTimeout(() => { + this.voiceShow = false; + }, 3000); + } + + build() { + Stack({ alignContent: Alignment.Center }) { + Stack({ alignContent: Alignment.TopStart }) { + Video({ src: this.mediaSrc, controller: this.videoController }) + .muted(this.voiceControl) + .objectFit(ImageFit.Contain) + .controls(false) + .onFinish(() => { + if (this.loopPlay) { + this.videoController.start(); + } else { + this.videoPauseShow = true; + this.videoController.pause(); + } + }) + .onTouch(() => { + this.showVoiceButton(); + }) + .onClick(() => { + if (this.clickPause) { + this.videoPauseShow = true; + this.videoController.pause(); + } + }) + .onVisibleAreaChange(this.exposureRatio, (isVisible: boolean, currentRatio: number) => { + this.visibleAreaJudge(currentRatio); + }) + if (this.voiceShow) { + SymbolGlyph(this.voiceControl ? $r('sys.symbol.speaker_slash_fill') : $r('sys.symbol.speaker_fill')) + .fontSize($r('app.float.voice_font_size')) + .fontColor([Color.White]) + .symbolEffect(new BounceSymbolEffect(EffectScope.WHOLE, EffectDirection.UP), this.triggerValueReplace) + .onClick(() => { + this.triggerValueReplace++; + this.voiceControl = !this.voiceControl; + }) + } + } + + if (this.videoPauseShow) { + SymbolGlyph($r('sys.symbol.play_circle')) + .fontSize($r('app.float.video_pause_font_size')) + .fontColor([Color.White]) + .symbolEffect(new BounceSymbolEffect(EffectScope.WHOLE, EffectDirection.UP), this.triggerValueReplace) + .onClick(() => { + this.triggerValueReplace++; + this.videoPauseShow = false; + this.videoController.start(); + }) + } + } + .width('100%') + .height('100%') + } +} \ No newline at end of file diff --git a/common/src/main/ets/component/WebSheet.ets b/common/src/main/ets/component/WebSheet.ets new file mode 100644 index 0000000000000000000000000000000000000000..5d200d1430ac1d63c6aba61176da1f6b2ce9420e --- /dev/null +++ b/common/src/main/ets/component/WebSheet.ets @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024 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 type { GlobalInfoModel } from '../model/GlobalInfoModel'; +import { BreakpointType } from '../util/BreakpointSystem'; +import { WebUtil } from '../util/WebUtil'; +import { LoadingView } from '../view/LoadingView'; +import { NoNetworkView } from '../view/NoNetworkView'; + +export enum WebUrlType { + GITEE = 0, + HARMONYOS = 1, +} + +const GITEE_WEB_BASE_WIDTH: number = 540.00; +const GITEE_TOP_HEIGHT_MD: number = 57; +const GITEE_TOP_HEIGHT_XL: number = 64; +const HARMONYOS_TOP_HEIGHT: number = 56; + +@Component +struct WebSheet { + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @StorageProp('webIsLoading') isLoading: boolean = false; + @Prop url: string; + @Prop urlType: WebUrlType; + @State loadFailed: boolean = false; + + aboutToAppear(): void { + this.checkWebLoaded(); + } + + checkWebLoaded() { + if (!WebUtil.checkWebLoaded(this.url)) { + this.loadFailed = true; + } else { + this.loadFailed = false; + } + } + + build() { + Stack() { + NodeContainer(WebUtil.getWebNode(this.url)) + .width('100%') + .margin({ + top: -(this.urlType === WebUrlType.GITEE ? new BreakpointType({ + sm: (this.globalInfoModel.deviceWidth / GITEE_WEB_BASE_WIDTH) * GITEE_TOP_HEIGHT_XL, + md: GITEE_TOP_HEIGHT_MD, + lg: GITEE_TOP_HEIGHT_MD, + xl: GITEE_TOP_HEIGHT_XL, + }).getValue(this.globalInfoModel.currentBreakpoint) : HARMONYOS_TOP_HEIGHT), + }) + if (this.isLoading) { + LoadingView(this.globalInfoModel.currentBreakpoint) + } else if (this.loadFailed) { + NoNetworkView(this.globalInfoModel.currentBreakpoint, () => { + this.checkWebLoaded(); + }) + } + } + .width('100%') + .height('100%') + } +} + +@Builder +export function WebSheetBuilder(url: string, urlType: WebUrlType) { + Column() { + WebSheet({ url, urlType }) + } +} \ No newline at end of file diff --git a/common/src/main/ets/constant/CommonConstants.ets b/common/src/main/ets/constant/CommonConstants.ets new file mode 100644 index 0000000000000000000000000000000000000000..d5cd934221af3b622e0e3235b039286d9f5be925 --- /dev/null +++ b/common/src/main/ets/constant/CommonConstants.ets @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 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 CommonConstants { + // Banner aspect + public static BANNER_ASPECT_SM: number = 1.28; + public static BANNER_ASPECT_MD: number = 0.53; + public static BANNER_ASPECT_LG: number = 0.53; + public static BANNER_ASPECT_XL: number = 0.56; + public static BANNER_ASPECT_VERDE: number = 0.58; + public static NAVIGATION_HEIGHT: number = 56; + public static TAB_BAR_HEIGHT: number = 48; + public static TAB_BAR_WIDTH: number = 96; + public static SIDE_BAR_WIDTH: number = 240; + // Percent + public static readonly FULL_PERCENT: string = '100%'; + // Space + public static readonly SPACE_2: number = 2; + public static readonly SPACE_4: number = 4; + public static readonly SPACE_6: number = 6; + public static readonly SPACE_8: number = 8; + public static readonly SPACE_10: number = 10; + public static readonly SPACE_12: number = 12; + public static readonly SPACE_16: number = 16; + public static readonly SPACE_24: number = 24; + public static readonly SPACE_32: number = 32; + public static readonly SWIPER_DURATION: number = 3000; + public static readonly PRELOAD_DURATION: number = 1200; + public static readonly REMOVE_DURATION: number = 600; + public static readonly ANIMATION_DELAY: number = 200; + public static readonly ANIMATION_DURATION: number = 300; + public static readonly TRANSITION_DURATION: number = 100; + public static readonly FEEDBACK_BOTTOM_SM: number = 16; + public static readonly SPAN_3: number = 3; + public static readonly SPAN_4: number = 4; + public static readonly SPAN_6: number = 6; + public static readonly SPAN_8: number = 8; + public static readonly SPAN_12: number = 12; + public static readonly LANE_SM: number = 1; + public static readonly LANE_MD: number = 2; + public static readonly LANE_LG: number = 3; + public static readonly LINEAR_GRADIENT_ANGLE: number = 180; + public static readonly DYNAMIC_INSTALL_EVENT: string = 'DynamicInstallEvent'; + public static readonly PROMISE_WAIT = (delay: number) => new Promise((resolve) => setTimeout(resolve, delay)); + public static readonly BANNER_GEOMETRY: string = 'hmos_world_banner_geometry'; + public static readonly CODE_PREVIEW_GEOMETRY_ID: string = 'hmos_world_preview_geometry_id'; + public static readonly SHEET_WIDTH_XL: number = 800; + public static readonly SHEET_HEIGHT_RATIO_XL: number = 0.9; + // Window Property + public static readonly WINDOW_RATIO: number = 0.9; + public static readonly MIN_WINDOW_WIDTH: number = 1440; + public static readonly MIN_WINDOW_HEIGHT: number = 940; +} \ No newline at end of file diff --git a/common/src/main/ets/constant/CommonEnums.ets b/common/src/main/ets/constant/CommonEnums.ets new file mode 100644 index 0000000000000000000000000000000000000000..e2aa3fb37f7544b79df35870f771d3c6f5408711 --- /dev/null +++ b/common/src/main/ets/constant/CommonEnums.ets @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 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. + */ + +/** + * Loading status enum. + */ +export enum LoadingStatus { + IDLE = 'idle', + OFF = 'off', + LOADING = 'loading', + SUCCESS = 'success', + FAILED = 'failed', + NO_NETWORK = 'no_network', +} + +/** + * GridRow column. + */ +export enum ColumnEnum { + SM = 4, + MD = 8, + LG = 12, +} + +/** + * Module name. + */ + +export enum ModuleNameEnum { + COMPONENT_LIST = 'componentListView', + COMPONENT_DETAIL = 'componentDetailView', + CODE_PREVIEW = 'codePreview', + ARTICLE_DETAIL = 'articleDetail', +} + +/** + * Web component scroll direction + */ +export enum ScrollDirectionEnum { + UP = 'up', + DOWN = 'down' +} + +export enum ProductSeriesEnum { + HPR = 'HPR', // fordable PC + VDE = 'VDE', +} \ No newline at end of file diff --git a/common/src/main/ets/constant/ErrorCodeConstants.ets b/common/src/main/ets/constant/ErrorCodeConstants.ets new file mode 100644 index 0000000000000000000000000000000000000000..99622cfe96bae82c55d5273195189967e1b84be3 --- /dev/null +++ b/common/src/main/ets/constant/ErrorCodeConstants.ets @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 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 RequestErrorCode { + /** + * The network seems to have deserted. + */ + public static readonly ERROR_NETWORK_CONNECT_FAILED = 2100002; +} \ No newline at end of file diff --git a/common/src/main/ets/model/BundleInfoData.ets b/common/src/main/ets/model/BundleInfoData.ets new file mode 100644 index 0000000000000000000000000000000000000000..8f5287ed4438706e4ce5204d629a1ff76e5e9342 --- /dev/null +++ b/common/src/main/ets/model/BundleInfoData.ets @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 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 BundleInfoData { + public versionName: string; + public versionCode: number; + public bundleName: string; + + public constructor(versionName?: string, versionCode?: number, bundleName?: string) { + this.versionName = versionName || '1.0.0'; + this.versionCode = versionCode || 1000000; + this.bundleName = bundleName || ''; + } +} \ No newline at end of file diff --git a/common/src/main/ets/model/GlobalInfoModel.ets b/common/src/main/ets/model/GlobalInfoModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..60887606414fe3059f7ac27640f823bcdf031a63 --- /dev/null +++ b/common/src/main/ets/model/GlobalInfoModel.ets @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 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. + */ + +@Observed +export class GlobalInfoModel { + public foldExpanded: boolean = false; + public currentBreakpoint: BreakpointTypeEnum = BreakpointTypeEnum.MD; + public naviIndicatorHeight: number = 0; + public statusBarHeight: number = 0; + public decorHeight: number = 0; + public deviceHeight: number = 0; + public deviceWidth: number = 0; +} + +export enum BreakpointTypeEnum { + XS = 'xs', + SM = 'sm', + MD = 'md', + LG = 'lg', + XL = 'xl', +} \ No newline at end of file diff --git a/common/src/main/ets/model/LoadingModel.ets b/common/src/main/ets/model/LoadingModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..2b94f8f5971df168548f7e0bd20637b53b527719 --- /dev/null +++ b/common/src/main/ets/model/LoadingModel.ets @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 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 { LoadingStatus } from '../constant/CommonEnums'; + +@Observed +export class LoadingModel { + public loadingStatus: LoadingStatus = LoadingStatus.OFF; + public loadingMoreStatus: LoadingStatus = LoadingStatus.OFF; + public hasNextPage: boolean = false; + + public constructor(loadingStatus?: LoadingStatus) { + this.loadingStatus = loadingStatus || LoadingStatus.OFF; + } +} \ No newline at end of file diff --git a/common/src/main/ets/model/ResponseData.ets b/common/src/main/ets/model/ResponseData.ets new file mode 100644 index 0000000000000000000000000000000000000000..93ccc6fb1725cc605c9849eeed2fd4dd3351a71e --- /dev/null +++ b/common/src/main/ets/model/ResponseData.ets @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 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 ResponseData { + /** + * current page number + */ + currentPage: number; + + /** + * Number of resource data on each page + */ + pageSize: number; + + /** + * Total resource data number + */ + totalSize: number; + + /** + * Resource List + */ + data: T; +} \ No newline at end of file diff --git a/common/src/main/ets/routermanager/PageContext.ets b/common/src/main/ets/routermanager/PageContext.ets new file mode 100644 index 0000000000000000000000000000000000000000..9c64c2ccc9e301daba5911196c803ebc193b0052 --- /dev/null +++ b/common/src/main/ets/routermanager/PageContext.ets @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024 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 Logger from '../util/Logger'; + +export interface RouterParam { + routerName: string; + param?: object; +} + +export interface IPageContext { + + openPage(data: RouterParam, animated?: boolean): void; + + popPage(animated?: boolean): void; + + replacePage(data: RouterParam, animated?: boolean): void; +} + +const TAG = '[PageContext]'; + +export class PageContext implements IPageContext { + private readonly pathStack: NavPathStack; + + constructor() { + this.pathStack = new NavPathStack(); + } + + public replacePage(data: RouterParam, animated: boolean = true): void { + try { + this.pathStack.replacePath({ + name: data.routerName, + param: data.param, + }, animated); + } catch (err) { + Logger.error(TAG, `Open Page ${data.routerName} failed. ${err.code} ${err.message}.`); + } + } + + public openPage(data: RouterParam, animated: boolean = true): void { + try { + this.pathStack.pushPath({ + name: data.routerName, + param: data.param, + }, animated); + } catch (err) { + Logger.error(TAG, `Open Page ${data.routerName} failed. ${err.code} ${err.message}.`); + } + } + + public popPage(animated: boolean = true): void { + try { + this.pathStack.pop(animated); + } catch (err) { + Logger.error(TAG, `Pop Page failed. ${err.code} ${err.message}.`); + } + } + + public get navPathStack(): NavPathStack { + return this.pathStack; + } +} \ No newline at end of file diff --git a/common/src/main/ets/storagemanager/MockRequest.ets b/common/src/main/ets/storagemanager/MockRequest.ets new file mode 100644 index 0000000000000000000000000000000000000000..c3ec3e0c8ae28913813c9d36527abe31be6358ca --- /dev/null +++ b/common/src/main/ets/storagemanager/MockRequest.ets @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 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 { util } from '@kit.ArkTS'; +import { BusinessError } from '@kit.BasicServicesKit'; +import Logger from '../util/Logger'; + +const TAG = '[MockRequest]'; + +/** + * Mock data request class. + */ +class MockRequest { + public call(trigger: string): Promise { + return new Promise((resolve: (value: T | PromiseLike) => void, + reject: ((reason?: BusinessError) => void)) => { + try { + const context: Context = getContext(); + const result: Uint8Array = context.resourceManager.getRawFileContentSync(`mockdata/${trigger}.json`); + const textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true }); + const content: string = textDecoder.decodeToString(result, { stream: false }); + const jsonContent: ResultData = JSON.parse(content) as ResultData; + Logger.info(TAG, `GetRawFileContent failed, cause: no ${trigger} json is configured.`); + resolve(jsonContent.data); + } catch (error) { + Logger.error(TAG, `GetRawFileContent failed, error code: ${error.code}, message: ${error.message}.`); + reject(error); + } + }); + } +} + +interface ResultData { + code: number; + data: T; + message: string; +} + +const mockRequest = new MockRequest(); + +export default mockRequest; \ No newline at end of file diff --git a/common/src/main/ets/storagemanager/PreferenceManager.ets b/common/src/main/ets/storagemanager/PreferenceManager.ets new file mode 100644 index 0000000000000000000000000000000000000000..367d3fafc79460a8d77e8e2ac28e845e5560f1f7 --- /dev/null +++ b/common/src/main/ets/storagemanager/PreferenceManager.ets @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2024 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 { preferences } from '@kit.ArkData'; +import { BusinessError } from '@kit.BasicServicesKit'; +import Logger from '../util/Logger'; + +const PREFERENCES_NAME: string = 'HMosWorldStore'; +const TAG: string = '[PreferenceManager]'; + +export class PreferenceManager { + private preferences?: preferences.Preferences; + private static instance: PreferenceManager; + + private constructor() { + this.initPreference(PREFERENCES_NAME); + } + + public static getInstance(): PreferenceManager { + if (!PreferenceManager.instance) { + PreferenceManager.instance = new PreferenceManager(); + } + return PreferenceManager.instance; + } + + private initPreference(storeName: string): Promise { + return preferences.getPreferences(getContext(), storeName) + .then((preferences: preferences.Preferences) => { + this.preferences = preferences; + }) + .catch((err: BusinessError) => { + Logger.error(TAG, `getPreferences failed, err code:${err.code},msg:${err.message}`); + }); + } + + public async setValue(key: string, value: T): Promise { + if (this.preferences) { + this.preferences.put(key, JSON.stringify(value)).then(() => { + this.saveValue(); + }) + } else { + this.initPreference(PREFERENCES_NAME).then(() => { + this.setValue(key, value); + }); + } + } + + public async getValue(key: string): Promise { + if (this.preferences) { + return this.preferences.get(key, '').then((res: preferences.ValueType) => { + let value: T | null = null; + if (res) { + value = JSON.parse(res as string) as T; + } + return value; + }); + } else { + return this.initPreference(PREFERENCES_NAME).then(() => { + return this.getValue(key); + }); + } + } + + public async hasValue(key: string): Promise { + if (this.preferences) { + return this.preferences.has(key); + } else { + return this.initPreference(PREFERENCES_NAME).then(() => { + return this.hasValue(key); + }); + } + } + + public async deleteValue(key: string): Promise { + if (this.preferences) { + this.preferences.delete(key).then(() => { + this.saveValue(); + }); + } else { + this.initPreference(PREFERENCES_NAME).then(() => { + this.deleteValue(key); + }); + } + } + + saveValue() { + this.preferences?.flush(); + } +} \ No newline at end of file diff --git a/common/src/main/ets/updateservice/UpdateService.ets b/common/src/main/ets/updateservice/UpdateService.ets new file mode 100644 index 0000000000000000000000000000000000000000..2414e13b264d61bc2688f73dc202de68acb2b8c7 --- /dev/null +++ b/common/src/main/ets/updateservice/UpdateService.ets @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 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 { updateManager } from '@kit.StoreKit'; +import type { common } from '@kit.AbilityKit'; +import type { BusinessError } from '@kit.BasicServicesKit'; +import Logger from '../util/Logger'; + +const TAG: string = '[UpdateService]'; + +export class UpdateService { + private static instance: UpdateService; + + private constructor() { + } + + public static getInstance(): UpdateService { + if (!UpdateService.instance) { + UpdateService.instance = new UpdateService(); + return UpdateService.instance; + } + return UpdateService.instance; + } + + public checkUpdate(): Promise { + return new Promise((resolve: Function, reject: Function) => { + try { + const context: common.UIAbilityContext = getContext() as common.UIAbilityContext; + updateManager.checkAppUpdate(context) + .then((checkResult: updateManager.CheckUpdateResult) => { + Logger.info(TAG, `Succeeded in checking Result updateAvailable:` + checkResult.updateAvailable); + if (checkResult.updateAvailable === updateManager.UpdateAvailableCode.LATER_VERSION_EXIST) { + resolve(true); + } else { + resolve(false); + } + }) + .catch((error: BusinessError) => { + Logger.error(TAG, `checkAppUpdate onError.code is ${error.code}, message is ${error.message}`); + reject(false); + }); + } catch (error) { + Logger.error(TAG, `checkAppUpdate onError.code is ${error.code}, message is ${error.message}`); + reject(false); + } + }) + } + + public updateVersion(): Promise { + return new Promise((resolve: Function, reject: Function) => { + const context: common.UIAbilityContext = getContext() as common.UIAbilityContext; + try { + updateManager.showUpdateDialog(context) + .then((resultCode: updateManager.ShowUpdateResultCode) => { + Logger.info(TAG, `Succeeded in showing UpdateDialog resultCode:` + resultCode); + resolve(true); + }) + .catch((error: BusinessError) => { + Logger.error(TAG, `showUpdateDialog onError.code is ${error.code}, message is ${error.message}`); + reject(false); + }); + } catch (error) { + Logger.error(TAG, `showUpdateDialog onError.code is ${error.code}, message is ${error.message}`); + reject(false); + } + }); + } +} \ No newline at end of file diff --git a/common/src/main/ets/util/BreakpointSystem.ets b/common/src/main/ets/util/BreakpointSystem.ets new file mode 100644 index 0000000000000000000000000000000000000000..1fe1b5f3a4bc6a730ea1649156ab9018ea197bf9 --- /dev/null +++ b/common/src/main/ets/util/BreakpointSystem.ets @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2024 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 { display, window } from '@kit.ArkUI'; +import { BusinessError, deviceInfo } from '@kit.BasicServicesKit'; +import { ProductSeriesEnum } from '../constant/CommonEnums'; +import { BreakpointTypeEnum, GlobalInfoModel } from '../model/GlobalInfoModel'; +import Logger from './Logger'; + +const TAG: string = '[BreakpointSystem]'; + +export interface BreakpointTypes { + xs?: T; + sm: T; + md: T; + lg: T; + xl?: T; +} + +export class BreakpointType { + private xs: T; + private sm: T; + private md: T; + private lg: T; + private xl: T; + + public constructor(param: BreakpointTypes) { + this.xs = param.xs ?? param.sm; + this.sm = param.sm; + this.md = param.md; + this.lg = param.lg; + this.xl = param.xl ?? param.lg; + } + + public getValue(currentBreakpoint: string): T { + if (currentBreakpoint === BreakpointTypeEnum.XS) { + return this.xs; + } + if (currentBreakpoint === BreakpointTypeEnum.SM) { + return this.sm; + } + if (currentBreakpoint === BreakpointTypeEnum.MD) { + return this.md; + } + if (currentBreakpoint === BreakpointTypeEnum.XL) { + return this.xl; + } + return this.lg; + } +} + +export class BreakpointSystem { + private static instance: BreakpointSystem; + private currentBreakpoint: BreakpointTypeEnum = BreakpointTypeEnum.MD; + + private constructor() { + } + + public static getInstance(): BreakpointSystem { + if (!BreakpointSystem.instance) { + BreakpointSystem.instance = new BreakpointSystem(); + } + return BreakpointSystem.instance; + } + + public updateCurrentBreakpoint(breakpoint: BreakpointTypeEnum): void { + if (this.currentBreakpoint !== breakpoint) { + this.currentBreakpoint = breakpoint; + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel') || new GlobalInfoModel(); + globalInfoModel.currentBreakpoint = this.currentBreakpoint; + AppStorage.setOrCreate('GlobalInfoModel', globalInfoModel); + } + } + + public onWindowSizeChange(window: window.Window): void { + this.updateWidthBp(window); + } + + public updateWidthBp(window: window.Window): void { + try { + const mainWindow: window.WindowProperties = window.getWindowProperties(); + const windowWidth: number = mainWindow.windowRect.width; + const windowWidthVp = px2vp(windowWidth); + const deviceType = deviceInfo.productSeries; + if (deviceType === ProductSeriesEnum.HPR) { + this.updateCurrentBreakpoint(BreakpointTypeEnum.XL); + return; + } + let widthBp: BreakpointTypeEnum = BreakpointTypeEnum.MD; + if (windowWidthVp < 320) { + widthBp = BreakpointTypeEnum.XS; + } else if (windowWidthVp >= 320 && windowWidthVp < 600) { + widthBp = BreakpointTypeEnum.SM; + } else if (windowWidthVp >= 600 && windowWidthVp < 840) { + widthBp = BreakpointTypeEnum.MD; + } else if (windowWidthVp >= 840 && windowWidthVp < 1440) { + widthBp = BreakpointTypeEnum.LG; + } else { + widthBp = BreakpointTypeEnum.XL; + } + this.updateCurrentBreakpoint(widthBp); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, `Failed to getWindowProperties. Cause: ${err.code}, ${err.message}`); + } + } +} \ No newline at end of file diff --git a/common/src/main/ets/util/BundleManagerUtil.ets b/common/src/main/ets/util/BundleManagerUtil.ets new file mode 100644 index 0000000000000000000000000000000000000000..ef143aad239dbed8aa53913f53ed7d60a7557408 --- /dev/null +++ b/common/src/main/ets/util/BundleManagerUtil.ets @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 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 { bundleManager } from '@kit.AbilityKit'; +import type { BusinessError } from '@kit.BasicServicesKit'; +import Logger from './Logger'; +import { BundleInfoData } from '../model/BundleInfoData'; + +const TAG = '[BundleManagerUtil]'; + +export class BundleManagerUtil { + public static getBundleInfo(): void { + try { + bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT) + .then((bundleInfo: bundleManager.BundleInfo) => { + Logger.debug(TAG, `getBundleInfoForSelf successed. ${bundleInfo}`); + AppStorage.setOrCreate('BundleInfoData', + new BundleInfoData(bundleInfo.versionName, bundleInfo.versionCode, bundleInfo.name)); + }); + } catch (err) { + const error = err as BusinessError; + Logger.error(TAG, `getBundleInfoForSelf failed: code ${error.code}, message ${error.message}`); + } + } +} \ No newline at end of file diff --git a/common/src/main/ets/util/ColorPickerUtil.ets b/common/src/main/ets/util/ColorPickerUtil.ets new file mode 100644 index 0000000000000000000000000000000000000000..83620e736c63ed0bf8823e9a474a684462a9b0ef --- /dev/null +++ b/common/src/main/ets/util/ColorPickerUtil.ets @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024 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 ColorPickerUtil { + private static currentColor: string; + + public static setRgba(red: number, green: number, blue: number, opacity: number): string { + return `rgba(${red},${green},${blue},${opacity})`; + } + + public static getRgbText() { + return ColorPickerUtil.currentColor; + } + + public static getBlockColor(value: number): string { + // Calculate the corresponding color based on the area of the slider. + const colorPercent = value / 100; + let selectedColor: string = ''; + let colorAreaPercent: number = 0; + if (colorPercent >= 0 && colorPercent <= 1 / 6) { + colorAreaPercent = colorPercent * 6; + selectedColor = ColorPickerUtil.setRgba(255, Math.floor(colorAreaPercent * 255), 0, 1.00); + } else if (colorPercent >= 1 / 6 && colorPercent <= 2 / 6) { + colorAreaPercent = (colorPercent - 1 / 6) * 6; + selectedColor = ColorPickerUtil.setRgba(Math.floor(((1 - colorAreaPercent) * 255)), 255, 0, 1.00); + } else if (colorPercent >= 2 / 6 && colorPercent <= 3 / 6) { + colorAreaPercent = (colorPercent - 2 / 6) * 6; + selectedColor = ColorPickerUtil.setRgba(0, 255, Math.floor(colorAreaPercent * 255), 1.00); + } else if (colorPercent >= 3 / 6 && colorPercent <= 4 / 6) { + colorAreaPercent = (colorPercent - 3 / 6) * 6; + selectedColor = ColorPickerUtil.setRgba(0, Math.floor(((1 - colorAreaPercent) * 255)), 255, 1.00); + } else if (colorPercent >= 4 / 6 && colorPercent <= 5 / 6) { + colorAreaPercent = (colorPercent - 4 / 6) * 6; + selectedColor = ColorPickerUtil.setRgba(Math.floor(colorAreaPercent * 255), 0, 255, 1.00); + } else if (colorPercent >= 5 / 6 && colorPercent <= 6 / 6) { + colorAreaPercent = (colorPercent - 5 / 6) * 6; + selectedColor = ColorPickerUtil.setRgba(255, 0, Math.floor(((1 - colorAreaPercent) * 255)), 1.00); + } + ColorPickerUtil.currentColor = `${selectedColor.substring(4, selectedColor.length - 3)})`; + return selectedColor; + } + + public static getRgb(rgb: string): number[] { + rgb = rgb.substring(5, rgb.length - 1); + const rgbArray = rgb.split(','); + const redArea: number = parseFloat(rgbArray[0]); + const greenArea: number = parseFloat(rgbArray[1]); + const blueArea: number = parseFloat(rgbArray[2]); + return [redArea, greenArea, blueArea]; + } + + public static getColorFromRgb(rgb: string): number { + const rgbArray = ColorPickerUtil.getRgb(rgb); + const redArea: number = rgbArray[0]; + const greenArea: number = rgbArray[1]; + const blueArea: number = rgbArray[2]; + const allColorCount = 255 * 6; + let colorPercent: number = 0.00; + if (redArea === 255 && blueArea === 0) { + colorPercent = greenArea / allColorCount; + } else if (greenArea === 255 && blueArea === 0) { + colorPercent = (redArea + 255) / allColorCount; + } else if (redArea === 0 && greenArea === 255) { + colorPercent = (blueArea + 255 * 2) / allColorCount; + } else if (redArea === 0 && blueArea === 255) { + colorPercent = (greenArea + 255 * 3) / allColorCount; + } else if (greenArea === 0 && blueArea === 255) { + colorPercent = (redArea + 255 * 4) / allColorCount; + } else if (redArea === 255 && greenArea === 0) { + colorPercent = (blueArea + 255 * 5) / allColorCount; + } + return colorPercent * 100; + } +} \ No newline at end of file diff --git a/common/src/main/ets/util/ColorUtil.ets b/common/src/main/ets/util/ColorUtil.ets new file mode 100644 index 0000000000000000000000000000000000000000..f51183162066cdddf81c3dcd6962e700868bb429 --- /dev/null +++ b/common/src/main/ets/util/ColorUtil.ets @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2024 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. + */ + +/** + * Color conversion processing class. + */ +export class ColorUtil { + /** + * Generate an immersive background color for the image. + * @Prop rRGB + * @Prop gRGB + * @Prop bRGB + * @returns + */ + public static getDeepenImmersionColor(rRGB: number, gRGB: number, bRGB: number): number[] { + const hsb: number[] = ColorUtil.rgbToHsb(rRGB, gRGB, bRGB); + const hHSB = hsb[0]; + let sHSB = hsb[1]; + let bHSB = hsb[2]; + sHSB = sHSB + 0.35; + if (bHSB > 0.15) { + bHSB = bHSB - 0.4; + if (bHSB < 0.15) { + bHSB = 0.15; + } + } + return ColorUtil.hsbToRgb(hHSB, sHSB, bHSB); + } + + private static rgbToHsb(rRGB: number, gRGB: number, bRGB: number): [hHsb: number, sHsb: number, bHsb: number] { + const max = Math.max(rRGB, gRGB, bRGB); + const min = Math.min(rRGB, gRGB, bRGB); + const sHSB = max === 0 ? 0 : (max - min) / max; + const bHSB = (max + 10) / 255; + let hHSB = 0; + if (max === rRGB && gRGB >= bRGB) { + hHSB = 60 * (gRGB - bRGB) / (max - min) + 0; + } + if (max === rRGB && gRGB < bRGB) { + hHSB = 60 * (gRGB - bRGB) / (max - min) + 360; + } + if (max === gRGB) { + hHSB = 60 * (bRGB - rRGB) / (max - min) + 120; + } + if (max === bRGB) { + hHSB = 60 * (rRGB - gRGB) / (max - min) + 240; + } + return [hHSB, sHSB, bHSB]; + } + + private static hsbToRgb(hHSB: number, sHSB: number, bHSB: number): number[] { + const i: number = Math.floor((hHSB / 60) % 6); + const f = (hHSB / 60) - i; + const p = bHSB * (1 - sHSB); + const q = bHSB * (1 - f * sHSB); + const t = bHSB * (1 - (1 - f) * sHSB); + let rRGB = bHSB; + let gRGB = t; + let bRGB = p; + switch (i) { + case 0: + rRGB = bHSB; + gRGB = t; + bRGB = p; + break; + case 1: + rRGB = q; + gRGB = bHSB; + bRGB = p; + break; + case 2: + rRGB = p; + gRGB = bHSB; + bRGB = t; + break; + case 3: + rRGB = p; + gRGB = q; + bRGB = bHSB; + break; + case 4: + rRGB = t; + gRGB = p; + bRGB = bHSB; + break; + case 5: + rRGB = bHSB; + gRGB = p; + bRGB = q; + break; + default: + break; + } + return [Math.max(0, Math.floor(rRGB * 255.0)), Math.max(0, Math.floor(gRGB * 255.0)), + Math.max(0, Math.floor(bRGB * 255.0))]; + } +} \ No newline at end of file diff --git a/common/src/main/ets/util/DynamicInstallManager.ets b/common/src/main/ets/util/DynamicInstallManager.ets new file mode 100644 index 0000000000000000000000000000000000000000..3943c044eb6ce425d30f088718e07a816e0f8235 --- /dev/null +++ b/common/src/main/ets/util/DynamicInstallManager.ets @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2024 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 type { common, StartOptions } from '@kit.AbilityKit'; +import { display } from '@kit.ArkUI'; +import { BusinessError, emitter } from '@kit.BasicServicesKit'; +import { moduleInstallManager } from '@kit.StoreKit'; +import Logger from './Logger'; +import { CommonConstants } from '../constant/CommonConstants'; + +const DOWNLOAD_TIMEOUT_LIMIT: number = 1800; +const TAG: string = '[DynamicInstallManager]'; + +export class DynamicInstallManager { + private static aVAbilityList: string[] = [ + 'KnocksharesampleAbility', + 'VideocastsampleAbility', + 'AudiointeractionsampleAbility', + ]; + + public static getModuleStatus(moduleName: string): moduleInstallManager.InstallStatus { + const result: moduleInstallManager.InstalledModule = moduleInstallManager.getInstalledModule(moduleName); + Logger.info(TAG, `getModuleStatus moduleName: ${result.moduleName}, installStatus: ${result.installStatus}`); + return result.installStatus; + } + + public static fetchModule(context: common.UIAbilityContext, + moduleName: string): Promise { + return new Promise((resolve: (value: moduleInstallManager.ModuleInstallSessionState) => void, + reject: (reason?: object) => void) => { + try { + Logger.info(TAG, `fetchModule moduleName: ${moduleName}`); + const myModuleInstallProvider: moduleInstallManager.ModuleInstallProvider = + new moduleInstallManager.ModuleInstallProvider(); + const moduleInstallRequest: moduleInstallManager.ModuleInstallRequest = + myModuleInstallProvider.createModuleInstallRequest(context); + moduleInstallRequest.addModule(moduleName); + moduleInstallManager.fetchModules(moduleInstallRequest) + .then((data: moduleInstallManager.ModuleInstallSessionState) => { + Logger.debug(TAG, `fetchModule success, result: ${JSON.stringify(data)}`); + resolve(data); + }); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, `request installing module failed, error: ${err.code} ${err.message}`); + reject(error); + } + }); + } + + public static cancelDownloadTask(taskId?: string): void { + try { + const rtnCode: moduleInstallManager.ReturnCode = moduleInstallManager.cancelTask(taskId); + Logger.info(TAG, `Succeeded in getting result: ${rtnCode}`); + } catch (error) { + Logger.error(TAG, `cancelTask error code is ${error.code}, message is ${error.message}`); + } + } + + public static subscribeDownloadProgress(): void { + try { + moduleInstallManager.on('moduleInstallStatus', (downloadData: moduleInstallManager.ModuleInstallSessionState) => { + Logger.info(TAG, + `subscribeDownloadProgress downloadsize: ${downloadData.downloadedSize}, totalsize: ${downloadData.totalSize}`); + const eventData: emitter.EventData = { + data: { + 'taskStatus': downloadData.taskStatus, + 'downloadedSize': downloadData.downloadedSize, + 'totalSize': downloadData.totalSize + } + }; + emitter.emit(CommonConstants.DYNAMIC_INSTALL_EVENT, eventData); + }, DOWNLOAD_TIMEOUT_LIMIT); + Logger.info(TAG, 'subscribe download progress success'); + } catch (error) { + Logger.error(TAG, `subscribeDownloadProgress failed, error: ${error.code}, ${error.message}`); + } + } + + public static unsubscribeDownloadProgress(): void { + try { + moduleInstallManager.off('moduleInstallStatus'); + Logger.info(TAG, 'unsubscribe download progress success'); + } catch (error) { + Logger.error(TAG, `onListening error code is ${error.code}, message is ${error.message}`); + } + } + + public static loadModule(context: common.UIAbilityContext, moduleAbility: string): Promise { + return new Promise((resolve: (value: void) => void, reject: (reason?: BusinessError) => void) => { + try { + const isAVAbility: boolean = DynamicInstallManager.aVAbilityList.includes(moduleAbility); + const isSameAbility: boolean = (moduleAbility === AppStorage.get('AVAbilityModule')); + const aVAbilityContext: common.UIAbilityContext = + AppStorage.get('AVAbilityContext') as common.UIAbilityContext; + if (isAVAbility && !isSameAbility && aVAbilityContext) { + aVAbilityContext.terminateSelf(); + AppStorage.delete('AVAbilityContext'); + } + const option: StartOptions = DynamicInstallManager.setStartAbilityProperty(); + context.startAbility({ bundleName: context.abilityInfo.bundleName, abilityName: moduleAbility }, option) + .then(() => { + if (isAVAbility) { + AppStorage.setOrCreate('AVAbilityModule', moduleAbility); + } + Logger.info(TAG, `start ${moduleAbility} success}`); + resolve(); + }) + .catch((error: BusinessError) => { + Logger.error(TAG, + `start ${moduleAbility} failed, error code is ${error.code}, message is ${error.message}`); + reject(error); + }); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, `startAbility failed, error code is ${err.code}, message is ${err.message}`); + } + }); + } + + public static setStartAbilityProperty(): StartOptions { + const displayData = display.getDefaultDisplaySync(); + const windowWidth = displayData.availableWidth * CommonConstants.WINDOW_RATIO; + const windowHeight = displayData.availableHeight * CommonConstants.WINDOW_RATIO; + const windowLeft = (displayData.availableWidth - windowWidth) / 2.0; + const windowTop = (displayData.availableHeight - windowHeight) / 2.0; + const option: StartOptions = { + minWindowWidth: Math.min(px2vp(windowWidth), CommonConstants.MIN_WINDOW_WIDTH), + minWindowHeight: Math.min(px2vp(windowHeight), CommonConstants.MIN_WINDOW_HEIGHT), + windowLeft: windowLeft, + windowTop: windowTop, + windowWidth: windowWidth, + windowHeight: windowHeight, + }; + return option; + } +} \ No newline at end of file diff --git a/common/src/main/ets/util/GlobalContext.ets b/common/src/main/ets/util/GlobalContext.ets new file mode 100644 index 0000000000000000000000000000000000000000..89b5fe43997eb50b86edc9ba5903fe512949e582 --- /dev/null +++ b/common/src/main/ets/util/GlobalContext.ets @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 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 GlobalContext { + private static instance: GlobalContext; + private _objects: Map; + + private constructor() { + this._objects = new Map(); + } + + public static getContext(): GlobalContext { + if (!GlobalContext.instance) { + GlobalContext.instance = new GlobalContext(); + } + return GlobalContext.instance; + } + + public getObject(key: string): Object | undefined { + return this._objects.get(key); + } + + public setObject(key: string, objectClass: Object): void { + this._objects.set(key, objectClass); + } + + public deleteObject(key: string): void { + this._objects.delete(key); + } +} \ No newline at end of file diff --git a/common/src/main/ets/util/ImageUtil.ets b/common/src/main/ets/util/ImageUtil.ets new file mode 100644 index 0000000000000000000000000000000000000000..838f31c420d2c9da8224121bb48cbe78add128e6 --- /dev/null +++ b/common/src/main/ets/util/ImageUtil.ets @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 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 type { BusinessError } from '@kit.BasicServicesKit'; +import { PreferenceManager } from '../storagemanager/PreferenceManager'; +import { ColorUtil } from './ColorUtil'; +import Logger from './Logger'; +import { ResourceUtil } from './ResourceUtil'; + +const BANNER_IMAGE_COLOR = 'banner_image_color'; +const TAG = '[ImageUtil]'; + +export class ImageUtil { + public static getColorFromImgUrl(imgUrl: string, isDeepColor?: boolean): Promise { + return new Promise((resolve: (value: number[]) => void, reject: (reason?: Object) => void) => { + ImageUtil.getColorDataByPreference(imgUrl).then((data: number[]) => { + resolve(data); + }).catch(() => { + ImageUtil.getColorByPath(imgUrl, isDeepColor).then((colorData: number[]) => { + resolve(colorData); + }).catch((err: BusinessError) => { + Logger.error(TAG, `Failed to Save ImageData. error ${err.code} ${err.message}`); + reject(); + }); + }); + }); + } + + private static getColorByPath(imageUrl: string, isDeepColor?: boolean): Promise { + return new Promise((resolve: (value: number[]) => void, reject: (reason?: Object) => void) => { + ResourceUtil.getColorDataByPath(imageUrl).then((colors: number[]) => { + let colorArr: number[] = colors; + if (isDeepColor) { + colorArr = ColorUtil.getDeepenImmersionColor(colors[0], colors[1], colors[2]); + } + ImageUtil.setColorDataToPreference(imageUrl, colorArr); + resolve(colorArr); + }).catch((err: BusinessError) => { + Logger.error(TAG, `Failed to getColorDataByPath. error ${err.code} ${err.message}`); + reject(); + }) + }); + } + + private static getColorDataByPreference(imgUrl: string): Promise { + return new Promise((resolve: (value: number[]) => void, reject: (reason?: Object) => void) => { + PreferenceManager.getInstance() + .getValue>(BANNER_IMAGE_COLOR) + .then((resp) => { + if (!resp) { + reject('There is no data in the Preference'); + } + resp = (resp as Record); + const ret = resp[imgUrl]; + if (!ret) { + reject('There is no data in the Preference'); + } + resolve(ret); + }); + }); + } + + private static setColorDataToPreference(imgUrl: string, data: number[]): Promise { + return new Promise((resolve: () => void) => { + PreferenceManager.getInstance().hasValue(BANNER_IMAGE_COLOR) + .then((result) => { + if (result) { + PreferenceManager.getInstance() + .getValue>(BANNER_IMAGE_COLOR) + .then((resp) => { + resp = (resp as Record); + resp[imgUrl] = data; + PreferenceManager.getInstance().setValue(BANNER_IMAGE_COLOR, resp); + resolve(); + }) + } else { + const record: Record = {}; + record[imgUrl] = data; + PreferenceManager.getInstance().setValue(BANNER_IMAGE_COLOR, record); + } + }); + }); + } +} \ No newline at end of file diff --git a/common/src/main/ets/util/Logger.ets b/common/src/main/ets/util/Logger.ets new file mode 100644 index 0000000000000000000000000000000000000000..d0de20ca7f7cbff89e8b1cd2efcf4ad61c4bc2e7 --- /dev/null +++ b/common/src/main/ets/util/Logger.ets @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 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'; + +class Logger { + private domain: number; + private prefix: string; + private format: string = '%{public}s, %{public}s'; + + public constructor(prefix: string) { + this.prefix = prefix; + this.domain = 0xFF00; + } + + public debug(...args: Object[]): void { + hilog.debug(this.domain, this.prefix, this.format, args); + } + + public info(...args: Object[]): void { + hilog.info(this.domain, this.prefix, this.format, args); + } + + public warn(...args: Object[]): void { + hilog.warn(this.domain, this.prefix, this.format, args); + } + + public error(...args: Object[]): void { + hilog.error(this.domain, this.prefix, this.format, args); + } +} + +export default new Logger('[HMOSWorld]'); \ No newline at end of file diff --git a/common/src/main/ets/util/NetworkUtil.ets b/common/src/main/ets/util/NetworkUtil.ets new file mode 100644 index 0000000000000000000000000000000000000000..557e9d0524faeabfc8ffc17611242b87d2bd3540 --- /dev/null +++ b/common/src/main/ets/util/NetworkUtil.ets @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 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 { connection } from '@kit.NetworkKit'; +import Logger from './Logger'; + +const TAG: string = '[NetworkUtil]'; + +export class NetworkUtil { + public static hasDefaultNet(): boolean { + try { + return connection.hasDefaultNetSync(); + } catch (err) { + Logger.error(TAG, `checkDefaultNet Failed. cause: ${err.code}`); + return false; + } + } +} \ No newline at end of file diff --git a/common/src/main/ets/util/ObservedArray.ets b/common/src/main/ets/util/ObservedArray.ets new file mode 100644 index 0000000000000000000000000000000000000000..d8cfc37b43c8534455b1b01198a3fe4dbe1fef39 --- /dev/null +++ b/common/src/main/ets/util/ObservedArray.ets @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 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. + */ + +@Observed +export class ObservedArray extends Array { + public constructor(arg?: T[]) { + if (arg instanceof Array) { + super(...arg); + } else { + super(); + } + } +} \ No newline at end of file diff --git a/common/src/main/ets/util/ProcessUtil.ets b/common/src/main/ets/util/ProcessUtil.ets new file mode 100644 index 0000000000000000000000000000000000000000..587b829e6cb0ed9b8ffd1a9bfe5cf3ccce597bad --- /dev/null +++ b/common/src/main/ets/util/ProcessUtil.ets @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 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 type { common } from '@kit.AbilityKit'; +import type { BusinessError } from '@kit.BasicServicesKit'; +import Logger from './Logger'; + +const TAG = '[ProcessUtil]'; + +export class ProcessUtil { + public static terminateAbility(context: common.UIAbilityContext): void { + try { + context.terminateSelf().then(() => { + Logger.info(TAG, 'terminateSelf succeed'); + }).catch((err: BusinessError) => { + Logger.error(TAG, `terminateSelf failed. Cause ${err.code}, ${err.message}.`); + }); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, `terminateSelf failed error:${err.code}, ${err.message}.`); + } + } + + public static moveAbilityToBackground(context: common.UIAbilityContext): void { + try { + context.moveAbilityToBackground().then(() => { + Logger.info(TAG, 'moveAbilityToBackground succeed'); + }).catch((err: BusinessError) => { + Logger.error(TAG, `moveAbilityToBackground failed, cause ${err.code}, ${err.message}`); + }); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, `moveAbilityToBackground failed error: ${err.code}, ${err.message}`); + } + } +} \ No newline at end of file diff --git a/common/src/main/ets/util/ResourceUtil.ets b/common/src/main/ets/util/ResourceUtil.ets new file mode 100644 index 0000000000000000000000000000000000000000..207f4ec26d34365aa75bbd21468d3c21642f5854 --- /dev/null +++ b/common/src/main/ets/util/ResourceUtil.ets @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2024 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 { effectKit } from '@kit.ArkGraphics2D'; +import { JSON, util } from '@kit.ArkTS'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { image } from '@kit.ImageKit'; +import Logger from './Logger'; + +const TAG: string = '[ResourceUtil]'; + +export class ResourceUtil { + /** + * Obtains the character string corresponding to the specified resource ID. + * + * @param resource resource. + */ + public static getResourceString(context: Context, resource: Resource): string { + if (ResourceUtil.isEmptyObj(resource)) { + Logger.error(TAG, '[getResourceString] resource is empty.'); + return ''; + } + let resourceString: string = ''; + try { + resourceString = context.resourceManager.getStringSync(resource.id); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, `[getResourceString]getStringSync failed, error: ${err.code}, ${err.message}.`); + } + return resourceString; + } + + /** + * Check whether the object is empty. + * + * @param obj Objects to be checked. + * @returns Return true if the object is empty or has no properties; Otherwise return false. + */ + private static isEmptyObj(obj: Object): boolean { + if (obj === null || typeof obj !== 'object') { + return true; + } + return Object.keys(obj).length === 0; + } + + /** + * Get content from the raw file resource "hmos_web_config.json" by key. + * @param context The base context of an ability or an application. + * @param key The Json key value. + * @returns Return the value of the key. + */ + public static getRawFileStringByKey(context: Context, key: ConfigMapKey): string { + const configStr: string | undefined = AppStorage.get(key); + if (configStr) { + return configStr; + } + try { + const result: Uint8Array = context.resourceManager.getRawFileContentSync('hmos_web_config.json'); + const textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true }); + const content: string = textDecoder.decodeToString(result, { stream: false }); + const jsonContent: ConfigMapData = JSON.parse(content) as ConfigMapData; + if (JSON.has(jsonContent, key)) { + const linkUrl: string = ResourceUtil.getDataByKey(jsonContent, key); + AppStorage.setOrCreate(key, linkUrl); + return linkUrl; + } + Logger.error(TAG, `GetRawFileContent failed, cause: no ${key} value is configured.`); + return ''; + } catch (error) { + Logger.error(TAG, `GetRawFileContent failed, error code: ${error.code}, message: ${error.message}.`); + return ''; + } + } + + private static getDataByKey(content: ConfigMapData, key: ConfigMapKey): string { + if (key === ConfigMapKey.GALLERY_URL) { + return content.galleryUrl; + } else if (key === ConfigMapKey.MIIT_URL) { + return content.miitUrl; + } else if (key === ConfigMapKey.WHITELIST) { + return content.whitelist; + } + return ''; + } + + public static getColorDataByPath(mediaUrl: string): Promise { + return new Promise((resolve: (value: number[]) => void, reject: (reason?: Object) => void) => { + getContext().resourceManager.getRawFileContent(mediaUrl) + .then((unit8Array: Uint8Array) => { + let imageSource: image.ImageSource | undefined = undefined; + let pixelMap: image.PixelMap | undefined = undefined; + try { + imageSource = image.createImageSource(unit8Array.buffer.slice(0, unit8Array.buffer.byteLength)); + pixelMap = imageSource.createPixelMapSync({ + desiredPixelFormat: image.PixelMapFormat.RGBA_8888, + }); + effectKit.createColorPicker(pixelMap, (err, colorPicker) => { + if (err) { + Logger.error(TAG, 'Failed to create color picker'); + reject(err); + } else { + const color = colorPicker.getLargestProportionColor(); + resolve([color.red, color.green, color.blue]); + } + }); + } catch (error) { + Logger.error(TAG, `GetRawFileContent failed, error code: ${error.code}, message: ${error.message}.`); + reject(error); + } finally { + imageSource?.release(); + pixelMap?.release(); + } + }).catch((error: BusinessError) => { + Logger.error(TAG, `[getPixelMapByPath] failed, error code: ${error.code}, message: ${error.message}.`); + reject(error); + }); + }); + } +} + +export class ConfigMapData { + public galleryUrl: string = ''; + public miitUrl: string = ''; + public whitelist: string = ''; +} + +export enum ConfigMapKey { + GALLERY_URL = 'galleryUrl', + MIIT_URL = 'miitUrl', + WHITELIST = 'whitelist', +} \ No newline at end of file diff --git a/common/src/main/ets/util/VibratorUtils.ets b/common/src/main/ets/util/VibratorUtils.ets new file mode 100644 index 0000000000000000000000000000000000000000..27b5f52a888b96f52b73327aaa642449e729015e --- /dev/null +++ b/common/src/main/ets/util/VibratorUtils.ets @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 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 { vibrator } from '@kit.SensorServiceKit'; +import type { BusinessError } from '@kit.BasicServicesKit'; +import Logger from './Logger'; + +const TAG: string = '[VibratorUtils]'; + +export class VibratorUtils { + public static startVibration() { + const effect: vibrator.VibrateEffect = { + type: 'preset', + effectId: 'haptic.clock.timer', + count: 1, + intensity: 50, + }; + const attribute: vibrator.VibrateAttribute = { + id: 0, + usage: 'touch', + }; + try { + // Trigger vibrator vibration. + vibrator.startVibration(effect, attribute, (error: BusinessError) => { + if (error) { + Logger.error(TAG, `Failed to start vibration. Code: ${error.code}, message: ${error.message}`); + return; + } + Logger.info(TAG, 'Succeed in starting vibration'); + }); + } catch (err) { + const e: BusinessError = err as BusinessError; + Logger.error(TAG, `An unexpected error occurred. Code: ${e.code}, message: ${e.message}`); + } + } +} \ No newline at end of file diff --git a/common/src/main/ets/util/WebUtil.ets b/common/src/main/ets/util/WebUtil.ets new file mode 100644 index 0000000000000000000000000000000000000000..08d7df31f601d9360ca54ea88667406654ff51b7 --- /dev/null +++ b/common/src/main/ets/util/WebUtil.ets @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2024 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 { uri } from '@kit.ArkTS'; +import { BuilderNode, FrameNode, NodeController, window } from '@kit.ArkUI'; +import { webview } from '@kit.ArkWeb'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { ModuleNameEnum, ScrollDirectionEnum } from '../constant/CommonEnums'; +import Logger from './Logger'; +import { NetworkUtil } from './NetworkUtil'; +import { ConfigMapKey, ResourceUtil } from './ResourceUtil'; + +const TAG: string = '[WebUtil]'; + +const webNodeMap: Map = new Map(); +const webControllerMap: Map = new Map(); +const eventEmitterMap: Map = new Map(); +const sheetEventMap: Map = new Map(); +const pageEventMap: Map = new Map(); +const nodeRequiredMap: Map = new Map(); // Check whether web node is required again. +const webLoadedMap: Map = new Map(); +let currentOffset: number = 0; +let context: UIContext; + +const componentCodeHtml: string = `/codePreview/index.html`; + +interface WebBuilderParam { + webUrl: string; + webController: WebviewController; + nestedScroll: NestedScrollOptions; + nativeActionData: NativeActionData; + module?: ModuleNameEnum; + onlyWhiteMode?: boolean; + verticalScrollBarAccess?: boolean; +} + +@Builder +function webBuilder(param: WebBuilderParam) { + Web({ src: param.webUrl, controller: param.webController }) + .zoomAccess(false) + .fileAccess(true) + .imageAccess(true) + .mixedMode(MixedMode.None) + .verticalScrollBarAccess(param.verticalScrollBarAccess) + .horizontalScrollBarAccess(false) + .cacheMode(CacheMode.Default) + .domStorageAccess(true) + .javaScriptAccess(true) + .javaScriptProxy(param.module === ModuleNameEnum.ARTICLE_DETAIL ? { + object: param.nativeActionData, + name: 'nativeActionData', + methodList: ['webSheet', 'jumpPage'], + controller: param.webController, + permission: javascriptProxyPermission, + } : undefined) + .geolocationAccess(false) + .backgroundColor(Color.Transparent) + .nestedScroll(param.nestedScroll) + .darkMode(param.onlyWhiteMode ? WebDarkMode.Off : WebDarkMode.Auto) + .forceDarkAccess(true) + .allowDrop(null) + .onPageBegin(() => { + param.webController.onActive(); + Logger.debug(TAG, `onPageBegin with url: ${param.webUrl}`); + }) + .onPageEnd(() => { + AppStorage.setOrCreate('webIsLoading', false); + WebUtil.setTrustList(param.webUrl); + Logger.debug(TAG, `onPageEnd with url: ${param.webUrl}`); + }) + .onLoadIntercept((event: OnLoadInterceptEvent) => { + const tempUrl = event.data.getRequestUrl(); + return WebUtil.checkUrl(tempUrl); + }) + .onSslErrorEventReceive((event) => { + Logger.error(TAG, `SSL checked failed, error: ${event.error.toString()}`); + event.handler.handleCancel(); + }) + .onScroll((event) => { + if (param.module && event.yOffset) { + const eventEmitter = eventEmitterMap.get(param.module); + const scrollOffset: number = event.yOffset - currentOffset; + currentOffset = event.yOffset; + if (scrollOffset > 0) { + eventEmitter && eventEmitter(ScrollDirectionEnum.DOWN, currentOffset); + } else if (scrollOffset < 0) { + eventEmitter && eventEmitter(ScrollDirectionEnum.UP, currentOffset); + } + } + }) + .onControllerAttached(() => { + try { + param.webController.onActive(); + const userAgent = `${param.webController.getUserAgent()} Mobile`; + param.webController.setCustomUserAgent(userAgent); + // Setting the local file path that allows cross-domain access. + param.webController.setPathAllowingUniversalAccess([getContext().resourceDir]); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, `Web User-Agent setting error: ${err.code}, ${err.message}.`); + } + }) + .width('100%') + .height('100%') +} + +export class NativeActionData { + public webSheet: (src: string, type: number) => void; + public jumpPage: (type: string, id: number, currentIndex?: number, componentName?: string) => void; + + constructor(url: string) { + this.webSheet = (src: string, type: number) => { + const sheetEvent = sheetEventMap.get(url); + if (sheetEvent !== undefined) { + sheetEvent(src, type); + } + }; + + this.jumpPage = (type: string, id: number, currentIndex?: number, componentName?: string) => { + const sheetEvent = pageEventMap.get(url); + if (sheetEvent !== undefined) { + sheetEvent(type, id, currentIndex, componentName); + } + } + } +} + +export class WebUtil { + public static readonly ARTICLE_WHITE_METHODS: string[] = ['checkPreview()', 'closePreview()']; + + static addNode(url: string) { + webNodeMap.get(url)?.add(); + } + + public static initialize(windowStage: window.WindowStage) { + try { + context = windowStage.getMainWindowSync().getUIContext(); + webNodeMap.clear(); + webControllerMap.clear(); + webview.WebviewController.initializeWebEngine(); + WebUtil.createWebNode(WebUtil.getComponentCodeUrl(), context, undefined, ModuleNameEnum.CODE_PREVIEW, true, + false); + } catch (err) { + Logger.error(TAG, `Initialize failed. Cause: ${err.code} ${err.message}`); + } + } + + public static removeNode(url: string) { + if (nodeRequiredMap.has(url)) { + Logger.info(TAG, `Web Node is Required again, should not dispose: ${url}`); + nodeRequiredMap.delete(url); + return; + } + const webNode = WebUtil.getWebNode(url); + webNode?.disposeNode(); + webLoadedMap.delete(url); + webNodeMap.delete(url); + webControllerMap.delete(url); + Logger.debug(TAG, `removeNode with url: ${url}`); + } + + public static checkWebLoaded(url: string): boolean { + const loaded: boolean = !!webLoadedMap.get(url); + if (!loaded && NetworkUtil.hasDefaultNet()) { + const webController = WebUtil.getWebController(url); + AppStorage.setOrCreate('webIsLoading', true); + webLoadedMap.set(url, true); + webController?.refresh(); + return true; + } + return loaded; + } + + public static createWebNode(webUrl: string, uiContext?: UIContext, nestedScrollMode?: NestedScrollMode, + module?: ModuleNameEnum, onlyWhiteMode?: boolean, verticalScrollBarAccess?: boolean) { + if (webNodeMap.has(webUrl)) { + nodeRequiredMap.set(webUrl, true); + Logger.debug(TAG, `Has web node with url: ${webUrl}, should not create web node.`); + return; + } + if (!context && uiContext) { + context = uiContext; + } + Logger.debug(TAG, `initWebNode with url: ${webUrl}`); + const webNode = new WebNodeController(); + const webController = new webview.WebviewController(); + const webBuilderParam: WebBuilderParam = { + webUrl: webUrl, + webController: webController, + nestedScroll: + { + scrollForward: NestedScrollMode.SELF_FIRST, + scrollBackward: NestedScrollMode.SELF_FIRST, + }, + module: module, + onlyWhiteMode, + nativeActionData: new NativeActionData(webUrl), + verticalScrollBarAccess: verticalScrollBarAccess || false, + }; + if (nestedScrollMode !== undefined) { + webBuilderParam.nestedScroll = { + scrollForward: nestedScrollMode, + scrollBackward: nestedScrollMode, + }; + } + webNode.initWebNode(context, webBuilderParam); + webLoadedMap.set(webUrl, NetworkUtil.hasDefaultNet()); + webControllerMap.set(webUrl, webController); + webNodeMap.set(webUrl, webNode); + } + + public static updateWebNode(webUrl: string, nestedScrollMode?: NestedScrollMode, module?: ModuleNameEnum, + onlyWhiteMode?: boolean, verticalScrollBarAccess?: boolean) { + const webController = webControllerMap.get(webUrl)!; + const webBuilderParam: WebBuilderParam = { + webUrl: webUrl, + webController: webController, + nestedScroll: + { + scrollForward: NestedScrollMode.SELF_FIRST, + scrollBackward: NestedScrollMode.SELF_FIRST, + }, + module: module, + onlyWhiteMode, + nativeActionData: new NativeActionData(webUrl), + verticalScrollBarAccess: verticalScrollBarAccess || false, + }; + if (nestedScrollMode !== undefined) { + webBuilderParam.nestedScroll = { + scrollForward: nestedScrollMode, + scrollBackward: nestedScrollMode, + }; + } + const nodeController: WebNodeController | undefined = webNodeMap.get(webUrl); + if (nodeController) { + nodeController.updateWebNode(webBuilderParam); + } + } + + public static getWebNode(webUrl: string): WebNodeController | undefined { + return webNodeMap.get(webUrl); + } + + public static getWebController(webUrl: string): webview.WebviewController | undefined { + return webControllerMap.get(webUrl); + } + + public static setWebController(webUrl: string, webViewController: webview.WebviewController): void { + webControllerMap.set(webUrl, webViewController); + } + + public static setTrustList(webUrl: string): void { + const webController: webview.WebviewController = webControllerMap.get(webUrl)!; + try { + const whitelist: string = ResourceUtil.getRawFileStringByKey(getContext(), ConfigMapKey.WHITELIST); + webController?.setUrlTrustList(whitelist); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, `Web User-Agent setting error: ${err.code}, ${err.message}.`); + } + } + + public static checkUrl(url: string): boolean { + const tempUri: uri.URI = new uri.URI(url); + const res: uri.URI = tempUri.normalize(); + Logger.debug(TAG, `Web request url : ${res.toString()}`); + return false; + } + + public static registerEmitter(module: ModuleNameEnum, callback: Function) { + eventEmitterMap.set(module, callback); + } + + public static setWebSheetAction(url: string, callback: Function) { + sheetEventMap.set(url, callback); + } + + public static setJumpPageAction(url: string, callback: Function) { + pageEventMap.set(url, callback); + } + + public static getComponentCodeUrl() { + return getContext().resourceDir + componentCodeHtml; + } +} + +export class WebNodeController extends NodeController { + private rootNode: BuilderNode | null = null; + private isRemove: boolean = false; + + makeNode(): FrameNode | null { + if (this.isRemove === true) { + return null; + } + if (this.rootNode) { + return this.rootNode.getFrameNode(); + } + return null; + } + + disposeNode() { + this.rootNode?.dispose(); + } + + remove() { + this.isRemove = true; + this.rebuild(); + this.isRemove = false; + } + + add() { + this.isRemove = false; + this.rebuild(); + } + + initWebNode(uiContext: UIContext, webBuilderParam: WebBuilderParam) { + if (!this.rootNode) { + this.rootNode = new BuilderNode(uiContext); + this.rootNode.dispose(); + this.rootNode.build(wrapBuilder(webBuilder), webBuilderParam); + } + } + + updateWebNode(webBuilderParam: WebBuilderParam) { + if (this.rootNode) { + this.rootNode.update(webBuilderParam); + } + } +} + +export const javascriptProxyPermission = `{ + "javascriptProxyPermission": { + "urlPermissionList": [ + { + "scheme": "resource", + "host": "resfile", + "port": "", + "path": "" + } + ], + "methodList": [ + { + "methodName": "toHref", + "urlPermissionList": [ + { + "scheme": "resource", + "host": "resfile", + "port": "", + "path": "" + } + ] + }, + { + "methodName": "jumpSampleDetail", + "urlPermissionList": [ + { + "scheme": "resource", + "host": "resfile", + "port": "", + "path": "" + } + ] + }, + { + "methodName": "jumpComponentDetail", + "urlPermissionList": [ + { + "scheme": "resource", + "host": "resfile", + "port": "", + "path": "" + } + ] + } + ] + } + }`; \ No newline at end of file diff --git a/common/src/main/ets/util/WindowUtil.ets b/common/src/main/ets/util/WindowUtil.ets new file mode 100644 index 0000000000000000000000000000000000000000..75e90f5a110fc8b877f0bbad6a17af69c03ffb6b --- /dev/null +++ b/common/src/main/ets/util/WindowUtil.ets @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2024 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 type { common } from '@kit.AbilityKit'; +import { AbilityConstant } from '@kit.AbilityKit'; +import { display, window } from '@kit.ArkUI'; +import { BusinessError, deviceInfo } from '@kit.BasicServicesKit'; +import { CommonConstants } from '../constant/CommonConstants'; +import { ProductSeriesEnum } from '../constant/CommonEnums'; +import { GlobalInfoModel } from '../model/GlobalInfoModel'; +import { BreakpointSystem } from './BreakpointSystem'; +import Logger from './Logger'; + +const TAG: string = '[WindowUtil]'; + +export class WindowUtil { + public static updateStatusBarColor(context: common.BaseContext, isDark: boolean): void { + window.getLastWindow(context).then((windowClass: window.Window) => { + try { + windowClass.setWindowSystemBarProperties({ + statusBarContentColor: isDark ? StatusBarColorType.WHITE : StatusBarColorType.BLACK + }).then(() => { + Logger.info(TAG, 'Succeeded in setting the system bar properties.'); + }).catch((err: BusinessError) => { + Logger.error(TAG, `Failed to set the system bar properties. Cause: ${err.code} ${err.message}`); + }); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, `Failed to set the system bar properties. Cause: ${err.code}, ${err.message}`); + } + }).catch((error: BusinessError) => { + Logger.error(TAG, `GetLastWindow failed. code: ${error.code}, message: ${error.message}`); + }); + } + + public static hideTitleBar(windowStage: window.WindowStage) { + windowStage.getMainWindow().then((data: window.Window) => { + try { + if (canIUse('SystemCapability.Window.SessionManager')) { + data.setWindowDecorVisible(false); + data.setWindowDecorHeight(CommonConstants.NAVIGATION_HEIGHT); + } else { + Logger.error(TAG, `setWindowDecorVisible invalid`); + } + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, `Failed to set the visibility of window decor. Cause: ${err.code}, ${err.message}`); + } + }).catch((err: BusinessError) => { + Logger.error(TAG, `Failed to obtain the main window. Cause: ${err.code}, ${err.message}`); + }) + } + + public static requestFullScreen(windowStage: window.WindowStage, context: Context): void { + windowStage.getMainWindow((err: BusinessError, data: window.Window) => { + if (err.code) { + Logger.error(TAG, `Failed to obtain the main window. Cause: ${err.code}, ${err.message}`); + return; + } + Logger.debug(TAG, `Succeeded in obtaining the main window. Data: ${JSON.stringify(data)}`); + const windowClass: window.Window = data; + // Realize the immersive effect. + try { + if (deviceInfo.productSeries === ProductSeriesEnum.HPR) { + WindowUtil.resetWindowSize(windowClass); + } + const promise: Promise = windowClass.setWindowLayoutFullScreen(true); + promise.then(() => { + Logger.info(TAG, 'Succeeded in setting the window layout to full-screen mode.'); + }).catch((err: BusinessError) => { + Logger.error(TAG, + `Failed to set the window layout to full-screen mode. Cause: ${err.code}, ${err.message}`); + }); + WindowUtil.getDeviceSize(context); + } catch { + Logger.error(TAG, 'Failed to set the window layout to full-screen mode. '); + } + }); + } + + private static resetWindowSize(windowClass: window.Window): void { + if (canIUse('SystemCapability.Window.SessionManager')) { + const windowSize: display.Display = display.getDefaultDisplaySync(); + const appWidth: number = windowSize.width * 9 / 10; + const appHeight: number = windowSize.height * 7 / 8; + const windowLimits: window.WindowLimits = { + maxWidth: appWidth, + maxHeight: appHeight, + minWidth: appWidth, + minHeight: appHeight, + } + windowClass.setWindowLimits(windowLimits); + windowClass.moveWindowToAsync(windowSize.width / 20, windowSize.height / 16); + } + } + + private static getDeviceSize(context: Context): void { + // Get device height. + window.getLastWindow(context).then((data: window.Window) => { + try { + const properties = data.getWindowProperties(); + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel') || new GlobalInfoModel(); + globalInfoModel.deviceHeight = px2vp(properties.windowRect.height); + globalInfoModel.deviceWidth = px2vp(properties.windowRect.width); + if (canIUse('SystemCapability.Window.SessionManager')) { + const decorHeight: number = data?.getWindowDecorHeight(); + globalInfoModel.decorHeight = decorHeight; + } + AppStorage.setOrCreate('GlobalInfoModel', globalInfoModel); + } catch (err) { + const error = err as BusinessError; + Logger.error(TAG, `Get and setDeviceSize failed. code: ${error.code}, message: ${error.message}`); + } + }).catch((error: BusinessError) => { + Logger.error(TAG, `GetLastWindow failed. code: ${error.code}, message: ${error.message}`); + }); + } + + public static setMainWindowOrientation(context: Context, orientation: window.Orientation, + onSuccess?: () => void): void { + window.getLastWindow(context).then((windowClass: window.Window) => { + if (windowClass === undefined) { + Logger.error(TAG, `MainWindowClass is undefined.`); + return; + } + try { + // Setting window preferred orientation. + windowClass.setPreferredOrientation(orientation, (err: BusinessError) => { + const errCode = err.code; + if (errCode) { + Logger.error(TAG, `Failed to set window orientation. Cause code: ${err.code}, message: ${err.message}`); + return; + } + onSuccess?.(); + }); + } catch (err) { + const error = err as BusinessError; + Logger.error(TAG, `SetPreferredOrientation failed. code: ${error.code}, message: ${error.message}`); + } + }).catch((error: BusinessError) => { + Logger.error(TAG, `GetLastWindow failed. code: ${error.code}, message: ${error.message}`); + }); + } + + public static setMissionContinueActive(context: common.UIAbilityContext, active: boolean) { + const activeState = active ? AbilityConstant.ContinueState.ACTIVE : AbilityConstant.ContinueState.INACTIVE; + context.setMissionContinueState(activeState).then(() => { + Logger.info(TAG, 'setMissionContinueState success'); + }).catch((err: BusinessError) => { + Logger.error(TAG, `setMissionContinueState failed, code is ${err.code}, message is ${err.message}`); + }); + } + + public static enableFloatWindowRotate(context: Context): void { + window.getLastWindow(context).then((windowClass: window.Window) => { + if (windowClass === undefined) { + Logger.error(TAG, `MainWindowClass is undefined`); + return; + } + try { + if (canIUse('SystemCapability.Window.SessionManager')) { + windowClass.enableLandscapeMultiWindow(); + } else { + Logger.error(TAG, `enableLandscapeMultiWindow invalid`); + } + } catch (err) { + const error = err as BusinessError; + Logger.error(TAG, `enableLandscapeMultiWindow failed. code: ${error.code}, message: ${error.message}`); + } + }).catch((error: BusinessError) => { + Logger.error(TAG, `GetLastWindow failed. code: ${error.code}, message: ${error.message}`); + }); + } + + public static disableFloatWindowRotate(context: Context): void { + window.getLastWindow(context).then((windowClass: window.Window) => { + if (windowClass === undefined) { + Logger.error(TAG, `MainWindowClass is undefined`); + return; + } + try { + if (canIUse('SystemCapability.Window.SessionManager')) { + windowClass.disableLandscapeMultiWindow(); + } else { + Logger.error(TAG, `disableLandscapeMultiWindow invalid`); + } + } catch (err) { + const error = err as BusinessError; + Logger.error(TAG, `disableLandscapeMultiWindow failed. code: ${error.code}, message: ${error.message}`); + } + }).catch((error: BusinessError) => { + Logger.error(TAG, `GetLastWindow failed. code: ${error.code}, message: ${error.message}`); + }); + ; + } + + public static registerBreakPoint(windowStage: window.WindowStage) { + windowStage.getMainWindow((err: BusinessError, data: window.Window) => { + if (err.code) { + Logger.error(TAG, `Failed to get main window: ${err.message}`); + return; + } + try { + BreakpointSystem.getInstance().updateWidthBp(data); + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel') || new GlobalInfoModel(); + const systemAvoidArea: window.AvoidArea = data.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM); + globalInfoModel.statusBarHeight = px2vp(systemAvoidArea.topRect.height); + const bottomArea: window.AvoidArea = data.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR); + globalInfoModel.naviIndicatorHeight = px2vp(bottomArea.bottomRect.height); + AppStorage.setOrCreate('GlobalInfoModel', globalInfoModel); + data.on('windowSizeChange', () => WindowUtil.onWindowSizeChange(data)); + data.on('avoidAreaChange', (avoidAreaOption) => { + if (avoidAreaOption.type === window.AvoidAreaType.TYPE_SYSTEM || + avoidAreaOption.type === window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) { + WindowUtil.setAvoidArea(avoidAreaOption.type, avoidAreaOption.area); + } + }); + } catch (e) { + const error = e as BusinessError; + Logger.error(TAG, `getWindowAvoidArea failed. code: ${error.code}, message: ${error.message}`); + } + }); + } + + // Get status bar height and indicator height. + public static setAvoidArea(type: window.AvoidAreaType, area: window.AvoidArea) { + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel') || new GlobalInfoModel(); + if (type === window.AvoidAreaType.TYPE_SYSTEM) { + globalInfoModel.statusBarHeight = px2vp(area.topRect.height); + } else { + globalInfoModel.naviIndicatorHeight = px2vp(area.bottomRect.height); + } + AppStorage.setOrCreate('GlobalInfoModel', globalInfoModel); + } + + public static onWindowSizeChange(window: window.Window) { + WindowUtil.getDeviceSize(getContext()); + BreakpointSystem.getInstance().onWindowSizeChange(window); + } +} + +export enum StatusBarColorType { + WHITE = '#ffffff', + BLACK = '#E5000000', +} + +export enum ScreenOrientation { + PORTRAIT = 'portrait', + LANDSCAPE = 'landscape', +} \ No newline at end of file diff --git a/common/src/main/ets/view/EmptyContentView.ets b/common/src/main/ets/view/EmptyContentView.ets new file mode 100644 index 0000000000000000000000000000000000000000..efcdd7a037315c365619ac99ae734857121d2ab5 --- /dev/null +++ b/common/src/main/ets/view/EmptyContentView.ets @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 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 { CommonConstants } from '../constant/CommonConstants'; + +@Builder +export function EmptyContentView(imgSrc: Resource, description: ResourceStr) { + Column({ space: CommonConstants.SPACE_8 }) { + Image(imgSrc) + .draggable(false) + .size({ width: $r('app.float.empty_content_image_size'), height: $r('app.float.empty_content_image_size') }) + Text(description) + .fontColor($r('sys.color.ohos_id_color_text_secondary')) + .fontSize($r('sys.float.Body_M')) + } + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Center) + .backgroundColor($r('sys.color.background_secondary')) + .width('100%') + .layoutWeight(1) +} \ No newline at end of file diff --git a/common/src/main/ets/view/LoadingFailedView.ets b/common/src/main/ets/view/LoadingFailedView.ets new file mode 100644 index 0000000000000000000000000000000000000000..6b6abb32d35500a7206f329ecb0dc47f2943c72a --- /dev/null +++ b/common/src/main/ets/view/LoadingFailedView.ets @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 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 { BreakpointTypeEnum } from '../model/GlobalInfoModel'; +import { BreakpointType } from '../util/BreakpointSystem'; + +@Builder +export function LoadingFailedView(breakpoint: BreakpointTypeEnum, handleReload?: () => void) { + Row() { + Column() { + Image($r('app.media.ic_failure')) + .draggable(false) + .width(new BreakpointType({ + sm: $r('app.float.failure_size_sm'), + md: $r('app.float.failure_size_md'), + lg: $r('app.float.failure_size_md'), + }).getValue(breakpoint)) + .aspectRatio(1) + Text($r('app.string.server_error')) + .fontColor($r('sys.color.font_tertiary')) + .fontSize($r('sys.float.Body_M')) + .margin({ top: $r('sys.float.padding_level4') }) + } + } + .onClick(() => handleReload?.()) + .width('100%') + .height('100%') + .backgroundColor($r('sys.color.background_secondary')) + .justifyContent(FlexAlign.Center) +} \ No newline at end of file diff --git a/common/src/main/ets/view/LoadingView.ets b/common/src/main/ets/view/LoadingView.ets new file mode 100644 index 0000000000000000000000000000000000000000..0fe9207173dc1d42f01b64edccd5b156c3b1bad2 --- /dev/null +++ b/common/src/main/ets/view/LoadingView.ets @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 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 { BreakpointTypeEnum } from '../model/GlobalInfoModel'; +import { BreakpointType } from '../util/BreakpointSystem'; + +@Builder +export function LoadingView(breakpoint: BreakpointTypeEnum) { + Column() { + Row() { + LoadingProgress() + } + .width(new BreakpointType({ + sm: $r('app.float.loading_size_sm'), + md: $r('app.float.loading_size_md'), + lg: $r('app.float.loading_size_md'), + }).getValue(breakpoint)) + .aspectRatio(1) + + Text($r('app.string.loading')) + .fontSize($r('sys.float.Body_M')) + .fontColor($r('sys.color.font_secondary')) + .margin($r('sys.float.padding_level12')) + } + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Center) + .backgroundColor($r('sys.color.background_secondary')) + .height('100%') + .width('100%') +} \ No newline at end of file diff --git a/common/src/main/ets/view/NoNetworkView.ets b/common/src/main/ets/view/NoNetworkView.ets new file mode 100644 index 0000000000000000000000000000000000000000..fa5c6f4f77bd9138486815d2961b83eac9191b83 --- /dev/null +++ b/common/src/main/ets/view/NoNetworkView.ets @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024 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 type { common, Want } from '@kit.AbilityKit'; +import type { BusinessError } from '@kit.BasicServicesKit'; +import { CommonConstants } from '../constant/CommonConstants'; +import { ColumnEnum } from '../constant/CommonEnums'; +import { BreakpointTypeEnum } from '../model/GlobalInfoModel'; +import { BreakpointType } from '../util/BreakpointSystem'; +import Logger from '../util/Logger'; + +const TAG = '[NoNetworkView]'; + +@Builder +export function NoNetworkView(breakpoint: BreakpointTypeEnum, handleReload?: () => void) { + GridRow({ columns: { sm: ColumnEnum.SM, md: ColumnEnum.MD, lg: ColumnEnum.LG } }) { + GridCol({ + span: { sm: CommonConstants.SPACE_4, md: CommonConstants.SPAN_6, lg: CommonConstants.SPAN_6 }, + offset: { sm: 0, md: 1, lg: CommonConstants.SPAN_3 }, + }) { + Column() { + Row() + .height($r('app.float.loading_size_sm')) + Column() { + Image($r('app.media.ic_failure')) + .draggable(false) + .width(new BreakpointType({ + sm: $r('app.float.failure_size_sm'), + md: $r('app.float.failure_size_md'), + lg: $r('app.float.failure_size_lg'), + }).getValue(breakpoint)) + .aspectRatio(1) + Text($r('app.string.network_error')) + .fontColor($r('sys.color.font_secondary')) + .fontSize($r('sys.float.Body_M')) + .margin({ top: $r('sys.float.padding_level4') }) + } + + Button($r('app.string.network_setting'), + { buttonStyle: ButtonStyleMode.NORMAL, controlSize: ControlSize.NORMAL }) + .width('100%') + .onClick(() => { + const context: common.UIAbilityContext = getContext() as common.UIAbilityContext; + const want: Want = { + bundleName: 'com.huawei.hmos.settings', + abilityName: 'com.huawei.hmos.settings.MainAbility', + uri: 'wifi_entry', + }; + try { + context.startAbility(want).then(() => { + Logger.info(TAG, `start setting ability succeed. `); + }).catch((err: BusinessError) => { + Logger.error(TAG, `start setting ability failed. ${err.code}, ${err.message}.`); + }); + } catch (err) { + const error = err as BusinessError; + Logger.error(TAG, `StartAbility failed. code: ${error.code}, message: ${error.message}`); + } + }) + .margin({ bottom: $r('app.float.loading_size_sm') }) + } + .onClick(() => handleReload?.()) + .width('100%') + .height('100%') + .padding($r('sys.float.padding_level8')) + .backgroundColor($r('sys.color.background_secondary')) + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.SpaceBetween) + } + } +} \ No newline at end of file diff --git a/common/src/main/ets/viewmodel/BaseState.ets b/common/src/main/ets/viewmodel/BaseState.ets new file mode 100644 index 0000000000000000000000000000000000000000..b16a8b2fe9945865924557fe3c3e79110dfd3cea --- /dev/null +++ b/common/src/main/ets/viewmodel/BaseState.ets @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024 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 BaseState { + +} \ No newline at end of file diff --git a/common/src/main/ets/viewmodel/BaseVM.ets b/common/src/main/ets/viewmodel/BaseVM.ets new file mode 100644 index 0000000000000000000000000000000000000000..43c0f7b58c40cfa6dee7fee5925e93f90c7c53c2 --- /dev/null +++ b/common/src/main/ets/viewmodel/BaseVM.ets @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 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 { BaseState } from './BaseState'; +import { BaseVMEvent } from './BaseVMEvent'; + +export abstract class BaseVM { + protected state: T; + + public constructor(initialState: T) { + this.state = initialState; + } + + getState(): T { + return this.state; + } + + public abstract sendEvent(baseVMEvent: BaseVMEvent); +} \ No newline at end of file diff --git a/common/src/main/ets/viewmodel/BaseVMEvent.ets b/common/src/main/ets/viewmodel/BaseVMEvent.ets new file mode 100644 index 0000000000000000000000000000000000000000..0e46a59e03f0a80d88bd151370bba3a68e0b6adb --- /dev/null +++ b/common/src/main/ets/viewmodel/BaseVMEvent.ets @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024 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 BaseVMEvent { + +} \ No newline at end of file diff --git a/common/src/main/module.json5 b/common/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..64f0e8fb3ff50240a2968537f1552ff35c092863 --- /dev/null +++ b/common/src/main/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "common", + "type": "har", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ] + } +} diff --git a/common/src/main/resources/base/element/float.json b/common/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..98b8e0300676c1527cd195f0a85e96130f7802ef --- /dev/null +++ b/common/src/main/resources/base/element/float.json @@ -0,0 +1,44 @@ +{ + "float": [ + { + "name": "loading_size_sm", + "value": "72vp" + }, + { + "name": "loading_size_md", + "value": "100vp" + }, + { + "name": "failure_size_sm", + "value": "120vp" + }, + { + "name": "failure_size_md", + "value": "160vp" + }, + { + "name": "failure_size_lg", + "value": "180vp" + }, + { + "name": "empty_content_image_size", + "value": "120vp" + }, + { + "name": "loading_more_height", + "value": "80vp" + }, + { + "name": "back_button_height", + "value": "40vp" + }, + { + "name": "voice_font_size", + "value": "28fp" + }, + { + "name": "video_pause_font_size", + "value": "50fp" + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/base/element/string.json b/common/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..63774a3f303709e4039aa24b31640acc8e5732fd --- /dev/null +++ b/common/src/main/resources/base/element/string.json @@ -0,0 +1,24 @@ +{ + "string": [ + { + "name": "loading", + "value": "Loading..." + }, + { + "name": "no_more", + "value": "No more resources" + }, + { + "name": "network_error", + "value": "The network connection is interrupted. Please check your network setting or try again later." + }, + { + "name": "server_error", + "value": "Failed to connect to the server, Please try again later." + }, + { + "name": "network_setting", + "value": "Setting up the network." + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/base/media/ic_failure.svg b/common/src/main/resources/base/media/ic_failure.svg new file mode 100644 index 0000000000000000000000000000000000000000..7175f281b94003406106a0e6da8c1fd803924a9e --- /dev/null +++ b/common/src/main/resources/base/media/ic_failure.svg @@ -0,0 +1,30 @@ + + + png_net_fail + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/src/main/resources/en_US/element/string.json b/common/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..63774a3f303709e4039aa24b31640acc8e5732fd --- /dev/null +++ b/common/src/main/resources/en_US/element/string.json @@ -0,0 +1,24 @@ +{ + "string": [ + { + "name": "loading", + "value": "Loading..." + }, + { + "name": "no_more", + "value": "No more resources" + }, + { + "name": "network_error", + "value": "The network connection is interrupted. Please check your network setting or try again later." + }, + { + "name": "server_error", + "value": "Failed to connect to the server, Please try again later." + }, + { + "name": "network_setting", + "value": "Setting up the network." + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/rawfile/hmos_web_config.json b/common/src/main/resources/rawfile/hmos_web_config.json new file mode 100644 index 0000000000000000000000000000000000000000..51c0c3b49dccf322d7a579a805f92a16f5ea7845 --- /dev/null +++ b/common/src/main/resources/rawfile/hmos_web_config.json @@ -0,0 +1,5 @@ +{ + "galleryUrl": "https://appgallery.huawei.com/app/detail?id=com.huawei.hmos.tips", + "miitUrl": "https://beian.miit.gov.cn", + "whitelist": "{\"UrlPermissionList\":[{\"scheme\":\"https\",\"host\":\"developer.huawei.com\"},{\"scheme\":\"https\",\"host\":\"beian.miit.gov.cn\"},{\"scheme\":\"https\",\"host\":\"gitee.com\",\"path\":\"harmonyos_samples\/\"},{\"host\":\"rawfile\"}]}" +} \ No newline at end of file diff --git a/common/src/main/resources/rawfile/mockdata/component-page.json b/common/src/main/resources/rawfile/mockdata/component-page.json new file mode 100644 index 0000000000000000000000000000000000000000..e58feaa87b35ba3b556336fb8cf4f7ed8a072ad6 --- /dev/null +++ b/common/src/main/resources/rawfile/mockdata/component-page.json @@ -0,0 +1,477 @@ +{ + "code": 200, + "message": "Success", + "data": { + "currentPage": 1, + "pageSize": 30, + "totalSize": "12", + "data": { + "bannerInfos": [ + { + "id": 1, + "bannerTitle": "开发者你好,欢迎来到HMOS代码工坊", + "bannerSubTitle": "HarmonyOS代码工坊", + "bannerDesc": "欢迎来到鸿蒙开发者世界,一起体验鸿蒙应用开发。", + "bannerType": 4, + "bannerValue": 14, + "mediaType": 1, + "mediaUrl": "image/banner/banner_HMOS.png", + "detailsUrl": "bannercols/hmos-world/index.html" + }, + { + "id": 4, + "bannerTitle": "HMOS代码工坊一多开发实践", + "bannerSubTitle": "一次开发,多端部署", + "bannerDesc": "探索HarmonyOS代码工坊一多开发实践", + "bannerType": 4, + "bannerValue": 18, + "mediaType": 1, + "mediaUrl": "image/banner/banner_ui_design.png", + "detailsUrl": "articlecols/articles/multi-adaptation/index.html" + }, + { + "id": 5, + "bannerTitle": "开箱即用的AI语音播报能力", + "bannerSubTitle": "AI朗读", + "bannerDesc": "朗读文本转语音,新闻小说即享听。", + "bannerType": 4, + "bannerValue": 19, + "mediaType": 1, + "mediaUrl": "image/banner/banner_ai.png", + "detailsUrl": "bannercols/ai-voice-out-of-box/index.html" + } + ], + "cardData": [ + { + "id": 1, + "cardTitle": "为页面添加文本", + "cardSubTitle": "文本类组件", + "cardType": 1, + "cardStyleType": 1, + "cardImage": "image/component/card/text.png", + "version": 1000000, + "cardContents": [ + { + "id": 10, + "type": 1, + "cardId": 1, + "mediaType": 1, + "mediaUrl": "image/component/icon/text/text.png", + "title": "Text", + "subTitle": "文本" + }, + { + "id": 42, + "type": 1, + "cardId": 1, + "mediaType": 1, + "mediaUrl": "image/component/icon/text/textinput.png", + "title": "TextInput", + "subTitle": "单行文本输入" + }, + { + "id": 40, + "type": 1, + "cardId": 1, + "mediaType": 1, + "mediaUrl": "image/component/icon/text/textarea.png", + "title": "TextArea", + "subTitle": "多行文本输入" + }, + { + "id": 41, + "type": 1, + "cardId": 1, + "mediaType": 1, + "mediaUrl": "image/component/icon/text/textstyle.png", + "title": "TextStyle", + "subTitle": "属性字符串" + } + ] + }, + { + "id": 7, + "cardTitle": "安全使用相机", + "cardSubTitle": "CameraPicker", + "cardType": 1, + "cardStyleType": 3, + "cardImage": "image/component/card/camerapicker.png", + "version": 1000000, + "cardContents": [ + { + "id": 28, + "type": 1, + "cardId": 7, + "mediaType": 1, + "mediaUrl": "image/component/icon/camerapicker.png", + "title": "CameraPicker", + "subTitle": "相机选择器" + } + ] + }, + { + "id": 2, + "cardTitle": "按钮、图片、进度条等", + "cardSubTitle": "常用组件", + "cardType": 1, + "cardStyleType": 2, + "cardImage": null, + "version": 1000000, + "cardContents": [ + { + "id": 1, + "type": 1, + "cardId": 2, + "mediaType": 1, + "mediaUrl": "image/component/icon/common/button.png", + "title": "Button", + "subTitle": "按钮" + }, + { + "id": 11, + "type": 1, + "cardId": 2, + "mediaType": 1, + "mediaUrl": "image/component/icon/common/image.png", + "title": "Image", + "subTitle": "图片" + }, + { + "id": 13, + "type": 1, + "cardId": 2, + "mediaType": 1, + "mediaUrl": "image/component/icon/common/progress.png", + "title": "Progress", + "subTitle": "进度条" + }, + { + "id": 22, + "type": 1, + "cardId": 2, + "mediaType": 1, + "mediaUrl": "image/component/icon/common/rating.png", + "title": "Rating", + "subTitle": "评分" + }, + { + "id": 2, + "type": 1, + "cardId": 2, + "mediaType": 1, + "mediaUrl": "image/component/icon/common/toggle.png", + "title": "Toggle", + "subTitle": "开关" + } + ] + }, + { + "id": 13, + "cardTitle": "安全使用图库", + "cardSubTitle": "PhotoPicker", + "cardType": 1, + "cardStyleType": 3, + "cardImage": "image/component/card/photopicker.png", + "version": 1000000, + "cardContents": [ + { + "id": 26, + "type": 1, + "cardId": 13, + "mediaType": 1, + "mediaUrl": "image/component/icon/photopicker.png", + "title": "PhotoViewPicker", + "subTitle": "图库选择器" + } + ] + }, + { + "id": 4, + "cardTitle": "使用布局容器构建页面", + "cardSubTitle": "常用布局", + "cardType": 1, + "cardStyleType": 1, + "cardImage": "image/component/card/layout.png", + "version": 1000000, + "cardContents": [ + { + "id": 5, + "type": 1, + "cardId": 4, + "mediaType": 1, + "mediaUrl": "image/component/icon/layout/column.png", + "title": "Column", + "subTitle": "线性布局 - 纵向" + }, + { + "id": 6, + "type": 1, + "cardId": 4, + "mediaType": 1, + "mediaUrl": "image/component/icon/layout/row.png", + "title": "Row", + "subTitle": "线性布局 - 横向" + }, + { + "id": 7, + "type": 1, + "cardId": 4, + "mediaType": 1, + "mediaUrl": "image/component/icon/layout/stack.png", + "title": "Stack", + "subTitle": "层叠布局" + }, + { + "id": 23, + "type": 1, + "cardId": 4, + "mediaType": 1, + "mediaUrl": "image/component/icon/layout/flex.png", + "title": "Flex", + "subTitle": "弹性布局" + } + ] + }, + { + "id": 11, + "cardTitle": "手写笔服务", + "cardSubTitle": "Pen Kit", + "cardType": 1, + "cardStyleType": 3, + "cardImage": "image/component/card/pankit.png", + "version": 1000000, + "cardContents": [ + { + "id": 38, + "type": 1, + "cardId": 11, + "mediaType": 1, + "mediaUrl": "image/component/icon/penkit.png", + "title": "Penkit", + "subTitle": "手写笔服务" + } + ] + }, + { + "id": 5, + "cardTitle": "构建列表类布局", + "cardSubTitle": "常用布局", + "cardType": 1, + "cardStyleType": 2, + "cardImage": null, + "version": 1000000, + "cardContents": [ + { + "id": 9, + "type": 1, + "cardId": 5, + "mediaType": 1, + "mediaUrl": "image/component/icon/list/list.png", + "title": "List", + "subTitle": "列表" + }, + { + "id": 8, + "type": 1, + "cardId": 5, + "mediaType": 1, + "mediaUrl": "image/component/icon/list/grid.png", + "title": "Grid", + "subTitle": "网格" + }, + { + "id": 25, + "type": 1, + "cardId": 5, + "mediaType": 1, + "mediaUrl": "image/component/icon/list/waterflow.png", + "title": "WaterFlow", + "subTitle": "瀑布流" + }, + { + "id": 24, + "type": 1, + "cardId": 5, + "mediaType": 1, + "mediaUrl": "image/component/icon/list/swiper.png", + "title": "Swiper", + "subTitle": "滑动轮播组件" + }, + { + "id": 18, + "type": 1, + "cardId": 5, + "mediaType": 1, + "mediaUrl": "image/component/icon/list/tabs.png", + "title": "Tabs", + "subTitle": "页签" + } + ] + }, + { + "id": 12, + "cardTitle": "AI抠图", + "cardSubTitle": "开箱即用的AI能力", + "cardType": 1, + "cardStyleType": 3, + "cardImage": "image/component/card/enable_analyzer.png", + "version": 1000000, + "cardContents": [ + { + "id": 32, + "type": 1, + "cardId": 12, + "mediaType": 1, + "mediaUrl": "image/component/icon/enable_analyzer.png", + "title": "AI Matting", + "subTitle": "AI抠图" + } + ] + }, + { + "id": 9, + "cardTitle": "给页面添加弹窗", + "cardSubTitle": "消息弹窗", + "cardType": 1, + "cardStyleType": 2, + "cardImage": null, + "version": 1000000, + "cardContents": [ + { + "id": 20, + "type": 1, + "cardId": 9, + "mediaType": 1, + "mediaUrl": "image/component/icon/dialog/alert_dialog.png", + "title": "AlertDialog", + "subTitle": "警告弹窗" + }, + { + "id": 21, + "type": 1, + "cardId": 9, + "mediaType": 1, + "mediaUrl": "image/component/icon/dialog/text_dialog.png", + "title": "TextPickerDialog", + "subTitle": "文本滑动选择器弹窗" + }, + { + "id": 35, + "type": 1, + "cardId": 9, + "mediaType": 1, + "mediaUrl": "image/component/icon/dialog/custom_dialog.png", + "title": "CustomDialog", + "subTitle": "自定义弹窗" + }, + { + "id": 33, + "type": 1, + "cardId": 9, + "mediaType": 1, + "mediaUrl": "image/component/icon/dialog/action_sheet.png", + "title": "ActionSheet", + "subTitle": "列表选择弹窗" + }, + { + "id": 34, + "type": 1, + "cardId": 9, + "mediaType": 1, + "mediaUrl": "image/component/icon/dialog/popup.png", + "title": "Popup", + "subTitle": "气泡弹窗" + } + ] + }, + { + "id": 6, + "cardTitle": "AI语音播报", + "cardSubTitle": "开箱即用的AI能力", + "cardType": 1, + "cardStyleType": 3, + "cardImage": "image/component/card/texttospeech.png", + "version": 1000000, + "cardContents": [ + { + "id": 29, + "type": 1, + "cardId": 6, + "mediaType": 1, + "mediaUrl": "image/component/icon/texttospeech.png", + "title": "TextToSpeech", + "subTitle": "文本转语音" + } + ] + }, + { + "id": 8, + "cardTitle": "高效拉起系统应用", + "cardSubTitle": "Picker和Linking", + "cardType": 1, + "cardStyleType": 1, + "cardImage": "image/component/card/link.png", + "version": 1000000, + "cardContents": [ + { + "id": 14, + "type": 1, + "cardId": 8, + "mediaType": 1, + "mediaUrl": "image/component/icon/link/calendar_picker.png", + "title": "CalendarPicker", + "subTitle": "日历选择器" + }, + { + "id": 15, + "type": 1, + "cardId": 8, + "mediaType": 1, + "mediaUrl": "image/component/icon/link/date_picker.png", + "title": "DatePicker", + "subTitle": "日期滑动选择器" + }, + { + "id": 27, + "type": 1, + "cardId": 8, + "mediaType": 1, + "mediaUrl": "image/component/icon/link/document_picker.png", + "title": "DocumentViewPicker", + "subTitle": "文件选择器" + }, + { + "id": 36, + "type": 1, + "cardId": 8, + "mediaType": 1, + "mediaUrl": "image/component/icon/link/linking.png", + "title": "AppLinking", + "subTitle": "应用拉起" + } + ] + }, + { + "id": 10, + "cardTitle": "AI语音字幕", + "cardSubTitle": "开箱即用的AI能力", + "cardType": 1, + "cardStyleType": 3, + "cardImage": "image/component/card/ai_caption_component.png", + "version": 1000000, + "cardContents": [ + { + "id": 30, + "type": 1, + "cardId": 10, + "mediaType": 1, + "mediaUrl": "image/component/icon/ai_caption_component.png", + "title": "AICaptionComponent", + "subTitle": "AI字幕组件" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/rawfile/mockdata/discovery-page.json b/common/src/main/resources/rawfile/mockdata/discovery-page.json new file mode 100644 index 0000000000000000000000000000000000000000..2ed278a243c755e889569469357dc958c0112773 --- /dev/null +++ b/common/src/main/resources/rawfile/mockdata/discovery-page.json @@ -0,0 +1,175 @@ +{ + "code": 200, + "message": "Success", + "data": { + "bannerInfos": [ + { + "id": 3, + "bannerTitle": "HarmonyOS UX设计新体验", + "bannerSubTitle": "UX设计体验", + "bannerDesc": "助力开发者打造鸿蒙应用新体验,共建和谐数字世界。", + "bannerType": 4, + "bannerValue": 16, + "mediaType": 1, + "mediaUrl": "image/banner/banner_new_features.png", + "detailsUrl": "bannercols/ux-design-new-experience/index.html" + }, + { + "id": 8, + "bannerTitle": "设计与开发应用介绍", + "bannerSubTitle": "设计与开发", + "bannerDesc": "打造高端精致、简单易用、极致流畅、纯净安全的应用。", + "bannerType": 4, + "bannerValue": 20, + "mediaType": 1, + "mediaUrl": "image/banner/banner_develop_design.png", + "detailsUrl": "bannercols/app-design-development/index.html" + }, + { + "id": 9, + "bannerTitle": "开发者你好,欢迎来到HMOS代码工坊", + "bannerSubTitle": "HarmonyOS代码工坊", + "bannerDesc": "欢迎来到鸿蒙开发者世界,一起体验鸿蒙应用开发。", + "bannerType": 4, + "bannerValue": 21, + "mediaType": 1, + "mediaUrl": "image/banner/banner_HMOS.png", + "detailsUrl": "bannercols/hmos-world/index.html" + } + ], + "discoveryData": [ + { + "id": 2, + "name": "鸿蒙应用开发实践", + "type": 1, + "contents": [ + { + "id": 1, + "type": 1, + "mediaType": 1, + "mediaUrl": "image/practice/latestAdvice/discovery_article_id_3.png", + "title": "小窗口大世界,智享实况窗服务", + "subTitle": null, + "desc": "实时信息随处展示。", + "author": null, + "detailsUrl": "articlecols/articles/live-window-service/index.html" + }, + { + "id": 2, + "type": 1, + "mediaType": 1, + "mediaUrl": "image/practice/latestAdvice/discovery_article_id_4.png", + "title": "Picker安心取,用户主导安全新体验", + "subTitle": null, + "desc": "纯净安全,打造全新体验。", + "author": null, + "detailsUrl": "articlecols/articles/native-safety/index.html" + }, + { + "id": 3, + "type": 1, + "mediaType": 1, + "mediaUrl": "image/practice/latestAdvice/discovery_article_id_1.png", + "title": "一镜到底,畅享无界丝滑视觉之旅", + "subTitle": null, + "desc": "极致流畅,畅享丝滑。", + "author": null, + "detailsUrl": "articlecols/articles/shared-element-transition/index.html" + }, + { + "id": 4, + "type": 1, + "mediaType": 1, + "mediaUrl": "image/practice/latestAdvice/discovery_article_id_5.png", + "title": "跨设备互联,打造无缝流转极致体验", + "subTitle": null, + "desc": "跨设备互联,无缝流转。", + "author": null, + "detailsUrl": "articlecols/articles/app-continuation/index.html" + }, + { + "id": 5, + "type": 1, + "mediaType": 1, + "mediaUrl": "image/practice/latestAdvice/discovery_article_id_2.png", + "title": "AI识图,开启智能图像处理新纪元", + "subTitle": null, + "desc": "开启图像智能新纪元。", + "author": null, + "detailsUrl": "articlecols/articles/smart-visual-recognition/index.html" + } + ] + }, + { + "id": 3, + "name": "应用体验设计", + "type": 2, + "contents": [ + { + "id": 18, + "type": 2, + "mediaType": 1, + "mediaUrl": "image/practice/experienceDesign/ux_multi.png", + "title": "多端UX设计", + "subTitle": "HarmonyOS", + "desc": "提供特征型场景界面设计,结合应用业务场景,实现最佳界面适配和创新设计。", + "author": null, + "detailsUrl": "articlecols/articles/native-ux-design/index.html" + }, + { + "id": 22, + "type": 2, + "mediaType": 1, + "mediaUrl": "image/practice/experienceDesign/ux_experience.png", + "title": "应用UX体验标准", + "subTitle": "HarmonyOS", + "desc": "提前了解HarmonyOS应用UX体验标准,快速满足上架条件。", + "author": null, + "detailsUrl": "articlecols/articles/ux-guidelines/index.html" + }, + { + "id": 7, + "type": 2, + "mediaType": 1, + "mediaUrl": "image/practice/experienceDesign/ux_effect_design.png", + "title": "UX动效设计", + "subTitle": "HarmonyOS", + "desc": "HarmonyOS UX动效设计,通过适配不同场景,提升用户体验,满足多样化需求。", + "author": null, + "detailsUrl": "articlecols/articles/design-animation/index.html" + } + ] + }, + { + "id": 5, + "name": "功能开发", + "type": 4, + "contents": [ + { + "id": 8, + "type": 3, + "mediaType": 1, + "mediaUrl": "image/practice/functionDevelopment/discovery_article_id_8.png", + "title": "分层架构设计", + "subTitle": null, + "desc": "HarmonyOS应用采用分层架构,一套代码工程,支持华为手机、PC/2in1等1+8全场景设备。", + "publishTime": "2024年8月15日", + "author": "发布者", + "detailsUrl": "articlecols/articles/layered-architecture-design/index.html" + }, + { + "id": 12, + "type": 4, + "mediaType": 1, + "mediaUrl": "image/practice/functionDevelopment/discovery_article_id_12.png", + "title": "HMOS代码工坊一多开发实践", + "subTitle": "HarmonyOS", + "desc": "践行“一次开发,多端部署”理念,实现多设备无缝协同,提供一致且高效的用户体验。", + "author": null, + "detailsUrl": "articlecols/articles/multi-adaptation/index.html" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/common/src/main/resources/rawfile/mockdata/file-data.json b/common/src/main/resources/rawfile/mockdata/file-data.json new file mode 100644 index 0000000000000000000000000000000000000000..d20037d759c811cb396522709c64cd6e7239ce7e --- /dev/null +++ b/common/src/main/resources/rawfile/mockdata/file-data.json @@ -0,0 +1,1233 @@ + +{ + "code": 200, + "message": "Success", + "data": [ + { + "id": 1, + "componentName": "Button", + "subTitle": "按钮", + "componentType": 1, + "props": [ + { + "propertyName": "controlSize", + "propertyDesc": "按钮尺寸", + "displayType": "enum", + "defaultProperty": "Normal", + "propertyValues": "[\"Normal\",\"Small\"]" + }, + { + "propertyName": "buttonType", + "propertyDesc": "边缘形状", + "displayType": "enum", + "defaultProperty": "Capsule", + "propertyValues": "[\"Capsule\",\"Normal\"]" + }, + { + "propertyName": "buttonStyle", + "propertyDesc": "按钮类型", + "displayType": "enum", + "defaultProperty": "Emphasized", + "propertyValues": "[\"Normal\",\"Emphasized\",\"Textual\"]" + }, + { + "propertyName": "operation", + "propertyDesc": "手势操作", + "displayType": "enum", + "defaultProperty": "Click", + "propertyValues": "[\"Click\", \"LongGesture\", \"NoClick\"]" + }, + { + "propertyName": "backgroundColor", + "propertyDesc": "按钮颜色", + "displayType": "color", + "defaultProperty": "rgba(0, 85, 255, 1.00)", + "propertyValues": "''" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-common-components-button" + }, + { + "articleType": 2, + "url": "https://developer.huawei.com/consumer/cn/doc/design-guides/button-0000001929683228" + } + ] + }, + { + "id": 2, + "componentName": "Toggle", + "subTitle": "开关", + "componentType": 1, + "props": [ + { + "propertyName": "isOn", + "propertyDesc": "是否开启", + "displayType": "boolean", + "defaultProperty": "true", + "propertyValues": "''" + }, + { + "propertyName": "toggleType", + "propertyDesc": "类型", + "displayType": "enum", + "defaultProperty": "Switch", + "propertyValues": "[\"Switch\",\"Button\",\"Checkbox\"]" + }, + { + "propertyName": "backgroundColor", + "propertyDesc": "背景色", + "displayType": "color", + "defaultProperty": "rgba(0, 85, 255, 1.00)", + "propertyValues": "''" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-common-components-switch" + }, + { + "articleType": 2, + "url": "https://developer.huawei.com/consumer/cn/doc/design-guides/toggleswitch-0000001956852745" + } + ] + }, + { + "id": 5, + "componentName": "Column", + "subTitle": "线性布局 - 纵向", + "componentType": 2, + "props": [ + { + "propertyName": "padding", + "propertyDesc": "边距", + "displayType": "enum", + "defaultProperty": "All", + "propertyValues": "[\"Vertical\", \"Horizontal\", \"All\",\"None\"]" + }, + { + "propertyName": "alignItems", + "propertyDesc": "横向对齐", + "displayType": "enum", + "defaultProperty": "Center", + "propertyValues": "[\"Start\",\"Center\",\"End\"]" + }, + { + "propertyName": "flexAlign", + "propertyDesc": "竖向对齐", + "displayType": "enum", + "defaultProperty": "Center", + "propertyValues": "[\"Start\",\"Center\",\"End\",\"SpaceBetween\"]" + }, + { + "propertyName": "space", + "propertyDesc": "自定义间距", + "displayType": "number", + "defaultProperty": "6", + "propertyValues": "{ \"left\":4, \"right\":24, \"step\": 1 }" + }, + { + "propertyName": "paddingNum", + "propertyDesc": "自定义边距", + "displayType": "number", + "defaultProperty": "6", + "propertyValues": "{ \"left\":4, \"right\":24, \"step\": 1 }" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-layout-development-linear" + } + ] + }, + { + "id": 6, + "componentName": "Row", + "subTitle": "线性布局 - 横向", + "componentType": 2, + "props": [ + { + "propertyName": "padding", + "propertyDesc": "边距", + "displayType": "enum", + "defaultProperty": "All", + "propertyValues": "[\"Vertical\", \"Horizontal\", \"All\",\"None\"]" + }, + { + "propertyName": "alignItems", + "propertyDesc": "竖向对齐", + "displayType": "enum", + "defaultProperty": "Center", + "propertyValues": "[\"Top\",\"Center\",\"Bottom\"]" + }, + { + "propertyName": "flexAlign", + "propertyDesc": "横向对齐", + "displayType": "enum", + "defaultProperty": "Center", + "propertyValues": "[\"Start\",\"Center\",\"End\",\"SpaceBetween\"]" + }, + { + "propertyName": "space", + "propertyDesc": "自定义间距", + "displayType": "number", + "defaultProperty": "6", + "propertyValues": "{ \"left\":4, \"right\":24, \"step\": 1 }" + }, + { + "propertyName": "paddingNum", + "propertyDesc": "自定义边距", + "displayType": "number", + "defaultProperty": "6", + "propertyValues": "{ \"left\":4, \"right\":24, \"step\": 1 }" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-layout-development-linear" + } + ] + }, + { + "id": 7, + "componentName": "Stack", + "subTitle": "层叠布局", + "componentType": 2, + "props": [ + { + "propertyName": "alignDirection", + "propertyDesc": "对齐方向", + "displayType": "enum", + "defaultProperty": "Center", + "propertyValues": "[\"Top\",\"Center\",\"Bottom\"]" + }, + { + "propertyName": "alignContentTop", + "propertyDesc": "对齐方式(上)", + "displayType": "enum", + "defaultProperty": "TopStart", + "propertyValues": "[\"Top\",\"TopStart\",\"TopEnd\"]" + }, + { + "propertyName": "alignContentCenter", + "propertyDesc": "对齐方式(中)", + "displayType": "enum", + "defaultProperty": "Center", + "propertyValues": "[\"Start\",\"Center\",\"End\"]" + }, + { + "propertyName": "alignContentBottom", + "propertyDesc": "对齐方式(下)", + "displayType": "enum", + "defaultProperty": "BottomStart", + "propertyValues": "[\"BottomStart\",\"BottomEnd\",\"Bottom\"]" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-layout-development-stack-layout" + } + ] + }, + { + "id": 8, + "componentName": "Grid", + "subTitle": "网格", + "componentType": 2, + "props": [ + { + "propertyName": "operationMode", + "propertyDesc": "编辑模式", + "displayType": "boolean", + "defaultProperty": "true", + "propertyValues": "''" + }, + { + "propertyName": "columnsGap", + "propertyDesc": "列与列的间距", + "displayType": "number", + "defaultProperty": "6", + "propertyValues": "{ \"left\":4, \"right\":24, \"step\": 1 }" + }, + { + "propertyName": "columnsNum", + "propertyDesc": "列数", + "displayType": "number", + "defaultProperty": "4", + "propertyValues": "{ \"left\":1, \"right\":4, \"step\": 1 }" + }, + { + "propertyName": "rowsGap", + "propertyDesc": "行与行的间距", + "displayType": "number", + "defaultProperty": "6", + "propertyValues": "{ \"left\":4, \"right\":24, \"step\": 1 }" + }, + { + "propertyName": "rowsNum", + "propertyDesc": "行数", + "displayType": "number", + "defaultProperty": "4", + "propertyValues": "{ \"left\":1, \"right\":4, \"step\": 1 }" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-layout-development-create-grid" + } + ] + }, + { + "id": 9, + "componentName": "List", + "subTitle": "列表", + "componentType": 2, + "props": [ + { + "propertyName": "sticky", + "propertyDesc": "是否吸顶", + "displayType": "boolean", + "defaultProperty": "true", + "propertyValues": "''" + }, + { + "propertyName": "edgeEffect", + "propertyDesc": "滑动效果", + "displayType": "enum", + "defaultProperty": "None", + "propertyValues": "[\"Spring\", \"Fade\", \"None\"]" + }, + { + "propertyName": "lanesNum", + "propertyDesc": "组件列数", + "displayType": "number", + "defaultProperty": "2", + "propertyValues": "{ \"left\": 1, \"right\": 4, \"step\": 1 }" + }, + { + "propertyName": "gutter", + "propertyDesc": "自定义间距", + "displayType": "number", + "defaultProperty": "5", + "propertyValues": "{ \"left\":1, \"right\":10, \"step\": 1 }" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-layout-development-create-list" + }, + { + "articleType": 2, + "url": "https://developer.huawei.com/consumer/cn/doc/design-guides/list-0000001929853910" + } + ] + }, + { + "id": 10, + "componentName": "Text", + "subTitle": "文本", + "componentType": 1, + "props": [ + { + "propertyName": "fontWeight", + "propertyDesc": "字重", + "displayType": "enum", + "defaultProperty": "Normal", + "propertyValues": "[\"Normal\",\"Lighter\",\"Bolder\"]" + }, + { + "propertyName": "fontSize", + "propertyDesc": "字号", + "displayType": "number", + "defaultProperty": "24", + "propertyValues": "{ \"left\": 10, \"right\": 50, \"step\": 1 }" + }, + { + "propertyName": "textShadowRadius", + "propertyDesc": "文字阴影", + "displayType": "number", + "defaultProperty": "0", + "propertyValues": "{ \"left\":0, \"right\":20, \"step\": 1 }" + }, + { + "propertyName": "letterSpacing", + "propertyDesc": "字间距", + "displayType": "number", + "defaultProperty": "0", + "propertyValues": "{ \"left\":0, \"right\":20, \"step\": 1 }" + }, + { + "propertyName": "fontColor", + "propertyDesc": "颜色", + "displayType": "color", + "defaultProperty": "rgba(0, 85, 255, 1.00)", + "propertyValues": "''" + }, + { + "propertyName": "opacity", + "propertyDesc": "透明度", + "displayType": "opacity", + "defaultProperty": "1", + "propertyValues": "{ \"left\": 0, \"right\": 1, \"step\": 0.01 }" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-common-components-text-display" + }, + { + "articleType": 2, + "url": "https://developer.huawei.com/consumer/cn/doc/design-guides/text-0000001956975261" + } + ] + }, + { + "id": 11, + "componentName": "Image", + "subTitle": "图片", + "componentType": 1, + "props": [ + { + "propertyName": "clip", + "propertyDesc": "是否裁剪", + "displayType": "boolean", + "defaultProperty": "true", + "propertyValues": "''" + }, + { + "propertyName": "colorFilterMatrixStr", + "propertyDesc": "颜色滤镜(png图片)", + "displayType": "enum", + "defaultProperty": "无滤镜", + "propertyValues": "[\"无滤镜\",\"色彩旋转\",\"灰色滤镜\",\"增色滤镜\"]" + }, + { + "propertyName": "objectFit", + "propertyDesc": "图片填充效果", + "displayType": "enum", + "defaultProperty": "Cover", + "propertyValues": "[\"Contain\",\"Cover\",\"Auto\",\"Fill\",\"ScaleDown\",\"None\"]" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-graphics-display" + }, + { + "articleType": 2, + "url": "https://developer.huawei.com/consumer/cn/doc/design-guides/image-0000001956975273" + } + ] + }, + { + "id": 13, + "componentName": "Progress", + "subTitle": "进度条", + "componentType": 1, + "props": [ + { + "propertyName": "kind", + "propertyDesc": "类型", + "displayType": "enum", + "defaultProperty": "Progress", + "propertyValues": "[\"Progress\",\"Loading\"]" + }, + { + "propertyName": "style", + "propertyDesc": "组件的样式", + "displayType": "enum", + "defaultProperty": "CapsuleStyle", + "propertyValues": "[\"CapsuleStyle\",\"LinearStyle\",\"ProgressStyle\",\"RingStyle1\",\"RingStyle2\",\"EclipseStyle\",\"ScaleRingStyle\"]" + }, + { + "propertyName": "value", + "propertyDesc": "进度条初始值", + "displayType": "number", + "defaultProperty": "38", + "propertyValues": "{ \"left\": 0, \"right\": 100, \"step\": 1 }" + }, + { + "propertyName": "color", + "propertyDesc": "进度条颜色", + "displayType": "color", + "defaultProperty": "rgba(0, 85, 255, 1.00)", + "propertyValues": "''" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-common-components-progress-indicator" + }, + { + "articleType": 2, + "url": "https://developer.huawei.com/consumer/cn/doc/design-guides/progress-0000001929656644" + } + ] + }, + { + "id": 14, + "componentName": "CalendarPicker", + "subTitle": "日历选择器", + "componentType": 1, + "props": [ + { + "propertyName": "calendarAlignType", + "propertyDesc": "对齐方向", + "displayType": "enum", + "defaultProperty": "Center", + "propertyValues": "[\"Start\",\"Center\",\"End\"]" + }, + { + "propertyName": "pickerFontWeight", + "propertyDesc": "入口区字体粗细", + "displayType": "enum", + "defaultProperty": "Normal", + "propertyValues": "[\"Normal\",\"Lighter\",\"Bolder\"]" + }, + { + "propertyName": "calendarOffsetX", + "propertyDesc": "水平偏移量", + "displayType": "number", + "defaultProperty": "0", + "propertyValues": "{ \"left\": 1, \"right\": 72, \"step\": 1 }" + }, + { + "propertyName": "calendarOffsetY", + "propertyDesc": "垂直偏移量", + "displayType": "number", + "defaultProperty": "0", + "propertyValues": "{ \"left\": 1, \"right\": 72, \"step\": 1 }" + }, + { + "propertyName": "pickerFontSize", + "propertyDesc": "入口区文字大小", + "displayType": "number", + "defaultProperty": "16", + "propertyValues": "{ \"left\": 16, \"right\": 38, \"step\": 1 }" + }, + { + "propertyName": "pickerFontColor", + "propertyDesc": "入口区文字颜色", + "displayType": "color", + "defaultProperty": "rgba(0, 85, 255, 1.0)", + "propertyValues": "''" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-calendarpicker" + }, + { + "articleType": 2, + "url": "https://developer.huawei.com/consumer/cn/doc/design-guides/picker-0000001956852749" + } + ] + }, + { + "id": 15, + "componentName": "DatePicker", + "subTitle": "日期滑动选择器", + "componentType": 1, + "props": [ + { + "propertyName": "lunar", + "propertyDesc": "显示农历", + "displayType": "boolean", + "defaultProperty": "false", + "propertyValues": "''" + }, + { + "propertyName": "selectedFontWeight", + "propertyDesc": "选中项字体粗细", + "displayType": "enum", + "defaultProperty": "Normal", + "propertyValues": "[\"Normal\",\"Lighter\",\"Bolder\"]" + }, + { + "propertyName": "selectedFontSize", + "propertyDesc": "选中项字体大小", + "displayType": "number", + "defaultProperty": "18", + "propertyValues": "{ \"left\": 12, \"right\": 22, \"step\": 1 }" + }, + { + "propertyName": "fontSize", + "propertyDesc": "标准项字体大小", + "displayType": "number", + "defaultProperty": "16", + "propertyValues": "{ \"left\": 12, \"right\": 22, \"step\": 1 }" + }, + { + "propertyName": "selectedTextColor", + "propertyDesc": "选中项字体颜色", + "displayType": "color", + "defaultProperty": "rgba(0, 85, 255, 1.0)", + "propertyValues": "''" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-datepicker" + }, + { + "articleType": 2, + "url": "https://developer.huawei.com/consumer/cn/doc/design-guides/picker-0000001956852749" + } + ] + }, + { + "id": 18, + "componentName": "Tabs", + "subTitle": "页签", + "componentType": 1, + "props": [ + { + "propertyName": "barPosition", + "propertyDesc": "页签位置", + "displayType": "enum", + "defaultProperty": "Start", + "propertyValues": "[\"Start\", \"End\"]" + }, + { + "propertyName": "vertical", + "propertyDesc": "是否垂直展示", + "displayType": "boolean", + "defaultProperty": "false", + "propertyValues": "''" + }, + { + "propertyName": "fadingEdge", + "propertyDesc": "渐隐效果", + "displayType": "boolean", + "defaultProperty": "false", + "propertyValues": "''" + }, + { + "propertyName": "backgroundBlurStyle", + "propertyDesc": "背景模糊材质", + "displayType": "enum", + "defaultProperty": "ComponentUltraThick", + "propertyValues": "[\"ComponentThin\",\"ComponentUltraThin\",\"ComponentRegular\",\"ComponentThick\",\"ComponentUltraThick\"]" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-navigation-tabs" + }, + { + "articleType": 2, + "url": "https://developer.huawei.com/consumer/cn/doc/design-guides/bottomtab-0000001956787789" + } + ] + }, + { + "id": 20, + "componentName": "AlertDialog", + "subTitle": "警告弹窗", + "componentType": 1, + "props": [ + { + "propertyName": "dialogAlignment", + "propertyDesc": "对齐", + "displayType": "enum", + "defaultProperty": "Center", + "propertyValues": "[\"Top\", \"Center\", \"Bottom\"]" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-methods-alert-dialog-box" + } + ] + }, + { + "id": 21, + "componentName": "TextPickerDialog", + "subTitle": "文本滑动选择器弹窗", + "componentType": 1, + "props": [ + { + "propertyName": "canLoop", + "propertyDesc": "循环滚动", + "displayType": "boolean", + "defaultProperty": "true", + "propertyValues": "''" + }, + { + "propertyName": "itemHeight", + "propertyDesc": "高度", + "displayType": "number", + "defaultProperty": "36", + "propertyValues": "{ \"left\": 24, \"right\": 56, \"step\": 1 }" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-methods-textpicker-dialog" + } + ] + }, + { + "id": 22, + "componentName": "Rating", + "subTitle": "评分", + "componentType": 1, + "props": [ + { + "propertyName": "starStyle", + "propertyDesc": "样式设置", + "displayType": "boolean", + "defaultProperty": "false", + "propertyValues": "''" + }, + { + "propertyName": "indicator", + "propertyDesc": "是否指示器", + "displayType": "boolean", + "defaultProperty": "false", + "propertyValues": "''" + }, + { + "propertyName": "stars", + "propertyDesc": "评分总数", + "displayType": "number", + "defaultProperty": "5", + "propertyValues": "{ \"left\": 1, \"right\": 5, \"step\": 1 }" + }, + { + "propertyName": "rating", + "propertyDesc": "评分值", + "displayType": "number", + "defaultProperty": "3", + "propertyValues": "{ \"left\": 0, \"right\": 5, \"step\": 0.5 }" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-rating" + } + ] + }, + { + "id": 23, + "componentName": "Flex", + "subTitle": "弹性布局", + "componentType": 1, + "props": [ + { + "propertyName": "elements", + "propertyDesc": "元素个数", + "displayType": "enum", + "defaultProperty": "2", + "propertyValues": "[\"2\",\"4\"]" + }, + { + "propertyName": "wrap", + "propertyDesc": "布局换行", + "displayType": "enum", + "defaultProperty": "NoWrap", + "propertyValues": "[\"NoWrap\",\"Wrap\",\"WrapReverse\"]" + }, + { + "propertyName": "direction", + "propertyDesc": "布局方向", + "displayType": "enum", + "defaultProperty": "Row", + "propertyValues": "[\"Row\",\"RowReverse\",\"Column\",\"ColumnReverse\"]" + }, + { + "propertyName": "justifyContent", + "propertyDesc": "主轴对齐", + "displayType": "enum", + "defaultProperty": "Center", + "propertyValues": "[\"Start\",\"Center\",\"End\",\"SpaceBetween\",\"SpaceAround\",\"SpaceEvenly\"]" + }, + { + "propertyName": "alignItems", + "propertyDesc": "交叉轴对齐", + "displayType": "enum", + "defaultProperty": "Center", + "propertyValues": "[\"Auto\",\"Start\",\"Center\",\"End\",\"Stretch\",\"Baseline\"]" + }, + { + "propertyName": "alignSelf", + "propertyDesc": "子元素交叉轴", + "displayType": "enum", + "defaultProperty": "Auto", + "propertyValues": "[\"Auto\",\"Start\",\"Center\",\"End\",\"Stretch\",\"Baseline\"]" + }, + { + "propertyName": "alignContent", + "propertyDesc": "内容对齐", + "displayType": "enum", + "defaultProperty": "Start", + "propertyValues": "[\"Start\",\"Center\",\"End\",\"SpaceBetween\",\"SpaceAround\",\"SpaceEvenly\"]" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-layout-development-flex-layout" + } + ] + }, + { + "id": 24, + "componentName": "Swiper", + "subTitle": "滑动轮播组件", + "componentType": 1, + "props": [ + { + "propertyName": "vertical", + "propertyDesc": "是否垂直显示", + "displayType": "boolean", + "defaultProperty": "false", + "propertyValues": "''" + }, + { + "propertyName": "loop", + "propertyDesc": "自动轮播", + "displayType": "boolean", + "defaultProperty": "true", + "propertyValues": "''" + }, + { + "propertyName": "isDisplayArrow", + "propertyDesc": "箭头展示", + "displayType": "boolean", + "defaultProperty": "false", + "propertyValues": "''" + }, + { + "propertyName": "effectMode", + "propertyDesc": "边缘效果", + "displayType": "enum", + "defaultProperty": "None", + "propertyValues": "[\"Spring\",\"Fade\",\"None\"]" + }, + { + "propertyName": "indicator", + "propertyDesc": "导航条", + "displayType": "enum", + "defaultProperty": "DotIndicator", + "propertyValues": "[\"DotIndicator\",\"DigitIndicator\",\"None\"]" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-layout-development-create-looping" + } + ] + }, + { + "id": 25, + "componentName": "WaterFlow", + "subTitle": "瀑布流", + "componentType": 1, + "props": [ + { + "propertyName": "friction", + "propertyDesc": "摩擦系数", + "displayType": "enum", + "defaultProperty": "0.75", + "propertyValues": "[\"0.1\",\"0.6\",\"0.75\",\"0.9\"]" + }, + { + "propertyName": "layoutDirection", + "propertyDesc": "布局主轴方向", + "displayType": "enum", + "defaultProperty": "Column", + "propertyValues": "[\"Column\",\"Row\",\"RowReverse\",\"ColumnReverse\"]" + }, + { + "propertyName": "rowsTemplate", + "propertyDesc": "行的数量", + "displayType": "number", + "defaultProperty": "3", + "propertyValues": "{ \"left\":1, \"right\":4, \"step\": 1 }" + }, + { + "propertyName": "columnsGap", + "propertyDesc": "列与列的间距", + "displayType": "number", + "defaultProperty": "6", + "propertyValues": "{ \"left\":4,\"right\":24, \"step\": 2 }" + }, + { + "propertyName": "columnsTemplate", + "propertyDesc": "列的数量", + "displayType": "number", + "defaultProperty": "3", + "propertyValues": "{ \"left\":1, \"right\":4, \"step\": 1 }" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-layout-development-create-waterflow" + } + ] + }, + { + "id": 26, + "componentName": "PhotoViewPicker", + "subTitle": "图库选择器", + "componentType": 1, + "props": [], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ohos-file-photopickercomponent" + } + ] + }, + { + "id": 27, + "componentName": "DocumentViewPicker", + "subTitle": "文件选择器", + "componentType": 1, + "props": [], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-picker" + } + ] + }, + { + "id": 28, + "componentName": "CameraPicker", + "subTitle": "相机选择器", + "componentType": 1, + "props": [ + { + "propertyName": "mediaTypes", + "propertyDesc": "相机模式", + "displayType": "enum", + "defaultProperty": "混合模式", + "propertyValues": "[\"拍照模式\",\"录制模式\",\"混合模式\"]" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-camerapicker" + } + ] + }, + { + "id": 29, + "componentName": "TextToSpeech", + "subTitle": "文本转语音", + "componentType": 1, + "props": [ + { + "propertyName": "speed", + "propertyDesc": "语速", + "displayType": "enum", + "defaultProperty": "1倍", + "propertyValues": "[\"0.5倍\",\"1倍\",\"1.5倍\",\"2倍\"]" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/hms-ai-texttospeech" + } + ] + }, + { + "id": 30, + "componentName": "AICaptionComponent", + "subTitle": "AI字幕组件", + "componentType": 1, + "props": [], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/speech-aicaption-guide" + } + ] + }, + { + "id": 32, + "componentName": "AI Matting", + "subTitle": "AI抠图", + "componentType": 1, + "props": [], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-image#draggable9" + } + ] + }, + { + "id": 33, + "componentName": "ActionSheet", + "subTitle": "列表选择弹窗", + "componentType": 1, + "props": [ + { + "propertyName": "sheetInfo", + "propertyDesc": "弹窗列表选项", + "displayType": "enum", + "defaultProperty": "sheetInfo1", + "propertyValues": "[\"sheetInfo1\",\"sheetInfo2\"]" + }, + { + "propertyName": "autoCancel", + "propertyDesc": "自动关闭弹窗", + "displayType": "boolean", + "defaultProperty": "true", + "propertyValues": "''" + }, + { + "propertyName": "transition", + "propertyDesc": "动画设置", + "displayType": "boolean", + "defaultProperty": "false", + "propertyValues": "''" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-methods-action-sheet" + } + ] + }, + { + "id": 34, + "componentName": "Popup", + "subTitle": "气泡弹窗", + "componentType": 1, + "props": [ + { + "propertyName": "placement", + "propertyDesc": "弹出位置", + "displayType": "enum", + "defaultProperty": "Bottom", + "propertyValues": "[\"Top\",\"Bottom\"]" + }, + { + "propertyName": "type", + "propertyDesc": "弹出样式", + "displayType": "enum", + "defaultProperty": "按钮气泡", + "propertyValues": "[\"按钮气泡\",\"文字气泡\",\"图文气泡\"]" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-popup-and-menu-components-popup" + }, + { + "articleType": 2, + "url": "https://developer.huawei.com/consumer/cn/doc/design-guides/popup-0000001956975269" + } + ] + }, + { + "id": 35, + "componentName": "CustomDialog", + "subTitle": "自定义弹窗", + "componentType": 1, + "props": [ + { + "propertyName": "style", + "propertyDesc": "弹窗样式", + "displayType": "enum", + "defaultProperty": "图文弹窗", + "propertyValues": "[\"图文弹窗\",\"进度弹窗\"]" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-common-components-custom-dialog" + } + ] + }, + { + "id": 36, + "componentName": "AppLinking", + "subTitle": "应用拉起", + "componentType": 1, + "props": [ + { + "propertyName": "type", + "propertyDesc": "拉起类型", + "displayType": "enum", + "defaultProperty": "应用市场", + "propertyValues": "[\"应用市场\",\"地图\",\"设置\",\"拨号\"]" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-inner-application-uiabilitycontext#uiabilitycontextopenlink12" + } + ] + }, + { + "id": 38, + "componentName": "Penkit", + "subTitle": "手写笔服务", + "componentType": 1, + "props": [], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/pen-introduction" + } + ] + }, + { + "id": 40, + "componentName": "TextArea", + "subTitle": "多行文本输入", + "componentType": 1, + "props": [ + { + "propertyName": "textOverflowType", + "propertyDesc": "超长文本显示", + "displayType": "enum", + "defaultProperty": "Clip", + "propertyValues": "[\"Clip\",\"Ellipsis\"]" + }, + { + "propertyName": "textAlign", + "propertyDesc": "文字对齐方式", + "displayType": "enum", + "defaultProperty": "Start", + "propertyValues": "[\"Start\",\"Center\",\"End\",\"Justify\"]" + }, + { + "propertyName": "maxLines", + "propertyDesc": "最大行数", + "displayType": "number", + "defaultProperty": "2", + "propertyValues": "{\"left\": 1, \"right\": 5, \"step\": 1 }" + }, + { + "propertyName": "lineSpacing", + "propertyDesc": "最大行距", + "displayType": "number", + "defaultProperty": "5", + "propertyValues": "{ \"left\": 5, \"right\": 30, \"step\": 1 }" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-common-components-text-input" + }, + { + "articleType": 2, + "url": "https://developer.huawei.com/consumer/cn/doc/design-guides/textinput-0000001957012557" + } + ] + }, + { + "id": 41, + "componentName": "TextStyle", + "subTitle": "属性字符串", + "componentType": 1, + "props": [ + { + "propertyName": "overflow", + "propertyDesc": "超长显示方式", + "displayType": "enum", + "defaultProperty": "Clip", + "propertyValues": "[\"Clip\",\"Ellipsis\"]" + }, + { + "propertyName": "textIndent", + "propertyDesc": "首行缩进", + "displayType": "number", + "defaultProperty": "0", + "propertyValues": "{ \"left\": 0, \"right\": 30, \"step\": 1 }" + }, + { + "propertyName": "maxLines", + "propertyDesc": "段落行数", + "displayType": "number", + "defaultProperty": "2", + "propertyValues": "{ \"left\": 1, \"right\": 6, \"step\": 1 }" + }, + { + "propertyName": "highlightColor", + "propertyDesc": "高亮文本颜色", + "displayType": "color", + "defaultProperty": "rgba(0, 85, 255, 1.0)", + "propertyValues": "''" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-styled-string" + } + ] + }, + { + "id": 42, + "componentName": "TextInput", + "subTitle": "单行文本输入", + "componentType": 1, + "props": [ + { + "propertyName": "placeholderFont", + "propertyDesc": "placeholder样式", + "displayType": "enum", + "defaultProperty": "正常字体", + "propertyValues": "[\"较细字体\",\"正常字体\",\"较粗字体\"]" + }, + { + "propertyName": "type", + "propertyDesc": "输入框类型", + "displayType": "enum", + "defaultProperty": "Normal", + "propertyValues": "[\"Normal\",\"Number\",\"NewPassword\"]" + }, + { + "propertyName": "fontColor", + "propertyDesc": "文本颜色", + "displayType": "color", + "defaultProperty": "rgba(0, 85, 255, 1.0)", + "propertyValues": "''" + } + ], + "recommendList": [ + { + "articleType": 1, + "url": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-common-components-text-input" + }, + { + "articleType": 2, + "url": "https://developer.huawei.com/consumer/cn/doc/design-guides/textinput-0000001957012557" + } + ] + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/rawfile/mockdata/sample-details-all.json b/common/src/main/resources/rawfile/mockdata/sample-details-all.json new file mode 100644 index 0000000000000000000000000000000000000000..f4eef444eda102b682075386b6efee09b529d307 --- /dev/null +++ b/common/src/main/resources/rawfile/mockdata/sample-details-all.json @@ -0,0 +1,622 @@ +{ + "code": 200, + "message": "Success", + "data": [ + { + "id": 35, + "categoryType": 4, + "order": 1, + "sampleDetail": [ + { + "id": 60, + "title": "HarmonyOS代码工坊", + "desc": "HarmonyOS代码工坊App是华为官方出品的一款大型开源开发示范应用。", + "preInstalled": true, + "sampleType": "commonClient", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/hmosworld_all_device.png", + "originalUrl": "https://gitee.com/harmonyos_samples/sample_in_harmonyos/blob/master/README.md", + "moduleName": "", + "abilityName": "", + "order": 1 + }, + { + "id": 63, + "title": "HarmonyOS代码工坊(手表版)", + "desc": "HarmonyOS代码工坊手表开发案例。", + "preInstalled": true, + "sampleType": "wearableClient", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/hmosworld_watch.png", + "originalUrl": "https://gitee.com/harmonyos_samples/sample_in_harmonyos/blob/master/README.md", + "moduleName": "", + "abilityName": "", + "order": 7 + }, + { + "id": 61, + "title": "三折叠,怎么折都有面", + "desc": "本示例主要使用断点监听和sidebarContainer组件、navigation组件相结合的方式,实现了商务办公类差异化的多场景响应式变化效果。", + "preInstalled": true, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/sample_business.png", + "originalUrl": "https://gitee.com/harmonyos_samples/MultiBusinessOffice/blob/br_release_hmosworld_new/README.md", + "moduleName": "multibusinesssample", + "abilityName": "MultibusinesssampleAbility", + "order": 2 + }, + { + "id": 62, + "title": "扩感导航,视野更清晰", + "desc": "锁屏沉浸实况窗在锁屏界面展示重要信息,让用户无需进入应用即可获取活动状态,适用于实时性要求高的场景。", + "preInstalled": true, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/sample_liveviewlockscreen.png", + "originalUrl": "https://gitee.com/harmonyos_samples/LiveViewLockScreen/blob/br_release_hmos/README.md", + "moduleName": "liveviewlockscreensample", + "abilityName": "LiveviewlockscreensampleAbility", + "order": 3 + }, + { + "id": 64, + "title": "碰一碰视频快速分享", + "desc": "本示例利用Share Kit与App Linking的结合,实现了快速跨设备分享视频并直接进入应用内视频播放页面的功能。", + "preInstalled": true, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/sample_knockshare.png", + "originalUrl": "https://gitee.com/harmonyos_samples/knock-share/blob/br_release_hmos/README.md", + "moduleName": "knocksharesample", + "abilityName": "KnocksharesampleAbility", + "order": 4 + }, + { + "id": 65, + "title": "视频投播更便捷", + "desc": "本实例基于播控中心和系统投播实现完整的视频投播功能,包含投播和播控基础控制:设备切换、集数切换、音量增减、进度切换。", + "preInstalled": true, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/sample_videocast.png", + "originalUrl": "https://gitee.com/harmonyos_samples/VideoCast/blob/br_release_hmos/README.md", + "moduleName": "videocastsample", + "abilityName": "VideocastsampleAbility", + "order": 5 + }, + { + "id": 66, + "title": "跨设备内容编辑新体验", + "desc": "本示例基于应用接续、分布式数据对象、分布式文件系统、跨设备互通等功能,实现文本图片数据跨设备交互及接续。", + "preInstalled": true, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/sample_continuepublic.png", + "originalUrl": "https://gitee.com/harmonyos_samples/ContinuePublish/blob/br_release_hmos/README.md", + "moduleName": "continuepublishsample", + "abilityName": "ContinuepublishsampleAbility", + "order": 6 + } + ] + }, + { + "id": 4, + "categoryType": 1, + "order": 1, + "sampleDetail": [ + { + "id": 16, + "title": "一多导航栏", + "desc": "本示例基于自适应布局和响应式布局,实现多设备上的分级导航栏效果。在sm、md断点下,展示为底部页签和顶部页签;在lg断点下,展示为侧边页签和顶部页签;在xl断点下,展示为侧边栏分级导航。为开发者提供分级导航栏的开发方案。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/multidevice/sample_multi_nav_bar.png", + "originalUrl": "https://gitee.com/harmonyos_samples/multi-nav-bar/blob/br_release_hmosworld_new/README.md", + "moduleName": "multinavbarsample", + "abilityName": "MultinavbarsampleAbility", + "order": 1 + } + ] + }, + { + "id": 18, + "categoryType": 1, + "order": 2, + "sampleDetail": [ + { + "id": 33, + "title": "一多便捷生活", + "desc": "本篇Sample基于自适应布局和响应式布局,实现一次开发,多端部署的便捷生活页面,并根据手机、折叠屏、平板以及2in1不同的设备尺寸实现对应页面。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/multidevice/sample_multi_convenient_life.png", + "originalUrl": "https://gitee.com/harmonyos_samples/multi-convenient-life/blob/br_release_hmosworld_new/README.md", + "moduleName": "multiconvinientlifesample", + "abilityName": "MulticonvinientlifesampleAbility", + "order": 1 + } + ] + }, + { + "id": 19, + "categoryType": 1, + "order": 3, + "sampleDetail": [ + { + "id": 34, + "title": "一多移动支付", + "desc": "本篇Sample基于Scan Kit中的默认界面扫码能力与码图生成能力实现移动支付应用中常见的扫一扫和收付款功能。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/multidevice/sample_multi_mobile_payment.png", + "originalUrl": "https://gitee.com/harmonyos_samples/multi-mobile-payment/blob/br_release_hmosworld_new/README.md", + "moduleName": "multimobilepaymentsample", + "abilityName": "MultimobilepaymentsampleAbility", + "order": 1 + } + ] + }, + { + "id": 21, + "categoryType": 1, + "order": 5, + "sampleDetail": [ + { + "id": 45, + "title": "一多新闻阅读", + "desc": "本示例基于自适应布局和响应式布局,实现一次开发,多端部署的新闻阅读页面。根据手机、折叠屏以及平板不同的设备尺寸实现对应页面。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/multidevice/sample_multi_news_read.png", + "originalUrl": "https://gitee.com/harmonyos_samples/multi-news-read/blob/br_release_hmosworld_new/README.md", + "moduleName": "multinewsreadsample", + "abilityName": "MultinewsreadsampleAbility", + "order": 1 + } + ] + }, + { + "id": 22, + "categoryType": 1, + "order": 6, + "sampleDetail": [ + { + "id": 55, + "title": "一多旅行住宿", + "desc": "本示例主要使用栅格布局和List组件相结合的方式,实现了旅行住宿差异化的多场景响应式变化效果。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/multidevice/sample_multi_travel_accommodation.png", + "originalUrl": "https://gitee.com/harmonyos_samples/multi-travel-accommodation/blob/br_release_hmosworld_new/README.md", + "moduleName": "multitravelsample", + "abilityName": "MultitravelsampleAbility", + "order": 1 + } + ] + }, + { + "id": 24, + "categoryType": 1, + "order": 8, + "sampleDetail": [ + { + "id": 57, + "title": "一多分栏控件", + "desc": "本示例通过使用SideBarContainer组件与Navigation组件,实现了多场景下,一多分栏控件的响应式变化效果。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/multidevice/sample_multi_columns.png", + "originalUrl": "https://gitee.com/harmonyos_samples/multi-columns/blob/br_release_hmosworld_new/README.md", + "moduleName": "multicolumnssample", + "abilityName": "MulticolumnssampleAbility", + "order": 1 + } + ] + }, + { + "id": 15, + "categoryType": 2, + "order": 1, + "sampleDetail": [ + { + "id": 20, + "title": "文字特效合集", + "desc": "本示例基于Text组件及通用属性实现多种文字特效。帮助开发者在ArkTS页面开发中实现文字渐变、歌词滚动、文字倒影、跑马灯渐变等多种文字效果。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/style/sample_text_effects.gif", + "originalUrl": "https://gitee.com/harmonyos_samples/text-effects/blob/br_release_hmosworld_new/README.md", + "moduleName": "texteffectssample", + "abilityName": "TexteffectssampleAbility", + "order": 1 + }, + { + "id": 35, + "title": "常见Tab导航样式合集", + "desc": "Tabs组件可以让用户能聚焦于当前显示的内容,对页面内容进行分类,提高页面空间利用率。本示例基于Tabs组件,为开发者提供不同场景下的导航样式,如:常见底部导航、舵式底部导航、可滑动+更多按钮样式、侧边导航等。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/style/sample_multi_tab_navigation.png", + "originalUrl": "https://gitee.com/harmonyos_samples/multi-tab-navigation/blob/br_release_hmosworld_new/README.md", + "moduleName": "multitabnavigationsample", + "abilityName": "MultitabnavigationsampleAbility", + "order": 2 + }, + { + "id": 36, + "title": "自定义弹窗合集", + "desc": "本示例通过CustomDialog、bindContentCover、bindSheet等接口,实现多种样式的弹窗。帮助开发者掌握自定义弹窗开发的步骤,灵活的实现自己业务需要用到的弹窗场景。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/style/sample_custom_dialog_gathers.png", + "originalUrl": "https://gitee.com/harmonyos_samples/custom-dialog-gathers/blob/br_release_hmosworld_new/README.md", + "moduleName": "customdialogsample", + "abilityName": "CustomdialogsampleAbility", + "order": 3 + } + ] + }, + { + "id": 6, + "categoryType": 2, + "order": 2, + "sampleDetail": [ + { + "id": 48, + "title": "Scroll组件嵌套滑动", + "desc": "本示例通过Scroll组件的滑动能力和List组件的nestedScroll属性,实现当Scroll嵌套List滑动时,优先滑动最外层的Scroll,当Scroll滑动至末端时,List再继续滚动。帮助开发者掌握Scroll嵌套List滑动时的场景如何处理。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/layout/sample_scroll_component_nested_sliding.png", + "originalUrl": "https://gitee.com/harmonyos_samples/scroll-component-nested-sliding/blob/br_release_hmosworld_new/README.md", + "moduleName": "nestedslidingsample", + "abilityName": "NestedslidingsampleAbility", + "order": 1 + }, + { + "id": 25, + "title": "WaterFlow瀑布流实例", + "desc": "本示例为开发者展示使用WaterFlow瀑布流容器实现首页布局效果,包括使用sections实现混排布局、结合item实现滑动吸顶、多种组件混合排列等场景。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/layout/sample_water_flow.png", + "originalUrl": "https://gitee.com/harmonyos_samples/water-flow/blob/br_release_hmosworld_new/README.md", + "moduleName": "waterflowsample", + "abilityName": "WaterflowsampleAbility", + "order": 2 + }, + { + "id": 43, + "title": "组件堆叠", + "desc": "本示例介绍运用Stack组件以构建多层次堆叠的视觉效果。通过绑定Scroll组件的onScrollFrameBegin滚动事件回调函数,精准捕获滚动动作的发生。当滚动时,实时地调节组件的透明度、高度等属性,从而成功实现了嵌套滚动效果、透明度动态变化以及平滑的组件切换。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/layout/sample_component_stack.png", + "originalUrl": "https://gitee.com/harmonyos_samples/component-stack/blob/br_release_hmosworld_new/README.md", + "moduleName": "componentstacksample", + "abilityName": "ComponentstacksampleAbility", + "order": 3 + }, + { + "id": 12, + "title": "基于Grid实现混合布局", + "desc": "本示例主要实现了Grid组件和List组件以及Swiper组件的嵌套混合布局。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/layout/sample_grid_hybrid.png", + "originalUrl": "https://gitee.com/harmonyos_samples/grid-hybrid/blob/br_release_hmosworld_new/README.md", + "moduleName": "gridhybridsample", + "abilityName": "GridhybridsampleAbility", + "order": 4 + } + ] + }, + { + "id": 3, + "categoryType": 2, + "order": 3, + "sampleDetail": [ + { + "id": 30, + "title": "转场动效合集", + "desc": "本示例基于基础组件、通用属性、显式动效,实现多模态页面转场动效以及多种常见一镜到底转场动效,便于用户进行常见的转场动效场景开发。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/effect/sample_transitions_collection.gif", + "originalUrl": "https://gitee.com/harmonyos_samples/transitions-collection/blob/br_release_hmosworld_new/README.md", + "moduleName": "transitionscollectionsample", + "abilityName": "TransitionscollectionsampleAbility", + "order": 1 + }, + { + "id": 9, + "title": "动效案例合集", + "desc": "本示例基于基础组件、通用属性、显式动效,实现多种常见动效案例,便于用户进行常见的动效场景开发。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/effect/sample_animation_collection.png", + "originalUrl": "https://gitee.com/harmonyos_samples/animation-collection/blob/br_release_hmosworld_new/README.md", + "moduleName": "animationcollectionsample", + "abilityName": "AnimationcollectionsampleAbility", + "order": 2 + }, + { + "id": 42, + "title": "拖拽框架开发实践", + "desc": "本示例基于ArkUI的拖拽框架,实现图片、富文本、文本、输入框、列表等组件的拖拽功能,通过设置拖拽事件中的接口信息自定义拖拽响应,实现拖拽图像增加水印、自定义拖拽背板图、AI识别拖拽内容等拖拽场景。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/effect/sample_drag_framework.png", + "originalUrl": "https://gitee.com/harmonyos_samples/DragFramework/blob/br_release_hmosworld_new/README.md", + "moduleName": "dragframeworksample", + "abilityName": "DragframeworksampleAbility", + "order": 3 + } + ] + }, + { + "id": 7, + "categoryType": 2, + "order": 4, + "sampleDetail": [ + { + "id": 24, + "title": "流畅刷文章", + "desc": "本示例实现了文章和媒体文件浏览的功能,通过设置组件的属性来控制屏幕刷新率,达到低功耗的目的,参考本示例可学习开发类似博客场景,并进行低功耗的适配。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/list/sample_fluent_blog.png", + "originalUrl": "https://gitee.com/harmonyos_samples/fluent-blog/blob/br_release_hmosworld_new/README.md", + "moduleName": "fluentblogsample", + "abilityName": "FluentblogsampleAbility", + "order": 1 + }, + { + "id": 14, + "title": "列表项交换", + "desc": "本示例介绍了如何通过组合手势结合List组件,来实现对List组件中列表项的交换排序。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/list/sample_list_exchange.png", + "originalUrl": "https://gitee.com/harmonyos_samples/list-exchange/blob/br_release_hmosworld_new/README.md", + "moduleName": "listexchangesample", + "abilityName": "ListexchangesampleAbility", + "order": 2 + }, + { + "id": 15, + "title": "列表编辑效果", + "desc": "本示例基于List组件,实现待办事项管理、文件管理、备忘录的等场景列表编辑效果。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/list/sample_list_item_edit.png", + "originalUrl": "https://gitee.com/harmonyos_samples/list-item-edit/blob/br_release_hmosworld_new/README.md", + "moduleName": "listitemeditsample", + "abilityName": "ListitemeditsampleAbility", + "order": 3 + } + ] + }, + { + "id": 1, + "categoryType": 3, + "order": 2, + "sampleDetail": [ + { + "id": 22, + "title": "画中画效果实现", + "desc": "本示例基于媒体服务和ArkUI的基本能力,实现视频播放、手动和自动拉起画中画、画中画窗口控制视频播放和暂停等功能。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/function/media/sample_window_pip.png", + "originalUrl": "https://gitee.com/harmonyos_samples/window-pip/blob/br_release_hmosworld_new/README.md", + "moduleName": "windowpipsample", + "abilityName": "WindowpipsampleAbility", + "order": 1 + }, + { + "id": 29, + "title": "多图片合集", + "desc": "本示例介绍了如何使用Swiper组件实现图片轮播效果,以及如何自定义Swiper组件的指示器,来实现图片的切换效果。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/function/media/sample_multiple_image.png", + "originalUrl": "https://gitee.com/harmonyos_samples/MultipleImage/blob/br_release_hmosworld_new/README.md", + "moduleName": "multipleimagesample", + "abilityName": "MultipleimagesampleAbility", + "order": 2 + }, + { + "id": 51, + "title": "基于AudioRenderer的音频播控和多场景交互", + "desc": "本场景解决方案主要面向前台音频开发人员。指导开发者基于AudioRenderer开发音频播控功能。功能包括后台播放、和播控中心的交互、适配不同类型的焦点打断策略、切换路由发声设备、切换输出设备等基础音频常见功能。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/function/media/sample_audio_interaction.png", + "originalUrl": "https://gitee.com/harmonyos_samples/audio-interaction/blob/br_release_hmosworld_new/README.md", + "moduleName": "audiointeractionsample", + "abilityName": "AudiointeractionsampleAbility", + "order": 3 + } + ] + }, + { + "id": 34, + "categoryType": 3, + "order": 3, + "sampleDetail": [ + { + "id": 26, + "title": "H5页面跳转", + "desc": "本示例基于ArkUI框架和Web实现了H5页面和ArkTS页面之间的相互跳转。帮助开发者在Web页面开发中掌握H5页面加载,H5页面跳转,H5页面与ArkTS页面参数传递等功能的实现方案。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/function/capacity/sample_page_redirection.png", + "originalUrl": "https://gitee.com/harmonyos_samples/page-redirection/blob/br_release_hmosworld_new/README.md", + "moduleName": "pageredirectionsample", + "abilityName": "PageredirectionsampleAbility", + "order": 1 + }, + { + "id": 31, + "title": "Web页面瞬开效果实践", + "desc": "本示例基于预渲染技术,实现了点击后Web页面瞬间打开的效果,无需额外加载过程,减少用户等待时长,提高了用户体验。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/function/capacity/sample_web_pre_render.png", + "originalUrl": "https://gitee.com/harmonyos_samples/web-pre-render/blob/br_release_hmosworld_new/README.md", + "moduleName": "webprerendersample", + "abilityName": "WebprerendersampleAbility", + "order": 2 + }, + { + "id": 49, + "title": "定位服务", + "desc": "本示例通过@kit.LocationKit中的geoLocationManager实现获取缓存位置、获取当前位置,同时运用map.Marker将位置信息标记在地图上。开发者可以在需要用到设备位置信息的开发场景中,如查看所在城市天气、出行打车、旅行导航以及观察运动轨迹等,集成本示例代码实现定位功能。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/function/capacity/sample_location_service.png", + "originalUrl": "https://gitee.com/harmonyos_samples/location-service/blob/br_release_hmosworld_new/README.md", + "moduleName": "locationservicesample", + "abilityName": "LocationservicesampleAbility", + "order": 3 + } + ] + }, + { + "id": 33, + "categoryType": 3, + "order": 5, + "sampleDetail": [ + { + "id": 17, + "title": "首选项", + "desc": "本示例使用@ohos.data.preferences接口,展示了使用首选项持久化存储数据的功能。帮助开发者实现主题切换且主题数据缓存读取的场景。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/function/data/sample_preferences.png", + "originalUrl": "https://gitee.com/harmonyos_samples/preferences/blob/br_release_hmosworld_new/README.md", + "moduleName": "preferencessample", + "abilityName": "PreferencessampleAbility", + "order": 1 + }, + { + "id": 21, + "title": "验证码场景合集", + "desc": "本示例实现了5种验证码场景,基本涵盖了大部分应用的验证码场景。开发者可按需下载代码,实现自己应用的验证码场景。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/scene/sample_verification_code_scenario.png", + "originalUrl": "https://gitee.com/harmonyos_samples/verification-code-scenario/blob/br_release_hmosworld_new/README.md", + "moduleName": "verificationcodescenariosample", + "abilityName": "VerificationcodescenariosampleAbility", + "order": 2 + }, + { + "id": 13, + "title": "发布图片评论", + "desc": "本示例通过拉起系统相机实现发布图片评论,便于用户了解系统相机接口的调用方式。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/scene/sample_image_comment.png", + "originalUrl": "https://gitee.com/harmonyos_samples/image-comment/blob/br_release_hmosworld_new/README.md", + "moduleName": "imagecommentsample", + "abilityName": "ImagecommentsampleAbility", + "order": 3 + }, + { + "id": 50, + "title": "选择并查看文档与媒体文件", + "desc": "应用使用@ohos.file.picker、@ohos.file.photoAccessHelper、@ohos.file.fs等接口,实现了拉起文档编辑保存、拉起系统相册图片查看、拉起视频并播放的功能。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/function/data/sample_picker.png", + "originalUrl": "https://gitee.com/harmonyos_samples/picker/blob/br_release_hmosworld_new/README.md", + "moduleName": "pickersample", + "abilityName": "PickersampleAbility", + "order": 4 + }, + { + "id": 47, + "title": "UI框架-软键盘弹出", + "desc": "本示例展示了输入框分别在屏幕顶部和底部时软键盘弹出对页面布局的影响,通过设置软键盘的避让模式为KeyboardAvoidMode.RESIZE、设置NavDestination的mode为NavDestinationMode.DIALOG等方式实现布局的避让,帮助开发者在多种软件盘弹出场景下实现合理的页面布局。", + "preInstalled": false, + "sampleType": "commonSample", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/scene/sample_keyboard.png", + "originalUrl": "https://gitee.com/harmonyos_samples/keyboard/blob/br_release_hmosworld_new/README.md", + "moduleName": "keyboardsample", + "abilityName": "KeyboardsampleAbility", + "order": 5 + } + ] + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/rawfile/mockdata/sample-page.json b/common/src/main/resources/rawfile/mockdata/sample-page.json new file mode 100644 index 0000000000000000000000000000000000000000..1bfb37b5a2ecdf1b6b15875350b455d0b693bd16 --- /dev/null +++ b/common/src/main/resources/rawfile/mockdata/sample-page.json @@ -0,0 +1,789 @@ +{ + "code": 200, + "message": "Success", + "data": { + "totalSize": "3", + "data": { + "bannerInfos": [ + { + "id": 2, + "bannerTitle": "HarmonyOS赋能套件", + "bannerSubTitle": "赋能套件介绍", + "bannerDesc": "打造开发者赋能产品,助力鸿蒙应用开发。", + "bannerType": 4, + "bannerValue": 15, + "mediaType": 1, + "mediaUrl": "image/banner/banner_arkui.png", + "detailsUrl": "bannercols/harmonyos-empowerment/index.html" + }, + { + "id": 6, + "bannerTitle": "多端UX设计", + "bannerSubTitle": "多端UX设计介绍", + "bannerDesc": "适配特征场景,助力高效设计适配。", + "bannerType": 4, + "bannerValue": 6, + "mediaType": 1, + "mediaUrl": "image/banner/banner_enabling_kit.png", + "detailsUrl": "articlecols/articles/native-ux-design/index.html" + }, + { + "id": 7, + "bannerTitle": "鸿蒙应用开发快速入门", + "bannerSubTitle": "快速入门介绍", + "bannerDesc": "快速上手HarmonyOS开发,轻松打造精美界面。", + "bannerType": 4, + "bannerValue": 17, + "mediaType": 1, + "mediaUrl": "image/banner/banner_quick_start.png", + "detailsUrl": "bannercols/quick-start/index.html" + } + ], + "sampleCategories": [ + { + "id": 4, + "categoryName": "2025 HDC", + "categoryType": 4, + "tabIcon": "image/common/hdc_tab_white.png", + "tabIconSelected": "image/common/hdc_tab_color.png", + "sampleCards": [ + { + "id": 60, + "cardTitle": "HarmonyOS代码工坊", + "cardStyleType": 5, + "cardImage": "image/common/sample_card_background_blue.png", + "version": 1000000, + "detailCardId": 35, + "sampleContents": [ + { + "id": 60, + "type": 2, + "cardId": 25, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/hmosworld_all_device.png", + "title": "HarmonyOS代码工坊", + "subTitle": "", + "tags": [ + "该案例已支持多设备" + ], + "order": 1 + } + ] + }, + { + "id": 63, + "cardTitle": "HarmonyOS代码工坊(手表版)", + "cardStyleType": 5, + "cardImage": "image/common/sample_card_background_green.png", + "version": 1000000, + "detailCardId": 35, + "sampleContents": [ + { + "id": 63, + "type": 2, + "cardId": 28, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/hmosworld_watch.png", + "title": "HarmonyOS代码工坊(手表版)", + "subTitle": "", + "tags": [ + "使用Watch5体验完整效果" + ], + "order": 1 + } + ] + }, + { + "id": 61, + "cardTitle": "三折叠,怎么折都有面", + "cardStyleType": 5, + "cardImage": "image/common/sample_card_background_pink.png", + "version": 1000000, + "detailCardId": 35, + "sampleContents": [ + { + "id": 61, + "type": 2, + "cardId": 26, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/sample_business.png", + "title": "三折叠,怎么折都有面", + "subTitle": "", + "tags": [ + "使用MateXT体验完整效果" + ], + "order": 1 + } + ] + }, + { + "id": 62, + "cardTitle": "扩感导航,视野更清晰", + "cardStyleType": 5, + "cardImage": "image/common/sample_card_background_green.png", + "version": 1000000, + "detailCardId": 35, + "sampleContents": [ + { + "id": 62, + "type": 2, + "cardId": 27, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/sample_liveviewlockscreen.png", + "title": "扩感导航,视野更清晰", + "subTitle": "", + "tags": [ + "使用PuarX体验完整效果" + ], + "order": 1 + } + ] + }, + { + "id": 64, + "cardTitle": "碰一碰视频快速分享", + "cardStyleType": 5, + "cardImage": "image/common/sample_card_background_pink.png", + "version": 1000000, + "detailCardId": 35, + "sampleContents": [ + { + "id": 64, + "type": 2, + "cardId": 29, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/sample_knockshare.png", + "title": "碰一碰视频快速分享", + "subTitle": "", + "tags": [ + "使用两台手机体验完整效果" + ], + "order": 1 + } + ] + }, + { + "id": 65, + "cardTitle": "视频投播更便捷", + "cardStyleType": 5, + "cardImage": "image/common/sample_card_background_blue.png", + "version": 1000000, + "detailCardId": 35, + "sampleContents": [ + { + "id": 65, + "type": 2, + "cardId": 30, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/sample_videocast.png", + "title": "视频投播更便捷", + "subTitle": "", + "tags": [ + "使用手机、PC体验完整效果" + ], + "order": 1 + } + ] + }, + { + "id": 66, + "cardTitle": "跨设备内容编辑新体验", + "cardStyleType": 5, + "cardImage": "image/common/sample_card_background_pink.png", + "version": 1000000, + "detailCardId": 35, + "sampleContents": [ + { + "id": 66, + "type": 2, + "cardId": 23, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/sample_continuepublic.png", + "title": "跨设备内容编辑新体验", + "subTitle": "", + "tags": [ + "分布式照相机", + "键鼠穿越" + ], + "order": 1 + } + ] + } + ] + }, + { + "id": 1, + "categoryName": "多设备开发", + "categoryType": 1, + "sampleCards": [ + { + "id": 22, + "cardTitle": "一多旅行住宿", + "cardStyleType": 4, + "cardImage": "image/common/sample_card_background.png", + "version": 1000000, + "order": 6, + "sampleContents": [ + { + "id": 55, + "type": 2, + "cardId": 22, + "mediaType": 1, + "mediaUrl": "image/sample/multidevice/sample_multi_travel_accommodation.png", + "title": "一多旅行住宿", + "subTitle": "本示例主要使用栅格布局和List组件相结合的方式,实现了旅行住宿差异化的多场景响应式变化效果。", + "tags": [ + "一次开发多端部署", + "Navigation" + ], + "order": 1 + } + ] + }, + { + "id": 19, + "cardTitle": "一多移动支付", + "cardStyleType": 4, + "cardImage": "image/common/sample_card_background.png", + "version": 1000000, + "order": 3, + "sampleContents": [ + { + "id": 34, + "type": 2, + "cardId": 19, + "mediaType": 1, + "mediaUrl": "image/sample/multidevice/sample_multi_mobile_payment.png", + "title": "一多移动支付", + "subTitle": "本篇Sample基于Scan Kit中的默认界面扫码能力与码图生成能力实现移动支付应用中常见的扫一扫和收付款功能。", + "tags": [ + "UI框架", + "扫码能力", + "码图生成" + ], + "order": 1 + } + ] + }, + { + "id": 18, + "cardTitle": "一多便捷生活", + "cardStyleType": 4, + "cardImage": "image/common/sample_card_background.png", + "version": 1000000, + "order": 2, + "sampleContents": [ + { + "id": 33, + "type": 2, + "cardId": 18, + "mediaType": 1, + "mediaUrl": "image/sample/multidevice/sample_multi_convenient_life.png", + "title": "一多便捷生活", + "subTitle": "本篇Sample基于自适应布局和响应式布局,实现一次开发,多端部署的便捷生活页面,并根据手机、折叠屏、平板以及2in1不同的设备尺寸实现对应页面。", + "tags": [ + "一次开发多端部署", + "自适应布局" + ], + "order": 1 + } + ] + }, + { + "id": 21, + "cardTitle": "一多新闻阅读", + "cardStyleType": 4, + "cardImage": "image/common/sample_card_background.png", + "version": 1000000, + "order": 5, + "sampleContents": [ + { + "id": 45, + "type": 2, + "cardId": 21, + "mediaType": 1, + "mediaUrl": "image/sample/multidevice/sample_multi_news_read.png", + "title": "一多新闻阅读", + "subTitle": "本示例基于自适应布局和响应式布局,实现一次开发,多端部署的新闻阅读页面。根据手机、折叠屏以及平板不同的设备尺寸实现对应页面。", + "tags": [ + "UI框架" + ], + "order": 1 + } + ] + }, + { + "id": 24, + "cardTitle": "一多分栏控件", + "cardStyleType": 4, + "cardImage": "image/common/sample_card_background.png", + "version": 1000000, + "order": 8, + "sampleContents": [ + { + "id": 57, + "type": 2, + "cardId": 24, + "mediaType": 1, + "mediaUrl": "image/sample/multidevice/sample_multi_columns.png", + "title": "一多分栏控件", + "subTitle": "本示例通过使用SideBarContainer组件与Navigation组件,实现了多场景下,一多分栏控件的响应式变化效果。", + "tags": [ + "一次开发多端部署", + "Navigation" + ], + "order": 1 + } + ] + }, + { + "id": 4, + "cardTitle": "一多导航栏", + "cardStyleType": 4, + "cardImage": "image/common/sample_card_background.png", + "version": 1000000, + "order": 1, + "sampleContents": [ + { + "id": 16, + "type": 2, + "cardId": 4, + "mediaType": 1, + "mediaUrl": "image/sample/multidevice/sample_multi_nav_bar.png", + "title": "一多导航栏", + "subTitle": "本示例基于自适应布局和响应式布局,实现多设备上的分级导航栏效果。在sm、md断点下,展示为底部页签和顶部页签;在lg断点下,展示为侧边页签和顶部页签;在xl断点下,展示为侧边栏分级导航。为开发者提供分级导航栏的开发方案。", + "tags": [ + "一次开发多端部署", + "自适应布局" + ], + "order": 1 + } + ] + } + ] + }, + { + "id": 2, + "categoryName": "ArkUI实践", + "categoryType": 2, + "sampleCards": [ + { + "id": 15, + "cardTitle": "组件样式", + "cardSubTitle": "组件样式Sample", + "cardStyleType": 2, + "version": 1000000, + "order": 1, + "sampleContents": [ + { + "id": 20, + "type": 2, + "cardId": 15, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/style/sample_text_effects.gif", + "title": "文字特效合集", + "subTitle": "本示例基于Text组件及通用属性实现多种文字特效。帮助开发者在ArkTS页面开发中实现文字渐变、歌词滚动、文字倒影、跑马灯渐变等多种文字效果。", + "tags": [ + "Text" + ], + "order": 1 + }, + { + "id": 35, + "type": 2, + "cardId": 15, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/style/sample_multi_tab_navigation.png", + "title": "常见Tab导航样式合集", + "subTitle": "Tabs组件可以让用户能聚焦于当前显示的内容,对页面内容进行分类,提高页面空间利用率。本示例基于Tabs组件,为开发者提供不同场景下的导航样式,如:常见底部导航、舵式底部导航、可滑动+更多按钮样式、侧边导航等。", + "tags": [ + "Tabs", + "TabContent", + "自定义Tab" + ], + "order": 2 + }, + { + "id": 36, + "type": 2, + "cardId": 15, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/style/sample_custom_dialog_gathers.png", + "title": "自定义弹窗合集", + "subTitle": "本示例通过CustomDialog、bindContentCover、bindSheet等接口,实现多种样式的弹窗。帮助开发者掌握自定义弹窗开发的步骤,灵活的实现自己业务需要用到的弹窗场景。", + "tags": [ + "CustomDialog", + "bindContentCover", + "bindSheet" + ], + "order": 3 + } + ] + }, + { + "id": 6, + "cardTitle": "布局", + "cardSubTitle": "布局类Sample", + "cardStyleType": 2, + "version": 1000000, + "order": 2, + "sampleContents": [ + { + "id": 48, + "type": 2, + "cardId": 6, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/layout/sample_scroll_component_nested_sliding.png", + "title": "Scroll组件嵌套滑动", + "subTitle": "本示例通过Scroll组件的滑动能力和List组件的nestedScroll属性,实现当Scroll嵌套List滑动时,优先滑动最外层的Scroll,当Scroll滑动至末端时,List再继续滚动。帮助开发者掌握Scroll嵌套List滑动时的场景如何处理。", + "tags": [ + "滑动", + "吸顶", + "Tabs", + "Scroll", + "List" + ], + "order": 1 + }, + { + "id": 25, + "type": 2, + "cardId": 6, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/layout/sample_water_flow.png", + "title": "WaterFlow瀑布流实例", + "subTitle": "本示例为开发者展示使用WaterFlow瀑布流容器实现首页布局效果,包括使用sections实现混排布局、结合item实现滑动吸顶、多种组件混合排列等场景。", + "tags": [ + "瀑布流", + "waterflow", + "吸顶" + ], + "order": 2 + }, + { + "id": 43, + "type": 2, + "cardId": 6, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/layout/sample_component_stack.png", + "title": "组件堆叠", + "subTitle": "本示例介绍运用Stack组件以构建多层次堆叠的视觉效果。通过绑定Scroll组件的onScrollFrameBegin滚动事件回调函数,精准捕获滚动动作的发生。当滚动时,实时地调节组件的透明度、高度等属性,从而成功实现了嵌套滚动效果、透明度动态变化以及平滑的组件切换。", + "tags": [ + "UI框架" + ], + "order": 3 + }, + { + "id": 12, + "type": 2, + "cardId": 6, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/layout/sample_grid_hybrid.png", + "title": "基于Grid实现混合布局", + "subTitle": "本示例主要实现了Grid组件和List组件以及Swiper组件的嵌套混合布局。", + "tags": [ + "Grid", + "List", + "Swiper" + ], + "order": 4 + } + ] + }, + { + "id": 3, + "cardTitle": "动效", + "cardSubTitle": "动效类Sample", + "cardStyleType": 2, + "version": 1000000, + "order": 3, + "sampleContents": [ + { + "id": 30, + "type": 2, + "cardId": 3, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/effect/sample_transitions_collection.gif", + "title": "转场动效合集", + "subTitle": "本示例基于基础组件、通用属性、显式动效,实现多模态页面转场动效以及多种常见一镜到底转场动效,便于用户进行常见的转场动效场景开发。", + "tags": [ + "转场动效", + "NavDestination" + ], + "order": 1 + }, + { + "id": 9, + "type": 2, + "cardId": 3, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/effect/sample_animation_collection.png", + "title": "动效案例合集", + "subTitle": "本示例基于基础组件、通用属性、显式动效,实现多种常见动效案例,便于用户进行常见的动效场景开发。", + "tags": [ + "UI框架", + "动效" + ], + "order": 2 + }, + { + "id": 42, + "type": 2, + "cardId": 3, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/effect/sample_drag_framework.png", + "title": "拖拽框架开发实践", + "subTitle": "本示例基于ArkUI的拖拽框架,实现图片、富文本、文本、输入框、列表等组件的拖拽功能,通过设置拖拽事件中的接口信息自定义拖拽响应,实现拖拽图像增加水印、自定义拖拽背板图、AI识别拖拽内容等拖拽场景。", + "tags": [ + "拖拽事件" + ], + "order": 3 + } + ] + }, + { + "id": 7, + "cardTitle": "列表开发实践", + "cardSubTitle": "列表开发实践类Sample", + "cardStyleType": 2, + "version": 1000000, + "order": 4, + "sampleContents": [ + { + "id": 24, + "type": 2, + "cardId": 7, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/list/sample_fluent_blog.png", + "title": "流畅刷文章", + "subTitle": "本示例实现了文章和媒体文件浏览的功能,通过设置组件的属性来控制屏幕刷新率,达到低功耗的目的,参考本示例可学习开发类似博客场景,并进行低功耗的适配。", + "tags": [ + "LTPO" + ], + "order": 1 + }, + { + "id": 14, + "type": 2, + "cardId": 7, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/list/sample_list_exchange.png", + "title": "列表项交换", + "subTitle": "本示例介绍了如何通过组合手势结合List组件,来实现对List组件中列表项的交换排序。", + "tags": [ + "List列表项交换", + "自定义手势" + ], + "order": 2 + }, + { + "id": 15, + "type": 2, + "cardId": 7, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/list/sample_list_item_edit.png", + "title": "列表编辑效果", + "subTitle": "本示例基于List组件,实现待办事项管理、文件管理、备忘录的等场景列表编辑效果。", + "tags": [ + "List" + ], + "order": 3 + } + ] + } + ] + }, + { + "id": 3, + "categoryName": "功能开发", + "categoryType": 3, + "sampleCards": [ + { + "id": 1, + "cardTitle": "多媒体", + "cardSubTitle": "多媒体类Sample", + "cardStyleType": 2, + "version": 1000000, + "order": 2, + "sampleContents": [ + { + "id": 22, + "type": 2, + "cardId": 1, + "mediaType": 1, + "mediaUrl": "image/sample/function/media/sample_window_pip.png", + "title": "画中画效果实现", + "subTitle": "本示例基于媒体服务和ArkUI的基本能力,实现视频播放、手动和自动拉起画中画、画中画窗口控制视频播放和暂停等功能。", + "tags": [ + "视频", + "播放", + "画中画" + ], + "order": 1 + }, + { + "id": 29, + "type": 2, + "cardId": 1, + "mediaType": 1, + "mediaUrl": "image/sample/function/media/sample_multiple_image.png", + "title": "多图片合集", + "subTitle": "本示例介绍了如何使用Swiper组件实现图片轮播效果,以及如何自定义Swiper组件的指示器,来实现图片的切换效果。", + "tags": [ + "多图片", + "多图文" + ], + "order": 2 + }, + { + "id": 51, + "type": 2, + "cardId": 1, + "mediaType": 1, + "mediaUrl": "image/sample/function/media/sample_audio_interaction.png", + "title": "基于AudioRenderer的音频播控和多场景交互", + "subTitle": "本场景解决方案主要面向前台音频开发人员。指导开发者基于AudioRenderer开发音频播控功能。功能包括后台播放、和播控中心的交互、适配不同类型的焦点打断策略、切换路由发声设备、切换输出设备等基础音频常见功能。", + "tags": [ + "音频播放;播控中心交互" + ], + "order": 3 + } + ] + }, + { + "id": 34, + "cardTitle": "开放能力", + "cardSubTitle": "开放能力类Sample", + "cardStyleType": 2, + "version": 1000000, + "order": 3, + "sampleContents": [ + { + "id": 26, + "type": 2, + "cardId": 34, + "mediaType": 1, + "mediaUrl": "image/sample/function/capacity/sample_page_redirection.png", + "title": "H5页面跳转", + "subTitle": "本示例基于ArkUI框架和Web实现了H5页面和ArkTS界面之间的相互跳转。帮助开发者在Web页面开发中掌握H5页面加载,H5页面跳转,H5页面与ArkTS页面参数传递等功能的实现方案。", + "tags": [ + "Web", + "javascript" + ], + "order": 1 + }, + { + "id": 31, + "type": 2, + "cardId": 34, + "mediaType": 1, + "mediaUrl": "image/sample/function/capacity/sample_web_pre_render.png", + "title": "Web页面瞬开效果实践", + "subTitle": "本示例基于预渲染技术,实现了点击后Web页面瞬间打开的效果,无需额外加载过程,减少用户等待时长,提高了用户体验。", + "tags": [ + "Web", + "预渲染", + "瞬开" + ], + "order": 2 + }, + { + "id": 49, + "type": 2, + "cardId": 34, + "mediaType": 1, + "mediaUrl": "image/sample/function/capacity/sample_location_service.png", + "title": "定位服务", + "subTitle": "本示例通过@kit.LocationKit中的geoLocationManager实现获取缓存位置、获取当前位置和持续定位功能,并结合@kit.BackgroundTasksKit中的backgroundTaskManager开启长时任务,实现后台定位功能,同时运用map.Marker将位置信息标记在地图上。", + "tags": [ + "定位" + ], + "order": 3 + } + ] + }, + { + "id": 33, + "cardTitle": "典型开发场景", + "cardSubTitle": "典型开发场景Sample", + "cardStyleType": 2, + "version": 1000000, + "order": 5, + "sampleContents": [ + { + "id": 17, + "type": 2, + "cardId": 33, + "mediaType": 1, + "mediaUrl": "image/sample/function/data/sample_preferences.png", + "title": "首选项", + "subTitle": "本示例使用@ohos.data.preferences接口,展示了使用首选项持久化存储数据的功能。帮助开发者实现主题切换且主题数据缓存读取的场景。", + "tags": [ + "首选项", + "持久化存储" + ], + "order": 1 + }, + { + "id": 21, + "type": 2, + "cardId": 33, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/scene/sample_verification_code_scenario.png", + "title": "验证码场景合集", + "subTitle": "本示例实现了5种验证码场景,基本涵盖了大部分应用的验证码场景。开发者可按需下载代码,实现自己应用的验证码场景。", + "tags": [ + "验证码", + "inputMethod", + "Slider" + ], + "order": 2 + }, + { + "id": 13, + "type": 2, + "cardId": 33, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/scene/sample_image_comment.png", + "title": "发布图片评论", + "subTitle": "本示例通过拉起系统相机实现发布图片评论,便于用户了解系统相机接口的调用方式。", + "tags": [ + "系统相机" + ], + "order": 3 + }, + { + "id": 50, + "type": 2, + "cardId": 33, + "mediaType": 1, + "mediaUrl": "image/sample/function/data/sample_picker.png", + "title": "选择并查看文档与媒体文件", + "subTitle": "应用使用@ohos.file.picker、@ohos.file.photoAccessHelper、@ohos.file.fs等接口,实现了拉起文档编辑保存、拉起系统相册图片查看、拉起视频并播放的功能。", + "tags": [ + "Picker" + ], + "order": 4 + }, + { + "id": 47, + "type": 2, + "cardId": 33, + "mediaType": 1, + "mediaUrl": "image/sample/arkui/scene/sample_keyboard.png", + "title": "UI框架-软键盘弹出", + "subTitle": "本示例展示了输入框分别在屏幕顶部和底部时软键盘弹出对页面布局的影响,通过设置软键盘的避让模式为KeyboardAvoidMode.RESIZE、设置NavDestination的mode为NavDestinationMode.DIALOG等方式实现布局的避让,帮助开发者在多种软件盘弹出场景下实现合理的页面布局。", + "tags": [ + "软键盘", + "输入框" + ], + "order": 5 + } + ] + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/rawfile/mockdata/wearable-sample-list.json b/common/src/main/resources/rawfile/mockdata/wearable-sample-list.json new file mode 100644 index 0000000000000000000000000000000000000000..2bb14e84204b2b484311fa851218c50a8c03cbc1 --- /dev/null +++ b/common/src/main/resources/rawfile/mockdata/wearable-sample-list.json @@ -0,0 +1,58 @@ +{ + "code": 200, + "message": "Success", + "data": [ + { + "id": 1, + "title": "音乐播放", + "desc": "样例1", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "app.media.wearable_sample_music", + "originalUrl": "https://gitee.com/zq-kexin/WearableMusic", + "moduleName": "wearablemusicsample", + "abilityName": "WearableMusicSampleAbility", + "order": 1, + "symbolGlyphColor": "#E02D50" + }, + { + "id": 2, + "title": "视频播放", + "desc": "样例2", + "isFavorite": false, + "mediaType": 3, + "mediaUrl": "sys.symbol.video_badge_adiowaves_fill", + "originalUrl": "https://gitee.com/lv-yuanyuan001/SmartWatchShortVideo", + "moduleName": "smartwatchshortvideosample", + "abilityName": "SmartWatchShortVideoSampleAbility", + "order": 1, + "symbolGlyphColor": "#FB6522" + }, + { + "id": 3, + "title": "地图导航", + "desc": "样例3", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "app.media.wearable_sample_map", + "originalUrl": "https://gitee.com/lv-yuanyuan001/SmartWatchMap", + "moduleName": "smartwatchmapsample", + "abilityName": "SmartWatchMapSampleAbility", + "order": 1, + "symbolGlyphColor": "#1F71FF" + }, + { + "id": 4, + "title": "远程控车", + "desc": "样例4", + "isFavorite": false, + "mediaType": 3, + "mediaUrl": "sys.symbol.car_fill", + "originalUrl": "https://gitee.com/dong-haifan/SmartWatchCarControl", + "moduleName": "smartwatchcarcontrolsample", + "abilityName": "SmartWatchCarControlSampleAbility", + "order": 1, + "symbolGlyphColor": "#00A5AD" + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/zh_CN/element/string.json b/common/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..d3d2e8cfd7f99939bb94045c45c7dffc2d2d8f8f --- /dev/null +++ b/common/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,24 @@ +{ + "string": [ + { + "name": "loading", + "value": "正在加载..." + }, + { + "name": "no_more", + "value": "已经滑至底部" + }, + { + "name": "network_error", + "value": "网络链接中断,请您检查网络设置或稍后重试。" + }, + { + "name": "server_error", + "value": "无法连接服务器,请稍后再试" + }, + { + "name": "network_setting", + "value": "设置网络" + } + ] +} \ No newline at end of file diff --git a/common/src/ohosTest/ets/test/Ability.test.ets b/common/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..85c78f67579d6e31b5f5aeea463e216b9b141048 --- /dev/null +++ b/common/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +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/common/src/ohosTest/ets/test/List.test.ets b/common/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..794c7dc4ed66bd98fa3865e07922906e2fcef545 --- /dev/null +++ b/common/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/common/src/ohosTest/module.json5 b/common/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..a71319f1b64f95ebe2583ce2b14b9d747e307f95 --- /dev/null +++ b/common/src/ohosTest/module.json5 @@ -0,0 +1,13 @@ +{ + "module": { + "name": "common_test", + "type": "feature", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/common/src/test/List.test.ets b/common/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..bb5b5c3731e283dd507c847560ee59bde477bbc7 --- /dev/null +++ b/common/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/common/src/test/LocalUnit.test.ets b/common/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..165fc1615ee8618b4cb6a622f144a9a707eee99f --- /dev/null +++ b/common/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +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/features/commonbusiness/.gitignore b/features/commonbusiness/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/features/commonbusiness/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/features/commonbusiness/Index.ets b/features/commonbusiness/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..4e326b7f470d0f73594209f75dbe7de58d217f4d --- /dev/null +++ b/features/commonbusiness/Index.ets @@ -0,0 +1,33 @@ +export { CardStyleTypeEnum, CardTypeEnum, MediaTypeEnum, CardData } from './src/main/ets/model/CardData'; + +export { BannerData, BannerTypeEnum, BANNER_SCALE_FACTOR, TITLE_SCALE_FACTOR } from './src/main/ets/model/BannerData'; + +export { FullScreenNavigationData } from './src/main/ets/model/FullScreenNavigationData'; + +export { ArticleDetailParams, ComponentDetailParams, SampleDetailParams } from './src/main/ets/model/RouterParams'; + +export { BannerCard } from './src/main/ets/component/BannerCard'; + +export { BannerItem } from './src/main/ets/component/BannerItem'; + +export { BaseDetailComponent } from './src/main/ets/component/BaseDetailComponent'; + +export { FullScreenNavigation } from './src/main/ets/component/FullScreenNavigation'; + +export { LoadingMoreItemBuilder } from './src/main/ets/component/LoadingMoreItem'; + +export { + BaseHomeViewModel, + BaseHomeEventType, + BaseHomeEventParam, + CalculateHeightParam, + OffsetParam +} from './src/main/ets/viewmodel/BaseHomeViewModel'; + +export { BannerSource } from './src/main/ets/viewmodel/BannerSource'; + +export { TabBarType, TAB_CONTENT_STATUSES } from './src/main/ets/model/TabStatusBarModel'; + +export { BaseHomeState, BannerState } from './src/main/ets/viewmodel/BaseHomeState'; + +export { BaseHomeView } from './src/main/ets/view/BaseHomeView'; \ No newline at end of file diff --git a/features/commonbusiness/build-profile.json5 b/features/commonbusiness/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..e6773f9f5d76a66d6d19fddc9c6ddb3f5621d3b1 --- /dev/null +++ b/features/commonbusiness/build-profile.json5 @@ -0,0 +1,31 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest" + } + ] +} diff --git a/features/commonbusiness/consumer-rules.txt b/features/commonbusiness/consumer-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..ef02b37be02b4542b3ebee2d684a56a924e90674 --- /dev/null +++ b/features/commonbusiness/consumer-rules.txt @@ -0,0 +1,4 @@ +-keep +./src/main/ets/model/BannerData.ets +./src/main/ets/model/CardData.ets +./src/main/ets/model/FullScreenNavigationData.ets\ \ No newline at end of file diff --git a/features/commonbusiness/hvigorfile.ts b/features/commonbusiness/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..42187071482d292588ad40babeda74f7b8d97a23 --- /dev/null +++ b/features/commonbusiness/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/features/commonbusiness/obfuscation-rules.txt b/features/commonbusiness/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/features/commonbusiness/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/features/commonbusiness/oh-package.json5 b/features/commonbusiness/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..6ecd40291316bba4640b326968610542095dc6f2 --- /dev/null +++ b/features/commonbusiness/oh-package.json5 @@ -0,0 +1,11 @@ +{ + "name": "commonbusiness", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@ohos/common": "file:../../common" + } +} diff --git a/features/commonbusiness/src/main/ets/component/BannerCard.ets b/features/commonbusiness/src/main/ets/component/BannerCard.ets new file mode 100644 index 0000000000000000000000000000000000000000..38a5f953b42c298f1e7d53997d1245c695ea401b --- /dev/null +++ b/features/commonbusiness/src/main/ets/component/BannerCard.ets @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2024 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 type { GlobalInfoModel } from '@ohos/common'; +import { BreakpointType, BreakpointTypeEnum, CommonConstants } from '@ohos/common'; +import type { BannerData } from '../model/BannerData'; +import type { BannerState } from '../viewmodel/BaseHomeState'; +import { BannerItem } from './BannerItem'; + +const ICON_HEIGHT = 32; +const ICON_PADDING = 24; +const BANNER_ASPECT = 1.75; +const BANNER_DOT_HEIGHT = 2; +const BANNER_DOT_SPACING = 8; + +@Component +export struct BannerCard { + @StorageProp('GlobalInfoModel') @Watch('handleBreakPointChange') globalInfoModel: GlobalInfoModel = + AppStorage.get('GlobalInfoModel')!; + @Prop @Require bannerState: BannerState; + @Prop @Require tabViewType: number; + handleItemClick?: (bannerData: BannerData) => void; + private bannerPadding: number = new BreakpointType({ + sm: CommonConstants.SPACE_16, + md: CommonConstants.SPACE_24, + lg: CommonConstants.SPACE_32 + }).getValue(this.globalInfoModel.currentBreakpoint); + private catchCount: number = this.bannerState.bannerResource.totalCount(); + private swiperController: SwiperController = new SwiperController(); + private scrollerController: Scroller = new Scroller(); + private bannerOffset: number = this.bannerState.bannerHeight * BANNER_ASPECT; + @State currentIndex: number = 0; + @State showLeftIcon: boolean = false; + @State showRightIcon: boolean = true; + @State bannerWidth: number = + (this.globalInfoModel.deviceWidth - this.bannerPadding * 2 - BANNER_DOT_SPACING * (this.catchCount - 1)) / + this.catchCount; + + handleBreakPointChange(): void { + this.bannerPadding = new BreakpointType({ + sm: CommonConstants.SPACE_16, + md: CommonConstants.SPACE_24, + lg: CommonConstants.SPACE_32 + }).getValue(this.globalInfoModel.currentBreakpoint); + if (this.globalInfoModel.currentBreakpoint !== BreakpointTypeEnum.LG && + this.globalInfoModel.currentBreakpoint !== BreakpointTypeEnum.XL) { + this.bannerWidth = + (this.globalInfoModel.deviceWidth - this.bannerPadding * 2 - BANNER_DOT_SPACING * (this.catchCount - 1)) / + this.catchCount; + } + } + + build() { + if (this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG || + this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL) { + Stack({ alignContent: Alignment.TopStart }) { + Scroll(this.scrollerController) { + Row({ space: CommonConstants.SPACE_16 }) { + LazyForEach(this.bannerState.bannerResource, (bannerData: BannerData) => { + Column() { + BannerItem({ bannerData: bannerData }) + .reuseId('banner_scroll') + .geometryTransition(CommonConstants.BANNER_GEOMETRY + bannerData.id.toString(), { follow: true }) + .transition(TransitionEffect.OPACITY) + .height('100%') + .aspectRatio(BANNER_ASPECT) + .onClick(() => { + this.handleItemClick?.(bannerData); + }) + } + }, (bannerData: BannerData) => bannerData.id.toString()) + } + .justifyContent(FlexAlign.Start) + .padding({ + left: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG ? + (CommonConstants.TAB_BAR_WIDTH + CommonConstants.SPACE_32) : $r('sys.float.padding_level16'), + right: $r('sys.float.padding_level16'), + }) + } + .onReachStart(() => { + this.showLeftIcon = false; + this.showRightIcon = true; + }) + .onReachEnd(() => { + this.showLeftIcon = true; + this.showRightIcon = false; + }) + .scrollBar(BarState.Off) + .edgeEffect(EdgeEffect.None) + .scrollable(ScrollDirection.Horizontal) + .height('100%') + .width('100%') + + if (this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL) { + Button({ type: ButtonType.Circle }) { + SymbolGlyph($r('sys.symbol.chevron_left')) + .fontSize($r('app.float.banner_icon_height')) + .fontColor([$r('sys.color.font_on_primary')]) + } + .backgroundColor($r('sys.color.mask_fourth')) + .height($r('app.float.banner_button_height')) + .width($r('app.float.banner_button_height')) + .margin({ + top: Math.floor((this.bannerState.bannerHeight - + (this.globalInfoModel.statusBarHeight + CommonConstants.NAVIGATION_HEIGHT + CommonConstants.SPACE_8) - + ICON_HEIGHT) / 2), + left: $r('sys.float.padding_level12') + }) + .visibility(this.showLeftIcon ? Visibility.Visible : Visibility.Hidden) + .onClick(() => { + this.scrollByOffset(-this.bannerOffset); + }) + + Button({ type: ButtonType.Circle }) { + SymbolGlyph($r('sys.symbol.chevron_right')) + .fontSize($r('app.float.banner_icon_height')) + .fontColor([$r('sys.color.font_on_primary')]) + } + .backgroundColor($r('sys.color.mask_fourth')) + .height($r('app.float.banner_button_height')) + .width($r('app.float.banner_button_height')) + .margin({ + top: Math.floor((this.bannerState.bannerHeight - + (this.globalInfoModel.statusBarHeight + CommonConstants.NAVIGATION_HEIGHT + CommonConstants.SPACE_8) - + ICON_HEIGHT) / 2), + left: this.globalInfoModel.deviceWidth - CommonConstants.SIDE_BAR_WIDTH - CommonConstants.SPACE_32 * 2 - + ICON_PADDING - ICON_HEIGHT, + }) + .position({ x: 0 }) + .visibility(this.showRightIcon ? Visibility.Visible : Visibility.Hidden) + .onClick(() => { + this.scrollByOffset(this.bannerOffset); + }) + } + } + .height(this.bannerState.bannerHeight) + .width('100%') + .padding({ + top: this.globalInfoModel.statusBarHeight + CommonConstants.NAVIGATION_HEIGHT + CommonConstants.SPACE_8, + }) + } else { + Swiper(this.swiperController) { + LazyForEach(this.bannerState.bannerResource, (bannerData: BannerData) => { + BannerItem({ bannerData: bannerData }) + .reuseId('banner_swiper') + .onClick(() => { + this.handleItemClick?.(bannerData); + }) + }, (bannerData: BannerData) => bannerData.id.toString()) + } + .onChange((index: number) => { + this.currentIndex = index; + }) + .cachedCount(this.catchCount) + .itemSpace(0) + .loop(true) + .autoPlay(this.catchCount > 1) + .effectMode(EdgeEffect.None) + .indicator(this.catchCount > 1 ? + new DotIndicator() + .itemWidth(this.bannerWidth) + .itemHeight(BANNER_DOT_HEIGHT) + .color($r('sys.color.icon_on_tertiary')) + .selectedItemWidth(this.bannerWidth) + .selectedItemHeight(BANNER_DOT_HEIGHT) + .selectedColor($r('sys.color.icon_on_primary')) : + false) + .width('100%') + .height(this.bannerState.bannerHeight) + .geometryTransition(CommonConstants.BANNER_GEOMETRY + this.tabViewType.toString(), { follow: true }) + .transition(TransitionEffect.OPACITY) + } + } + + private scrollByOffset(offset: number): void { + const currentOffset = this.scrollerController.currentOffset().xOffset; + this.scrollerController.scrollTo({ + xOffset: currentOffset + offset, yOffset: 0, animation: { + duration: 200, + } + }); + } +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/ets/component/BannerItem.ets b/features/commonbusiness/src/main/ets/component/BannerItem.ets new file mode 100644 index 0000000000000000000000000000000000000000..263a13bc6ba28626d8b3440dd2034cb78075dc39 --- /dev/null +++ b/features/commonbusiness/src/main/ets/component/BannerItem.ets @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024 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 { deviceInfo } from '@kit.BasicServicesKit'; +import type { GlobalInfoModel } from '@ohos/common'; +import { BreakpointType, ProductSeriesEnum } from '@ohos/common'; +import type { BannerData } from '../model/BannerData'; + +@Reusable +@Component +export struct BannerItem { + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @State bannerData?: BannerData = undefined; + + aboutToReuse(params: Record): void { + console.log('aboutToReuse'); + this.bannerData = params.bannerData; + } + + build() { + Stack({ alignContent: Alignment.Bottom }) { + Image($rawfile(this.bannerData?.mediaUrl)) + .alt($r('app.media.ic_placeholder')) + .objectFit(ImageFit.Cover) + .width('100%') + .height('100%') + Column() { + Text(this.bannerData?.bannerTitle) + .fontColor($r('sys.color.font_on_primary')) + .fontSize(deviceInfo.productSeries === ProductSeriesEnum.VDE ? $r('sys.float.Subtitle_M') : + $r('sys.float.Subtitle_L')) + .fontWeight(FontWeight.Bold) + Text(this.bannerData?.bannerSubTitle) + .margin({ top: $r('sys.float.padding_level3'), bottom: $r('sys.float.padding_level2') }) + .fontColor($r('sys.color.font_on_primary')) + .fontSize($r('sys.float.Body_S')) + .fontWeight(FontWeight.Medium) + Text(this.bannerData?.bannerDesc) + .fontColor($r('sys.color.font_on_secondary')) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontSize($r('sys.float.Body_S')) + .fontWeight(FontWeight.Medium) + } + .padding({ + top: $r('sys.float.padding_level6'), + left: new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level12'), + lg: $r('sys.float.padding_level8'), + }).getValue(this.globalInfoModel.currentBreakpoint), + right: new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level12'), + lg: $r('sys.float.padding_level8'), + }).getValue(this.globalInfoModel.currentBreakpoint), + bottom: new BreakpointType({ + sm: $r('sys.float.padding_level16'), + md: $r('sys.float.padding_level16'), + lg: $r('sys.float.padding_level8'), + }).getValue(this.globalInfoModel.currentBreakpoint), + }) + .height($r('app.float.banner_info_height')) + .width('100%') + .justifyContent(FlexAlign.End) + .alignItems(HorizontalAlign.Start) + } + .renderGroup(true) + .backgroundColor($r('sys.color.background_secondary')) + .borderRadius(new BreakpointType({ + sm: 0, + md: 0, + lg: $r('sys.float.corner_radius_level8'), + xl: $r('sys.float.corner_radius_level8'), + }).getValue(this.globalInfoModel.currentBreakpoint)) + .clip(true) + } +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/ets/component/BaseDetailComponent.ets b/features/commonbusiness/src/main/ets/component/BaseDetailComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..40520bb3bbf56612697bb987005cd324565197f9 --- /dev/null +++ b/features/commonbusiness/src/main/ets/component/BaseDetailComponent.ets @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 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 { GlobalInfoModel, LoadingFailedView, LoadingStatus, LoadingView, NoNetworkView } from '@ohos/common'; + +@Component +export struct BaseDetailComponent { + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @Prop loadingStatus: LoadingStatus; + @BuilderParam @Require detailContentView: () => void; + @BuilderParam @Require topTitleView: () => void; + reloadData?: Function; + + build() { + Stack({ alignContent: Alignment.TopStart }) { + if (this.loadingStatus !== LoadingStatus.IDLE) { + this.detailContentView() + } + if (this.loadingStatus === LoadingStatus.IDLE || this.loadingStatus === LoadingStatus.LOADING) { + LoadingView(this.globalInfoModel.currentBreakpoint) + } else if (this.loadingStatus === LoadingStatus.FAILED) { + LoadingFailedView(this.globalInfoModel.currentBreakpoint, () => { + this.reloadData?.(); + }) + } else if (this.loadingStatus === LoadingStatus.NO_NETWORK) { + NoNetworkView(this.globalInfoModel.currentBreakpoint, () => { + this.reloadData?.(); + }) + } + this.topTitleView() + } + .backgroundColor(Color.Transparent) + } +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/ets/component/FullScreenNavigation.ets b/features/commonbusiness/src/main/ets/component/FullScreenNavigation.ets new file mode 100644 index 0000000000000000000000000000000000000000..b3bc0a1dac70d1ca69ce3f94f3aead1ba5721a0b --- /dev/null +++ b/features/commonbusiness/src/main/ets/component/FullScreenNavigation.ets @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024 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 type { GlobalInfoModel } from '@ohos/common'; +import { BreakpointType, BreakpointTypeEnum, CommonConstants } from '@ohos/common'; +import { FullScreenNavigationData } from '../model/FullScreenNavigationData'; + +@Component +export struct FullScreenNavigation { + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @Prop topNavigationData: FullScreenNavigationData = new FullScreenNavigationData(); + @StorageProp('BlurRenderGroup') blurRenderGroup: boolean = false; + @BuilderParam tabView?: () => void; + + build() { + Column() { + Row() { + Text(this.topNavigationData.title) + .scale({ + x: this.topNavigationData.titleScale, + y: this.topNavigationData.titleScale, + z: this.topNavigationData.titleScale, + centerX: 0, + centerY: 0, + }) + .fontSize(new BreakpointType({ + sm: $r('sys.float.Title_M'), + md: $r('sys.float.Title_L'), + lg: $r('sys.float.Title_L'), + xl: $r('sys.float.Title_S'), + }).getValue(this.globalInfoModel.currentBreakpoint)) + .fontColor(this.topNavigationData.titleColor) + .fontWeight(FontWeight.Bold) + .textAlign(TextAlign.Start) + .layoutWeight(1) + } + .margin({ top: this.topNavigationData.titleOffsetY }) + .alignItems(VerticalAlign.Center) + .height(CommonConstants.NAVIGATION_HEIGHT) + .padding({ + left: new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level12'), + lg: $r('sys.float.padding_level16'), + }).getValue(this.globalInfoModel.currentBreakpoint), + right: new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level12'), + lg: $r('sys.float.padding_level16'), + }).getValue(this.globalInfoModel.currentBreakpoint), + }) + + if (this.tabView && this.topNavigationData.showTab) { + this.tabView() + } + Divider() + .color($r('sys.color.comp_divider')) + .visibility(this.topNavigationData.isBlur ? Visibility.Visible : Visibility.Hidden) + } + .backgroundBlurStyle(this.topNavigationData.isBlur ? BlurStyle.COMPONENT_THICK : undefined) + .backgroundColor(Color.Transparent) + .renderGroup(this.blurRenderGroup) + .padding({ + top: this.globalInfoModel.statusBarHeight, + left: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG ? CommonConstants.TAB_BAR_WIDTH : 0, + }) + .width('100%') + } +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/ets/component/LoadingMoreItem.ets b/features/commonbusiness/src/main/ets/component/LoadingMoreItem.ets new file mode 100644 index 0000000000000000000000000000000000000000..eb5e4301a2c0267deb4982527b7847ceaf6eccd8 --- /dev/null +++ b/features/commonbusiness/src/main/ets/component/LoadingMoreItem.ets @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 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 type { LoadingModel } from '@ohos/common'; +import { LoadingMore, LoadingStatus, NoMore } from '@ohos/common'; + +@Builder +export function LoadingMoreItemBuilder(loadingModel: LoadingModel) { + Column() { + if (loadingModel.loadingMoreStatus === LoadingStatus.LOADING) { + LoadingMore() + } else if (!loadingModel.hasNextPage) { + NoMore() + } + } + .padding({ left: $r('sys.float.padding_level8'), right: $r('sys.float.padding_level8') }) +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/ets/model/BannerData.ets b/features/commonbusiness/src/main/ets/model/BannerData.ets new file mode 100644 index 0000000000000000000000000000000000000000..2e67171db47dd34db0ef7d305c1416cd6ac1d62c --- /dev/null +++ b/features/commonbusiness/src/main/ets/model/BannerData.ets @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 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 { MediaTypeEnum } from './CardData'; + +@Observed +export class BannerData { + public id: number = 0; + public bannerTitle: string = ''; + public bannerSubTitle: string = ''; + public bannerDesc: string = ''; + public bannerType: BannerTypeEnum = 0; + public bannerValue: string = ''; + public mediaType: MediaTypeEnum = MediaTypeEnum.IMAGE; + public mediaUrl: string = ''; + public detailsUrl: string = ''; + public tabViewType: number = -1; +} + +export enum BannerTypeEnum { + COMPONENT = 1, + SAMPLE = 2, + CODELAB = 3, + ARTICLE = 4, + UNKNOWN = 0, +} + +// Banner magnification effect factor. +export const BANNER_SCALE_FACTOR: number = 3.00; +// Title magnification effect factor. +export const TITLE_SCALE_FACTOR: number = 6.00; \ No newline at end of file diff --git a/features/commonbusiness/src/main/ets/model/CardData.ets b/features/commonbusiness/src/main/ets/model/CardData.ets new file mode 100644 index 0000000000000000000000000000000000000000..d0abb5364985e20b1cc62c37a595154f3f8fdd52 --- /dev/null +++ b/features/commonbusiness/src/main/ets/model/CardData.ets @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 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 CardContent { + public id: number = 0; + public type: CardTypeEnum = CardTypeEnum.UNKNOWN; + public mediaType: MediaTypeEnum = MediaTypeEnum.IMAGE; + public mediaUrl: string = ''; + public title: string = ''; + public subTitle: string = ''; + public desc: string = ''; + public detailsUrl?: string = ''; + public originalUrl?: string = ''; +} + +export class CardData { + public id: number = 0; + public cardTitle: string = ''; + public cardSubTitle: string = ''; + public cardType: CardTypeEnum = CardTypeEnum.UNKNOWN; + public cardStyleType: CardStyleTypeEnum = CardStyleTypeEnum.LIST; + public cardImage: string = ''; + public version: string = ''; + public cardContents: CardContent[] = []; +} + +export enum CardStyleTypeEnum { + PICTURE_ABOVE_LIST = 1, + LIST = 2, + PICTURE = 3, + PICTURE_ABOVE_TEXT = 4, + PICTURE_TO_SWIPER = 5, +} + +export enum MediaTypeEnum { + IMAGE = 1, + VIDEO = 2, + SYMBOL = 3, +} + +export enum CardTypeEnum { + COMPONENT = 1, + SAMPLE = 2, + CODELAB = 3, + ARTICLE = 4, + UNKNOWN = 0, +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/ets/model/FullScreenNavigationData.ets b/features/commonbusiness/src/main/ets/model/FullScreenNavigationData.ets new file mode 100644 index 0000000000000000000000000000000000000000..30c3362c270f49a99c27451082e42a14787e61a3 --- /dev/null +++ b/features/commonbusiness/src/main/ets/model/FullScreenNavigationData.ets @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 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 { TopNavigationData } from '@ohos/common'; + +export class FullScreenNavigationData extends TopNavigationData { + public titleOffsetY: number = 0; + public titleScale: number = 1; + public showTab: boolean = false; +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/ets/model/RouterParams.ets b/features/commonbusiness/src/main/ets/model/RouterParams.ets new file mode 100644 index 0000000000000000000000000000000000000000..af5e53749f237dddc3416f89c1e8926f410b81d9 --- /dev/null +++ b/features/commonbusiness/src/main/ets/model/RouterParams.ets @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 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 ComponentDetailParams { + componentName: string; + componentId: number; +} + +export interface SampleDetailParams { + currentIndex: number; + sampleCardId: number; +} + +export interface ArticleDetailParams { + id: number; + isArticle: boolean; + title: string; + detailsUrl: string; + tabViewType?: number; +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/ets/model/TabStatusBarModel.ets b/features/commonbusiness/src/main/ets/model/TabStatusBarModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..b6c1a9f9e0d60f8298b7a254b110d7f63f29f192 --- /dev/null +++ b/features/commonbusiness/src/main/ets/model/TabStatusBarModel.ets @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 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. + */ + +// TabContent views is dark StatusBar or not. +export const TAB_CONTENT_STATUSES: boolean[] = [true, true, true]; + +export enum TabBarType { + HOME = 0, + SAMPLE = 1, + PRACTICE = 2, + MINE = 3, +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/ets/view/BaseHomeView.ets b/features/commonbusiness/src/main/ets/view/BaseHomeView.ets new file mode 100644 index 0000000000000000000000000000000000000000..88df38bc54129283228037209c31a5239d17a279 --- /dev/null +++ b/features/commonbusiness/src/main/ets/view/BaseHomeView.ets @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 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 type { GlobalInfoModel, LoadingModel } from '@ohos/common'; +import { LoadingFailedView, LoadingStatus, LoadingView, NoNetworkView } from '@ohos/common'; + +@Component +export struct BaseHomeView { + @Prop @Require loadingModel: LoadingModel; + @BuilderParam @Require contentView: () => void; + @BuilderParam @Require topTitleView: () => void; + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + reloadData?: Function; + + build() { + Stack({ alignContent: Alignment.TopStart }) { + if (this.loadingModel.loadingStatus === LoadingStatus.SUCCESS) { + this.contentView() + } else if (this.loadingModel.loadingStatus === LoadingStatus.FAILED) { + LoadingFailedView(this.globalInfoModel.currentBreakpoint, () => { + this.reloadData?.(); + }) + } else if (this.loadingModel.loadingStatus === LoadingStatus.LOADING) { + LoadingView(this.globalInfoModel.currentBreakpoint) + + } else if (this.loadingModel.loadingStatus === LoadingStatus.NO_NETWORK) { + NoNetworkView(this.globalInfoModel.currentBreakpoint, () => { + this.reloadData?.(); + }) + } + this.topTitleView() + } + .backgroundColor($r('sys.color.background_secondary')) + .width('100%') + .height('100%') + } +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/ets/viewmodel/BannerSource.ets b/features/commonbusiness/src/main/ets/viewmodel/BannerSource.ets new file mode 100644 index 0000000000000000000000000000000000000000..78f034dfed321d69dfa76a06e1458670bb110ddd --- /dev/null +++ b/features/commonbusiness/src/main/ets/viewmodel/BannerSource.ets @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 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 type { BannerData } from '../model/BannerData'; + +export class BannerSource implements IDataSource { + private splashArray: BannerData[] = []; + private listeners: DataChangeListener[] = []; + + public setDataArray(dataArray: BannerData[]): void { + this.splashArray = dataArray; + } + + public totalCount(): number { + return this.splashArray.length; + } + + public getData(index: number): BannerData { + return this.splashArray[index]; + } + + public registerDataChangeListener(listener: DataChangeListener): void { + if (this.listeners.indexOf(listener) < 0) { + this.listeners.push(listener); + } + } + + public unregisterDataChangeListener(listener: DataChangeListener): void { + const pos: number = this.listeners.indexOf(listener); + if (pos >= 0) { + this.listeners.splice(pos, 1); + } + } +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/ets/viewmodel/BaseHomeState.ets b/features/commonbusiness/src/main/ets/viewmodel/BaseHomeState.ets new file mode 100644 index 0000000000000000000000000000000000000000..225596fb978072f8708a3ba66ef0cba45ed82c76 --- /dev/null +++ b/features/commonbusiness/src/main/ets/viewmodel/BaseHomeState.ets @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 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 { BaseState, LoadingModel } from '@ohos/common'; +import { FullScreenNavigationData } from '../model/FullScreenNavigationData'; +import { BannerSource } from './BannerSource'; + +@Observed +export class BaseHomeState extends BaseState { + public loadingModel: LoadingModel = new LoadingModel(); + public topNavigationData: FullScreenNavigationData = new FullScreenNavigationData(); + public currentScrollState: ScrollState = ScrollState.Idle; + public bannerState: BannerState = new BannerState(); + public bannerHeight: number = 0; + public currentPage: number = 1; + public hasEdgeEffect: boolean = false; + + public constructor() { + super(); + } +} + +@Observed +export class BannerState { + public bannerHeight: number = 0; + public bannerResource: BannerSource = new BannerSource(); +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/ets/viewmodel/BaseHomeViewModel.ets b/features/commonbusiness/src/main/ets/viewmodel/BaseHomeViewModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..d74347bf7aa2cda4b0c819d10087c9f9d62a557c --- /dev/null +++ b/features/commonbusiness/src/main/ets/viewmodel/BaseHomeViewModel.ets @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2024 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 { ConfigurationConstant } from '@kit.AbilityKit'; +import { curves } from '@kit.ArkUI'; +import { deviceInfo } from '@kit.BasicServicesKit'; +import type { GlobalInfoModel } from '@ohos/common'; +import { + BaseVM, + BreakpointType, + BreakpointTypeEnum, + CommonConstants, + Logger, + PageContext, + ProductSeriesEnum, + StatusBarColorType, + WindowUtil, +} from '@ohos/common'; +import type { BannerData } from '../model/BannerData'; +import { BANNER_SCALE_FACTOR, BannerTypeEnum, TITLE_SCALE_FACTOR } from '../model/BannerData'; +import type { ArticleDetailParams, ComponentDetailParams, SampleDetailParams } from '../model/RouterParams'; +import { TAB_CONTENT_STATUSES, TabBarType } from '../model/TabStatusBarModel'; +import type { BaseHomeState } from './BaseHomeState'; + +const TAG = '[BaseHomeViewModel]'; +const BANNER_HEIGHT_LG = 242; +const TITLE_MAX_SCALE = 1.1; +const TITLE_MIN_SCALE = 1; +const TITLE_OFFSET_FACTOR = 0.01; + +@Observed +export class BaseHomeViewModel extends BaseVM { + protected state: T; + protected readonly pageSize: number = 30; + private originBannerHeight: number = 0; + private springBackAnimation: curves.ICurve = curves.interpolatingSpring(0, 1, 288, 30); + + public constructor(initialState: T) { + super(initialState); + this.state = initialState; + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + const isLargeWidth: boolean = (globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG || + globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL); + this.state.topNavigationData.bgColor = isLargeWidth ? '#FFF1F3F5' : '#00FFFFFF'; + this.state.topNavigationData.titleColor = isLargeWidth ? StatusBarColorType.BLACK : StatusBarColorType.WHITE; + const naviTitleHeight: number = + globalInfoModel.statusBarHeight + CommonConstants.NAVIGATION_HEIGHT + CommonConstants.SPACE_8; + this.state.bannerState.bannerHeight = new BreakpointType({ + sm: deviceInfo.productSeries === ProductSeriesEnum.VDE ? + CommonConstants.BANNER_ASPECT_VERDE * globalInfoModel.deviceWidth : + CommonConstants.BANNER_ASPECT_SM * globalInfoModel.deviceWidth, + md: CommonConstants.BANNER_ASPECT_MD * globalInfoModel.deviceWidth, + lg: BANNER_HEIGHT_LG + naviTitleHeight, + xl: BANNER_HEIGHT_LG + naviTitleHeight, + }).getValue(globalInfoModel.currentBreakpoint); + this.state.bannerHeight = this.state.bannerState.bannerHeight; + this.originBannerHeight = this.state.bannerHeight; + } + + public getState(): T { + return this.state; + } + + public sendEvent

(eventParam: BaseHomeEventParam

): boolean | void { + const eventType: BaseHomeEventType = eventParam.type; + if (eventType === BaseHomeEventType.JUMP_BANNER_DETAIL) { + return this.jumpBannerDetail(eventParam.param as BannerData); + } else if (eventType === BaseHomeEventType.HANDLE_SCROLL_OFFSET) { + return this.handleListOffset(eventParam.param as OffsetParam); + } else if (eventType === BaseHomeEventType.CALCULATE_BANNER_HEIGHT) { + return this.calculateBannerHeight(eventParam.param as CalculateHeightParam); + } else if (eventType === BaseHomeEventType.CHANGE_BANNER_HEIGHT) { + return this.changeBannerHeight(); + } else if (eventType === BaseHomeEventType.HANDLE_BREAKPOINT_CHANGE) { + return this.handleBreakpointChange(eventParam.param as OffsetParam); + } else if (eventType === BaseHomeEventType.HANDLE_COLOR_CHANGE) { + return this.handleColorModeChange(eventParam.param as OffsetParam); + } + return false; + } + + protected calculateBannerHeight(param: CalculateHeightParam): boolean { + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + if (param.yOffset === 0) { + this.state.hasEdgeEffect = false; + } else { + this.state.hasEdgeEffect = param.offset > 0; + } + if (globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG || + globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL || param.yOffset > 0) { + return false; + } + if (param.state === ScrollState.Scroll && param.offset !== 0) { + this.state.currentScrollState = ScrollState.Scroll; + const currentBannerHeight: number = this.state.bannerHeight - param.offset; + if (currentBannerHeight < this.originBannerHeight) { + this.state.currentScrollState = ScrollState.Fling; + this.state.bannerState.bannerHeight = this.originBannerHeight; + this.state.bannerHeight = this.state.bannerState.bannerHeight; + return false; + } + const bannerOffset: number = -(param.offset / BANNER_SCALE_FACTOR); + this.state.bannerState.bannerHeight += bannerOffset; + this.state.bannerHeight = this.state.bannerState.bannerHeight; + const titleOffset: number = bannerOffset / TITLE_SCALE_FACTOR; + const titleOffsetY: number = this.state.topNavigationData.titleOffsetY + titleOffset; + + this.state.topNavigationData.titleOffsetY = titleOffsetY > 0 ? titleOffsetY : 0; + let titleScale = this.state.topNavigationData.titleScale + TITLE_OFFSET_FACTOR * titleOffset; + if (titleScale > TITLE_MAX_SCALE) { + titleScale = TITLE_MAX_SCALE; + } else if (titleScale < TITLE_MIN_SCALE) { + titleScale = TITLE_MIN_SCALE; + } + this.state.topNavigationData.titleScale = titleScale; + return true; + } else if (this.state.currentScrollState === ScrollState.Scroll && param.state === ScrollState.Fling) { + this.bannerSpringBack(); + return true; + } + return false; + } + + protected handleBreakpointChange(offsetParam: OffsetParam) { + this.changeBannerHeight(); + offsetParam.breakpointChange = true; + this.handleListOffset(offsetParam); + } + + protected handleColorModeChange(offsetParam: OffsetParam) { + const pageContext: PageContext = AppStorage.get('pageContext') as PageContext; + if (pageContext.navPathStack.size() > 1) { + return; + } + const isDark: boolean = AppStorage.get('systemColorMode') === ConfigurationConstant.ColorMode.COLOR_MODE_DARK; + if (isDark) { + this.state.topNavigationData.titleColor = `rgba(255,255,255, 1)`; + } else { + offsetParam.breakpointChange = true; + this.handleListOffset(offsetParam); + } + } + + protected changeBannerHeight(): void { + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + const naviTitleHeight: number = + globalInfoModel.statusBarHeight + CommonConstants.NAVIGATION_HEIGHT + CommonConstants.SPACE_8; + this.state.bannerState.bannerHeight = new BreakpointType({ + sm: (deviceInfo.productSeries === ProductSeriesEnum.VDE) ? + CommonConstants.BANNER_ASPECT_VERDE * globalInfoModel.deviceWidth : + CommonConstants.BANNER_ASPECT_SM * globalInfoModel.deviceWidth, + md: CommonConstants.BANNER_ASPECT_MD * globalInfoModel.deviceWidth, + lg: BANNER_HEIGHT_LG + naviTitleHeight, + xl: CommonConstants.BANNER_ASPECT_XL * + Math.floor((globalInfoModel.deviceWidth - CommonConstants.SIDE_BAR_WIDTH) / 2.7) + naviTitleHeight, + }).getValue(globalInfoModel.currentBreakpoint); + this.state.bannerHeight = this.state.bannerState.bannerHeight; + this.originBannerHeight = this.state.bannerHeight; + } + + protected handleListOffset(offsetParam: OffsetParam): void { + Logger.debug(TAG, `onDidScroll: ${JSON.stringify(offsetParam)}`); + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + const isDark: boolean = AppStorage.get('systemColorMode') === ConfigurationConstant.ColorMode.COLOR_MODE_DARK; + const pageContext: PageContext = AppStorage.get('pageContext') as PageContext; + // Calculate whether to display the sample category tabBar. + if (offsetParam.tabIndex === TabBarType.SAMPLE) { + const marginTop: number = globalInfoModel.statusBarHeight + CommonConstants.NAVIGATION_HEIGHT; + const showTabOffset: number = this.state.bannerState.bannerHeight - marginTop; + this.state.topNavigationData.showTab = (offsetParam.yOffset >= showTabOffset); + } + + if (offsetParam.yOffset > this.state.bannerState.bannerHeight || + globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG || + globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL) { + if (offsetParam.breakpointChange) { + this.state.topNavigationData.isBlur = true; + if (offsetParam.tabIndex === AppStorage.get('currentTabIndex') && pageContext.navPathStack.size() === 1) { + WindowUtil.updateStatusBarColor(getContext(), isDark); + } + TAB_CONTENT_STATUSES[offsetParam.tabIndex] = false; + let colorData: number = 255; + if (!isDark) { + colorData = 0; + } + this.state.topNavigationData.titleColor = `rgba(${colorData},${colorData},${colorData}, 1)`; + } + if (offsetParam.yOffset >= CommonConstants.SPACE_8) { + this.state.topNavigationData.isBlur = true; + } else { + this.state.topNavigationData.isBlur = false; + } + return; + } + + if (offsetParam.yOffset === 0 && this.state.currentScrollState === 1) { + this.bannerSpringBack(); + } + const bannerHeight: number = this.state.bannerHeight - + (CommonConstants.NAVIGATION_HEIGHT + globalInfoModel.statusBarHeight) * 2; + const yOffset: number = Math.abs(offsetParam.yOffset || 0); + let opacity: number = yOffset >= bannerHeight ? (yOffset - bannerHeight) / CommonConstants.NAVIGATION_HEIGHT : 0; + if (opacity >= 1) { + opacity = 1; + } + if (opacity > 0) { + this.state.topNavigationData.isBlur = true; + TAB_CONTENT_STATUSES[offsetParam.tabIndex] = false; + } else { + this.state.topNavigationData.isBlur = false; + TAB_CONTENT_STATUSES[offsetParam.tabIndex] = true; + } + if (offsetParam.tabIndex === AppStorage.get('currentTabIndex') && pageContext.navPathStack.size() === 1) { + WindowUtil.updateStatusBarColor(getContext(), opacity > 0 ? isDark : true); + } + let colorData: number = 255; + if (!isDark) { + colorData = 255 - opacity * 255; + } + this.state.topNavigationData.titleColor = `rgba(${colorData},${colorData},${colorData}, 1)`; + } + + protected jumpBannerDetail(banner: BannerData): void { + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + const pageContext: PageContext = AppStorage.get('pageContext') as PageContext; + if (banner.bannerType === BannerTypeEnum.COMPONENT) { + const componentPageContext: PageContext = + globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? AppStorage.get('componentListPageContext')! : + pageContext; + const componentDetailParam: ComponentDetailParams = { + componentName: banner.bannerTitle, + componentId: Number(banner.bannerValue), + }; + componentPageContext.openPage({ + routerName: 'ComponentDetailView', + param: componentDetailParam, + }, true); + } else if (banner.bannerType === BannerTypeEnum.SAMPLE) { + const samplePageContext: PageContext = + globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? AppStorage.get('samplePageContext')! : + pageContext; + const sampleParam: SampleDetailParams = { + currentIndex: 0, + sampleCardId: Number(banner.bannerValue), + }; + samplePageContext.openPage({ + routerName: 'SampleDetailView', + param: sampleParam, + }, true); + + } else if (banner.bannerType === BannerTypeEnum.ARTICLE) { + // Get the current path stack. + let currentPageContext: PageContext = pageContext; + if (globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL) { + if (banner.tabViewType === TabBarType.HOME) { + currentPageContext = AppStorage.get('componentListPageContext')!; + } else if (banner.tabViewType === TabBarType.SAMPLE) { + currentPageContext = AppStorage.get('samplePageContext')!; + } else { + currentPageContext = AppStorage.get('explorationPageContext')!; + } + } + const articleParam: ArticleDetailParams = { + id: Number(banner.bannerValue), + title: banner.bannerTitle, + detailsUrl: banner.detailsUrl, + isArticle: false, + tabViewType: globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG ? banner.id : banner.tabViewType, + }; + animateTo({ curve: curves.interpolatingSpring(0, 1, 273, 33) }, () => { + currentPageContext.openPage({ + routerName: 'BannerDetailView', + param: articleParam, + }, false); + }); + } + } + + private bannerSpringBack() { + this.state.currentScrollState = 2; + animateTo({ curve: this.springBackAnimation, duration: 250 }, () => { + this.changeBannerHeight(); + this.state.topNavigationData.titleOffsetY = 0; + this.state.topNavigationData.titleScale = 1; + }); + } +} + +export interface CalculateHeightParam { + yOffset: number; + offset: number; + state: ScrollState; +} + +export interface OffsetParam { + yOffset: number; + tabIndex: number; + breakpointChange?: boolean; // Indicates wether to process breakpoint change. +} + +export enum BaseHomeEventType { + JUMP_BANNER_DETAIL = 'jumpBannerDetail', + HANDLE_SCROLL_OFFSET = 'handleScrollOffset', + CALCULATE_BANNER_HEIGHT = 'calculateBannerHeight', + CHANGE_BANNER_HEIGHT = 'changeBannerHeight', + BANNER_SPRING_BACK = 'bannerSpringBack', + HANDLE_BREAKPOINT_CHANGE = 'handleBreakpointChange', + HANDLE_COLOR_CHANGE = 'handleColorModeChange', +} + +export interface BaseHomeEventParam { + type: BaseHomeEventType; + param: T; +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/module.json5 b/features/commonbusiness/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..53770b9dbfec3167f556f1e3a7748df80401ad9f --- /dev/null +++ b/features/commonbusiness/src/main/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "commonbusiness", + "type": "har", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ] + } +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/resources/base/element/color.json b/features/commonbusiness/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..d058d3d88b13d34a9daa42540fe3deb766937933 --- /dev/null +++ b/features/commonbusiness/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "blur_bg", + "value": "#CCF1F3F5" + } + ] +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/resources/base/element/float.json b/features/commonbusiness/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..009d074e8bfb375046392e5cf2a0230d12a1aaba --- /dev/null +++ b/features/commonbusiness/src/main/resources/base/element/float.json @@ -0,0 +1,16 @@ +{ + "float": [ + { + "name": "banner_info_height", + "value": "160vp" + }, + { + "name": "banner_button_height", + "value": "32vp" + }, + { + "name": "banner_icon_height", + "value": "24vp" + } + ] +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/resources/base/media/ic_placeholder.png b/features/commonbusiness/src/main/resources/base/media/ic_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..eba4138d934c2b7e4532ef747136a132ba48319e Binary files /dev/null and b/features/commonbusiness/src/main/resources/base/media/ic_placeholder.png differ diff --git a/features/commonbusiness/src/main/resources/dark/element/color.json b/features/commonbusiness/src/main/resources/dark/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..37552f6ceb42667b2b4eeaea795419bfdd8e03ac --- /dev/null +++ b/features/commonbusiness/src/main/resources/dark/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "blur_bg", + "value": "#B3000000" + } + ] +} \ No newline at end of file diff --git a/features/commonbusiness/src/main/resources/dark/media/ic_placeholder.png b/features/commonbusiness/src/main/resources/dark/media/ic_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..d48cc01efc41207ba919a592343464c39fcceda7 Binary files /dev/null and b/features/commonbusiness/src/main/resources/dark/media/ic_placeholder.png differ diff --git a/features/commonbusiness/src/ohosTest/ets/test/Ability.test.ets b/features/commonbusiness/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..85c78f67579d6e31b5f5aeea463e216b9b141048 --- /dev/null +++ b/features/commonbusiness/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +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/features/commonbusiness/src/ohosTest/ets/test/List.test.ets b/features/commonbusiness/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..794c7dc4ed66bd98fa3865e07922906e2fcef545 --- /dev/null +++ b/features/commonbusiness/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/features/commonbusiness/src/ohosTest/module.json5 b/features/commonbusiness/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..f672137a75bf57ba5cac2d47594632f2fe76da1e --- /dev/null +++ b/features/commonbusiness/src/ohosTest/module.json5 @@ -0,0 +1,13 @@ +{ + "module": { + "name": "commonbusiness_test", + "type": "feature", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} \ No newline at end of file diff --git a/features/commonbusiness/src/test/List.test.ets b/features/commonbusiness/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..bb5b5c3731e283dd507c847560ee59bde477bbc7 --- /dev/null +++ b/features/commonbusiness/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/features/commonbusiness/src/test/LocalUnit.test.ets b/features/commonbusiness/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..165fc1615ee8618b4cb6a622f144a9a707eee99f --- /dev/null +++ b/features/commonbusiness/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +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/features/componentlibrary/.gitignore b/features/componentlibrary/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/features/componentlibrary/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/features/componentlibrary/Index.ets b/features/componentlibrary/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..24eb2bee6bc58e652d9ec9d59f2b6c2eeeb15299 --- /dev/null +++ b/features/componentlibrary/Index.ets @@ -0,0 +1,3 @@ +export { ComponentListView } from './src/main/ets/view/ComponentListView'; + +export { ComponentListModel } from './src/main/ets/model/ComponentListModel'; \ No newline at end of file diff --git a/features/componentlibrary/build-profile.json5 b/features/componentlibrary/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..697dff23e224373edb713dc2b8a08ed7341d5b4c --- /dev/null +++ b/features/componentlibrary/build-profile.json5 @@ -0,0 +1,31 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest" + } + ] +} diff --git a/features/componentlibrary/consumer-rules.txt b/features/componentlibrary/consumer-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..83d5e33c9430c0acea521d92f4f4d11957201404 --- /dev/null +++ b/features/componentlibrary/consumer-rules.txt @@ -0,0 +1,3 @@ +-keep +./src/main/ets/model/ComponentData.ets +./src/main/ets/model/ComponentDetailData.ets \ No newline at end of file diff --git a/features/componentlibrary/hvigorfile.ts b/features/componentlibrary/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..42187071482d292588ad40babeda74f7b8d97a23 --- /dev/null +++ b/features/componentlibrary/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/features/componentlibrary/obfuscation-rules.txt b/features/componentlibrary/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..fdbb5b9852d7dd5f39bddaeb21ab5ee1f3346749 --- /dev/null +++ b/features/componentlibrary/obfuscation-rules.txt @@ -0,0 +1,22 @@ +# 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/features/componentlibrary/oh-package.json5 b/features/componentlibrary/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..0dc5eb66ff9fe6490fc8b3da468080c65c7d8c0f --- /dev/null +++ b/features/componentlibrary/oh-package.json5 @@ -0,0 +1,12 @@ +{ + "name": "componentlibrary", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@ohos/common": "file:../../common", + "@ohos/commonbusiness": "file:../commonbusiness" + } +} diff --git a/features/componentlibrary/src/main/ets/component/AttributeChangeArea.ets b/features/componentlibrary/src/main/ets/component/AttributeChangeArea.ets new file mode 100644 index 0000000000000000000000000000000000000000..3b8a99e69bea1cda74c2e59df4ff3f31c66b5528 --- /dev/null +++ b/features/componentlibrary/src/main/ets/component/AttributeChangeArea.ets @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2024 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 type { ObservedArray } from '@ohos/common'; +import { VibratorUtils } from '@ohos/common'; +import type { Attribute } from '../viewmodel/Attribute'; +import { ColorPickerComponent } from './ColorPickerComponent'; +import { SelectComponent } from './SelectComponent'; +import { SliderComponent } from './SliderComponent'; +import { ToggleComponent } from './ToggleComponent'; +import { OpacityComponent } from './OpacityComponent'; +import { ToggleButtonComponent } from './ToggleButtonComponent'; +import { AttributeTypeEnum } from '../viewmodel/AttributeTypeEnum'; +import { + ColorPickerAttribute, + OpacityPickerAttribute, + SelectComAttribute, + SliderComAttribute, + ToggleButtonAttribute, + ToggleComAttribute, +} from '../viewmodel/ComponentDetailState'; +import { ChangeAttributeEvent, ComponentDetailPageVM } from '../viewmodel/ComponentDetailPageVM'; +import { ComponentDetailManager } from '../viewmodel/ComponentDetailManager'; + +@Component +export struct AttributeChangeArea { + @ObjectLink @Watch('getAttributeData') attributes: ObservedArray; + @Prop componentName: string; + @State attributeDataTwo: Attribute[] = []; + @State attributeDataOne: Attribute[] = []; + @State attributeDataTwoLength: number = 0; + @State attributeDataOneLength: number = 0; + + aboutToAppear(): void { + this.getAttributeData(); + } + + eventCallback = (name: string, value: string, mode?: SliderChangeMode) => { + const viewModel: ComponentDetailPageVM | undefined = + ComponentDetailManager.getInstance().getDetailViewModel(this.componentName); + viewModel?.sendEvent(new ChangeAttributeEvent(name, value)); + if (mode && (mode === SliderChangeMode.Moving)) { + VibratorUtils.startVibration(); + } + } + + getAttributeData() { + const attributeDataOne: Attribute[] = []; + const attributeDataTwo: Attribute[] = []; + this.attributes.forEach((item: Attribute) => { + if ([AttributeTypeEnum.TOGGLE_BUTTON, AttributeTypeEnum.TOGGLE, AttributeTypeEnum.SLIDER, + AttributeTypeEnum.SELECT].includes(item.type)) { + attributeDataOne.push(item); + } else { + attributeDataTwo.push(item); + } + }); + this.attributeDataTwoLength = attributeDataTwo.length; + this.attributeDataOneLength = attributeDataOne.length; + this.attributeDataTwo = attributeDataTwo; + this.attributeDataOne = attributeDataOne; + } + + build() { + Column() { + if (this.attributeDataOneLength !== 0) { + Column() { + ForEach(this.attributeDataOne, (item: Attribute, index: number) => { + if (index !== 0) { + Divider() + .color($r('sys.color.comp_background_tertiary')) + .width('100%') + .padding({ left: $r('sys.float.padding_level6'), right: $r('sys.float.padding_level6') }) + } + if (item instanceof SelectComAttribute) { + SelectComponent({ attribute: item, callback: this.eventCallback }) + } else if (item instanceof ToggleButtonAttribute) { + ToggleButtonComponent({ attribute: item, callback: this.eventCallback }) + } else if (item instanceof SliderComAttribute) { + SliderComponent({ attribute: item, callback: this.eventCallback }) + } else if (item instanceof ToggleComAttribute) { + ToggleComponent({ attribute: item, callback: this.eventCallback }) + } + }, (item: Attribute, _index: number) => item.name) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .padding({ + top: $r('sys.float.padding_level2'), + bottom: $r('sys.float.padding_level2'), + }) + .margin({ bottom: this.attributeDataTwoLength === 0 ? 0 : $r('sys.float.padding_level8') }) + .backgroundColor($r('sys.color.comp_background_primary')) + .borderRadius($r('sys.float.corner_radius_level8')) + } + + if (this.attributeDataTwoLength !== 0) { + Column() { + ForEach(this.attributeDataTwo, (item: Attribute, index: number) => { + if (index !== 0) { + Divider() + .color($r('sys.color.comp_background_tertiary')) + .width('100%') + .padding({ left: $r('sys.float.padding_level6'), right: $r('sys.float.padding_level6') }) + } + if (item instanceof ColorPickerAttribute) { + ColorPickerComponent({ attribute: item, callback: this.eventCallback }) + } else if (item instanceof OpacityPickerAttribute) { + OpacityComponent(item, this.eventCallback) + } + }, (item: Attribute, _index: number) => item.name) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .padding({ + top: $r('sys.float.padding_level2'), + bottom: $r('sys.float.padding_level2'), + }) + .backgroundColor($r('sys.color.comp_background_primary')) + .borderRadius($r('sys.float.corner_radius_level8')) + } + } + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/component/CodeLabCard.ets b/features/componentlibrary/src/main/ets/component/CodeLabCard.ets new file mode 100644 index 0000000000000000000000000000000000000000..52ce40bf37d1917023a1a805588385f3db268fd3 --- /dev/null +++ b/features/componentlibrary/src/main/ets/component/CodeLabCard.ets @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2024 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 { deviceInfo } from '@kit.BasicServicesKit'; +import { ProductSeriesEnum } from '@ohos/common'; +import type { ComponentCardData, ComponentContent } from '../model/ComponentData'; + +@Reusable +@Component +export struct CodeLabCard { + @State componentCardData?: ComponentCardData = undefined; + @State content?: ComponentContent = undefined; + + aboutToAppear(): void { + this.content = this.componentCardData?.cardContents[0]; + } + + aboutToReuse(params: Record): void { + this.componentCardData = params.componentCardData as ComponentCardData; + this.content = this.componentCardData.cardContents[0]; + } + + build() { + Stack({ alignContent: Alignment.Bottom }) { + Image($rawfile(this.componentCardData?.cardImage)) + .alt($r('app.media.ic_placeholder')) + .objectFit(ImageFit.Cover) + .width('100%') + .height('100%') + Column() { + Text(this.componentCardData?.cardTitle) + .fontSize($r('sys.float.Body_S')) + .fontColor($r('sys.color.font_on_secondary')) + .fontWeight(FontWeight.Medium) + .lightUpEffect(1) + .margin({ + left: $r('sys.float.padding_level6'), + top: $r('sys.float.padding_level6'), + bottom: $r('sys.float.padding_level2'), + }) + Text(this.componentCardData?.cardSubTitle) + .fontSize($r('sys.float.Title_M')) + .fontColor($r('sys.color.font_on_primary')) + .fontWeight(FontWeight.Bold) + .margin({ left: $r('sys.float.padding_level6'), bottom: $r('sys.float.padding_level2') }) + Row() { + Image($rawfile(this.content?.mediaUrl)) + .draggable(false) + .width($r('app.float.tip_image_height')) + .borderRadius($r('sys.float.corner_radius_level5')) + .aspectRatio(1) + Column() { + Text(this.content?.subTitle) + .fontSize($r('sys.float.Subtitle_M')) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontColor($r('sys.color.font_on_primary')) + .fontWeight(FontWeight.Medium) + .margin({ bottom: $r('sys.float.padding_level4') }) + Text(this.content?.title) + .fontSize($r('sys.float.Body_S')) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontColor($r('sys.color.font_on_primary')) + .maxLines(1) + } + .alignItems(HorizontalAlign.Start) + .padding({ left: $r('sys.float.padding_level6'), right: $r('sys.float.padding_level8') }) + .layoutWeight(1) + + Button() { + SymbolGlyph($r('sys.symbol.chevron_right')) + .fontColor([$r('sys.color.icon_secondary')]) + .fontSize($r('sys.float.Title_M')) + } + .backgroundColor($r('sys.color.comp_background_primary')) + .width($r('app.float.card_button_height')) + .aspectRatio(1) + } + .padding($r('sys.float.padding_level6')) + .width('100%') + .height($r('app.float.codelab_content_height')) + .backgroundColor($r('sys.color.comp_background_secondary')) + } + .renderGroup(true) + .height($r('app.float.card_content_height')) + .width('100%') + .justifyContent(FlexAlign.End) + .alignItems(HorizontalAlign.Start) + } + .width('100%') + .height(deviceInfo.productSeries === ProductSeriesEnum.VDE ? $r('app.float.codelab_card_height_verde') : $r('app.float.codelab_card_height')) + .borderRadius($r('sys.float.corner_radius_level8')) + .clip(true) + .clickEffect({ level: ClickEffectLevel.MIDDLE }) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/component/CodePreviewComponent.ets b/features/componentlibrary/src/main/ets/component/CodePreviewComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..3e31484b99c2eeb04823fdfb03e23fbe8f7f679b --- /dev/null +++ b/features/componentlibrary/src/main/ets/component/CodePreviewComponent.ets @@ -0,0 +1,577 @@ +/* + * Copyright (c) 2024 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 { common, ConfigurationConstant } from '@kit.AbilityKit'; +import { uniformTypeDescriptor as utd } from '@kit.ArkData'; +import { promptAction, window } from '@kit.ArkUI'; +import { BusinessError, pasteboard } from '@kit.BasicServicesKit'; +import { systemShare } from '@kit.ShareKit'; +import type { GlobalInfoModel } from '@ohos/common'; +import { + BreakpointTypeEnum, + CommonConstants, + Logger, + ScreenOrientation, + WebNodeController, + WindowUtil, +} from '@ohos/common'; +import { CodePreviewJSUtil } from '../util/CodePreviewJSUtil'; + +const TAG: string = '[CodePreviewComponent]'; + +@Component +export struct CodePreviewComponent { + @StorageProp('systemColorMode') @Watch('onHandleColorModeChange') systemColorMode: ConfigurationConstant.ColorMode = + AppStorage.get('systemColorMode')!; + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @Prop @Watch('flushCode') code: string = ''; + @Prop pageContainer: boolean = false; + @Prop isFocus: boolean = false; + @Prop isBack: boolean = false; + @Prop topTranslateY: number = 0; + @Prop bottomTranslateY: number = 0; + @Prop navigationOpacity: number = 1; + colorMode: ConfigurationConstant.ColorMode = this.systemColorMode; + @State isDarkMode: boolean = (this.systemColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK); + @State screenOrientation: string = this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG ? + ScreenOrientation.LANDSCAPE : ScreenOrientation.PORTRAIT; + @State isFloatWindowType: boolean = false; + @State isHover: boolean = false; + @State currentIndex: number = 0; + @State backIconBgColor: ResourceColor = + this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? Color.Transparent : + $r('sys.color.comp_background_tertiary'); + @State shareIconBgColor: ResourceColor = + this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? Color.Transparent : + $r('sys.color.comp_background_tertiary'); + @Link webNodeController: WebNodeController; + onBackPage?: () => void; + public pushPage?: (value: string) => void; + public componentName: string = ''; + private windowClass: window.Window | undefined = undefined; + private topIconItems: ItemData[] = [ + new ItemData( + { + iconResource: $r('sys.symbol.arrow_up_left_and_arrow_down_right'), + } as ResourceInterface, + () => { + this.pushPage?.(this.code); + }, 0) + ]; + private bottomIconItems: ItemData[] = [ + new ItemData( + { + iconResource: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? + $r('sys.symbol.plus_square_on_square') : $r('sys.symbol.plus_square_on_square_fill'), + iconTitle: $r('app.string.copy') + } as ResourceInterface, + () => { + this.copyText(this.code); + }, 0), + new ItemData( + { + iconResource: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? + $r('sys.symbol.sun_max') : $r('sys.symbol.sun_max_fill'), + iconTitle: $r('app.string.dark_mode'), + } as ResourceInterface, + () => { + this.changeColorMode(); + }, 1, + { + iconResource: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? + $r('sys.symbol.moon') : $r('sys.symbol.moon_fill'), + iconTitle: $r('app.string.light_mode'), + } as ResourceInterface), + new ItemData( + { + iconResource: $r('sys.symbol.screen_rotation'), + iconTitle: this.screenOrientation === ScreenOrientation.PORTRAIT ? $r('app.string.landscape_screen') : + $r('app.string.portrait_screen') + } as ResourceInterface, + () => { + this.changeScreenOrientation(); + }, 2) + ]; + private pcBottomIconItems: ItemData[] = this.bottomIconItems.slice(0, 1); + private beginBreakpoint: BreakpointTypeEnum = this.globalInfoModel.currentBreakpoint; + private windowSizeCallback: Callback = (size) => { + if (size.width > size.height) { + this.screenOrientation = ScreenOrientation.LANDSCAPE; + if (this.pageContainer && !this.isBack) { + const showLandscapeMethod: string = + this.isFloatWindowType ? 'showLandscapeFloatView(%param)' : 'showLandscapeView(%param)'; + const params: string = JSON.stringify(this.beginBreakpoint); + CodePreviewJSUtil.codeViewRunJS(showLandscapeMethod, params); + } + } else { + this.screenOrientation = ScreenOrientation.PORTRAIT; + if (this.pageContainer && !this.isBack) { + const showPortraitMethod: string = 'showPortraitView()'; + CodePreviewJSUtil.codeViewRunJS(showPortraitMethod); + } + } + this.changeBottomIconData(); + }; + + aboutToAppear?(): void { + if (this.pageContainer) { + try { + window.getLastWindow(getContext()).then((windowClass: window.Window) => { + if (windowClass === undefined) { + Logger.error(TAG, `MainWindowClass is undefined`); + return; + } + this.windowClass = windowClass; + this.windowClass.on('windowSizeChange', this.windowSizeCallback); + }); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, + `Failed to register windowSizeChange. Cause code: ${err.code}, message: ${err.message}`); + } + } + } + + aboutToDisappear(): void { + if (this.pageContainer) { + try { + this.windowClass?.off('windowSizeChange', this.windowSizeCallback); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, + `Failed to unregister windowSizeChange. Cause code: ${err.code}, message: ${err.message}`); + } + } + } + + handleDeviceOrientation() { + try { + if (canIUse('SystemCapability.Window.SessionManager')) { + // Retrieve the floating window status + this.windowClass!.on('windowStatusChange', (windowStatusType) => { + if (windowStatusType === window.WindowStatusType.FLOATING) { + this.isFloatWindowType = true; + // For the first time, directly set it to vertical floating mode. + this.screenOrientation = ScreenOrientation.PORTRAIT; + } else { + this.isFloatWindowType = false; + // if it is in fullscreen mode, restore it based on the device. + // Restore the tablet to landscape mode. + if (this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM) { + this.screenOrientation = ScreenOrientation.PORTRAIT; + } + } + }); + } + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, + `Failed to unregister callback. Cause code: ${err.code}, message: ${err.message}`); + } + } + + flushCode() { + const codeToHtmlMethod: string = 'codeToHtml(%param)'; + const params: string = JSON.stringify(this.code); + CodePreviewJSUtil.codeViewRunJS(codeToHtmlMethod, params); + } + + onHandleColorModeChange() { + if (this.colorMode !== this.systemColorMode) { + this.changeColorMode(); + } + } + + changeColorMode() { + if (this.colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT) { + this.colorMode = ConfigurationConstant.ColorMode.COLOR_MODE_DARK; + this.isDarkMode = true; + } else { + this.colorMode = ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT; + this.isDarkMode = false; + } + WindowUtil.updateStatusBarColor(getContext(this), this.isDarkMode); + const changeColorModeMethod: string = 'changeColorMode(%param)'; + const params: string = JSON.stringify(this.colorMode); + CodePreviewJSUtil.codeViewRunJS(changeColorModeMethod, params); + } + + private changeScreenOrientation(): void { + if (this.screenOrientation === ScreenOrientation.PORTRAIT) { + WindowUtil.enableFloatWindowRotate(getContext()); + WindowUtil.setMainWindowOrientation(getContext(), window.Orientation.LANDSCAPE); + } else { + WindowUtil.disableFloatWindowRotate(getContext()); + WindowUtil.setMainWindowOrientation(getContext(), window.Orientation.PORTRAIT); + } + } + + private changeBottomIconData(): void { + if (this.bottomIconItems[2]) { + this.bottomIconItems[2].lightMode = { + iconResource: $r('sys.symbol.screen_rotation'), + iconTitle: this.screenOrientation === ScreenOrientation.PORTRAIT ? $r('app.string.landscape_screen') : + $r('app.string.portrait_screen'), + }; + this.bottomIconItems[2].darkMode = { + iconResource: $r('sys.symbol.screen_rotation'), + iconTitle: this.screenOrientation === ScreenOrientation.PORTRAIT ? $r('app.string.landscape_screen') : + $r('app.string.portrait_screen'), + } + } + } + + private handleShare(): void { + let context = getContext(this) as common.UIAbilityContext; + const shareDescription = + context.resourceManager.getStringSync($r('app.string.share_description').id, this.componentName); + const shareData: systemShare.SharedData = new systemShare.SharedData({ + utd: utd.UniformDataType.PLAIN_TEXT, + content: this.code, + title: this.componentName, + description: shareDescription + }); + + let controller: systemShare.ShareController = new systemShare.ShareController(shareData); + controller.show(context, { + selectionMode: systemShare.SelectionMode.SINGLE, + previewMode: systemShare.SharePreviewMode.DEFAULT + }).catch((error: BusinessError) => { + Logger.error(TAG, `Component code sharing error. Code: ${error.code}, message: ${error.message}`); + }); + } + + copyText(text: string) { + try { + const pasteboardData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text); + const systemPasteboard = pasteboard.getSystemPasteboard(); + systemPasteboard.setData(pasteboardData).then(() => { + try { + promptAction.showToast({ message: $r('app.string.copy_success') }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Show toast error, the code is ${error.code}}, the message is ${error.message}`); + } + }).catch((error: BusinessError) => { + promptAction.showToast({ message: $r('app.string.copy_fail') }); + Logger.error(TAG, `Copy data failed, the code is ${error.code}}, the message is ${error.message}`); + }) + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Pasteboard invoke error, the code is ${error.code}}, the message is ${error.message}`); + } + } + + @Builder + TopIconItem(resourceData: ResourceInterface | undefined, onClickIcon: (() => void) | undefined) { + Row({ space: CommonConstants.SPACE_4 }) { + SymbolGlyph(resourceData?.iconResource) + .fontSize($r('sys.float.Title_S')) + .fontColor([$r('sys.color.icon_primary')]) + if (resourceData?.iconTitle !== undefined) { + Text(resourceData?.iconTitle) + .fontSize($r('sys.float.Caption_L')) + .fontColor($r('sys.color.font_primary')) + .fontWeight(FontWeight.Medium) + } + } + .backgroundColor($r('sys.color.comp_background_tertiary')) + .borderRadius($r('sys.float.corner_radius_level10')) + .padding($r('sys.float.padding_level5')) + .backgroundBlurStyle(BlurStyle.BACKGROUND_THIN) + .onClick(() => { + onClickIcon?.(); + }) + } + + @Builder + TopMenu() { + Row({ space: CommonConstants.SPACE_8 }) { + ForEach(this.topIconItems, (item: ItemData) => { + this.TopIconItem(this.isDarkMode ? item?.darkMode : item?.lightMode, item?.onClickIcon + ) + }, (_item: ItemData, index: number) => index.toString()) + } + .width('100%') + .justifyContent(FlexAlign.End) + .padding({ + top: $r('sys.float.padding_level6'), + }) + } + + @Builder + BottomMenu() { + Row() { + ForEach(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? this.pcBottomIconItems : + this.bottomIconItems, (item: ItemData) => { + BottomIconItem({ + isDark: this.isDarkMode, + itemData: item, + }) + }, (_item: ItemData, index: number) => index.toString()) + } + .translate({ y: this.bottomTranslateY }) + .width('100%') + .height(CommonConstants.TAB_BAR_HEIGHT + this.globalInfoModel.naviIndicatorHeight) + .justifyContent(FlexAlign.SpaceAround) + .padding({ bottom: this.globalInfoModel.naviIndicatorHeight }) + .backgroundBlurStyle(BlurStyle.COMPONENT_THICK, + { + colorMode: this.isDarkMode ? ThemeColorMode.DARK : ThemeColorMode.LIGHT + }) + } + + @Builder + TopNavigationMenu() { + Column() { + Row() { + Button({ type: ButtonType.Circle }) { + SymbolGlyph($r('sys.symbol.chevron_backward')) + .fontColor(this.isDarkMode ? [$r('app.color.icon_primary_dark')] : [$r('app.color.icon_primary_light')]) + .fontSize($r('sys.float.Title_M')) + } + .height($r('app.float.code_preview_top_navigation_height')) + .aspectRatio(1) + .margin({ right: $r('sys.float.padding_level4') }) + .backgroundColor(this.backIconBgColor) + .onClick(() => this.onBackPage?.()) + .onHover((isHover: boolean) => { + this.backIconBgColor = !isHover ? Color.Transparent : + this.colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT ? + $r('sys.color.comp_background_tertiary') : $r('sys.color.icon_on_tertiary'); + }) + + Text(this.componentName) + .fontSize($r('sys.float.Title_S')) + .fontColor(this.isDarkMode ? $r('app.color.font_primary_dark') : $r('app.color.font_primary_light')) + .fontWeight(FontWeight.Bold) + .textAlign(TextAlign.Start) + .layoutWeight(1) + + Button({ type: ButtonType.Circle }) { + SymbolGlyph($r('sys.symbol.share')) + .fontColor(this.isDarkMode ? [$r('app.color.icon_primary_dark')] : [$r('app.color.icon_primary_light')]) + .fontSize($r('sys.float.Title_M')) + } + .height($r('app.float.code_preview_top_navigation_height')) + .aspectRatio(1) + .margin({ left: $r('sys.float.padding_level4') }) + .backgroundColor(this.shareIconBgColor) + .onClick(() => this.handleShare()) + .onHover((isHover: boolean) => { + this.shareIconBgColor = !isHover ? Color.Transparent : + this.colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT ? + $r('sys.color.comp_background_tertiary') : $r('sys.color.icon_on_tertiary'); + }) + + if (this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL) { + Row({ space: CommonConstants.SPACE_8 }) { + ForEach(this.pcBottomIconItems, (item: ItemData, index: number) => { + Row() { + SymbolGlyph(this.colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT ? + item.lightMode.iconResource : item.darkMode.iconResource) + .fontSize($r('app.float.symbol_size_large')) + .fontColor(this.colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT ? + [$r('sys.color.icon_primary')] : [$r('sys.color.icon_on_primary')]) + .opacity(this.isFocus ? 1 : 0.4) + } + .justifyContent(FlexAlign.Center) + .height($r('app.float.code_preview_top_navigation_height')) + .aspectRatio(1) + .backgroundColor(this.currentIndex === item.id && this.isHover ? $r('sys.color.ohos_id_color_hover') : + Color.Transparent) + .onHover((isHover: boolean) => { + this.isHover = isHover; + this.currentIndex = index; + }) + .onClick(() => { + item.onClickIcon(); + }) + }, (index: number) => index.toString()) + } + .margin({ right: $r('app.float.code_preview_icon_margin') }) + } + } + .opacity(this.navigationOpacity) + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.SpaceBetween) + .height(CommonConstants.NAVIGATION_HEIGHT) + .padding({ + left: $r('sys.float.padding_level8'), + right: $r('sys.float.padding_level8'), + }) + } + .translate({ y: this.topTranslateY }) + .position({ x: 0, y: 0 }) + .backgroundBlurStyle(BlurStyle.COMPONENT_THICK, + { + colorMode: this.isDarkMode ? ThemeColorMode.DARK : ThemeColorMode.LIGHT + }) + .padding({ + top: this.globalInfoModel.statusBarHeight + }) + .width('100%') + } + + @Styles + normalStyles(): void { + .backgroundColor(this.isHover ? $r('sys.color.interactive_hover') : Color.Transparent) + } + + @Styles + pressedStyles(): void { + .backgroundColor($r('sys.color.interactive_pressed')) + } + + @Styles + focusStyles(): void { + .borderColor($r('sys.color.comp_emphasize_tertiary')) + .borderWidth($r('app.float.toolbar_focus_border_width')) + } + + @Builder + Toolbar() { + Divider() + .width('100%') + .margin({ + top: $r('sys.float.padding_level6'), + left: $r('sys.float.padding_level4'), + right: $r('sys.float.padding_level4'), + bottom: $r('sys.float.padding_level2'), + }) + Row({ space: CommonConstants.SPACE_8 }) { + Text($r('app.string.copy_code')) + .fontSize($r('sys.float.Subtitle_M')) + .fontWeight(FontWeight.Medium) + SymbolGlyph($r('sys.symbol.plus_square_on_square')) + .fontSize($r('sys.float.Title_S')) + .fontColor([$r('sys.color.icon_primary')]) + } + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + .onClick(() => this.copyText(this.code)) + .padding({ + top: $r('sys.float.padding_level4'), + bottom: $r('sys.float.padding_level4'), + left: $r('sys.float.padding_level4'), + right: $r('sys.float.padding_level4'), + }) + .stateStyles({ + normal: this.normalStyles, + pressed: this.pressedStyles, + focused: this.focusStyles, + }) + .borderRadius($r('sys.float.corner_radius_level6')) + } + + @Builder + WebOverlay() { + Row() + .width('100%') + .height($r('app.float.web_overlay_height')) + .linearGradient({ + colors: [[$r('app.color.web_overlay_color_start'), 0], [$r('sys.color.comp_background_primary'), 1]] + }) + .visibility(this.pageContainer ? Visibility.None : Visibility.Visible) + } + + build() { + Column() { + Stack() { + NodeContainer(this.webNodeController) + .backgroundColor(this.isDarkMode ? $r('app.color.code_preview_bg_color') : Color.White) + .overlay(this.WebOverlay(), { align: Alignment.Bottom }) + if (this.pageContainer === true) { + this.TopNavigationMenu() + if (this.globalInfoModel.currentBreakpoint !== BreakpointTypeEnum.XL) { + this.BottomMenu() + } + } else { + this.TopMenu() + } + } + .layoutWeight(1) + .alignContent(this.pageContainer ? Alignment.Bottom : Alignment.TopEnd) + .margin({ + left: this.pageContainer ? 0 : $r('sys.float.padding_level4'), + right: this.pageContainer ? 0 : $r('sys.float.padding_level4'), + }) + + if (!this.pageContainer) { + this.Toolbar() + } + } + .onAppear(() => { + if (this.pageContainer) { + this.handleDeviceOrientation(); + } + }) + .onDisAppear(() => { + WindowUtil.disableFloatWindowRotate(getContext()); + }) + .backgroundColor($r('sys.color.comp_background_primary')) + .padding(this.pageContainer === false ? { + left: $r('sys.float.padding_level2'), + right: $r('sys.float.padding_level2'), + bottom: $r('sys.float.padding_level2'), + } : $r('sys.float.padding_level0')) + .width('100%') + .height('100%') + } +} + +@Component +struct BottomIconItem { + @Link isDark: boolean; + @ObjectLink itemData: ItemData | undefined; + + build() { + Column() { + SymbolGlyph(this.isDark ? this.itemData?.darkMode.iconResource : this.itemData?.lightMode.iconResource) + .fontSize($r('sys.float.Title_M')) + .fontColor(this.isDark ? [$r('app.color.icon_secondary_dark')] : [$r('app.color.icon_secondary_light')]) + Text(this.isDark ? this.itemData?.darkMode.iconTitle : this.itemData?.lightMode.iconTitle) + .textAlign(TextAlign.Center) + .fontSize($r('sys.float.Caption_M')) + .fontWeight(FontWeight.Medium) + .fontColor(this.isDark ? $r('app.color.font_secondary_dark') : $r('app.color.font_secondary_light')) + .margin({ top: $r('sys.float.padding_level2') }) + } + .onClick(() => { + this.itemData?.onClickIcon(); + }) + } +} + +@Observed +class ResourceInterface { + public iconResource?: Resource; + public iconTitle?: ResourceStr; +} + +@Observed +class ItemData { + public id: number; + public lightMode: ResourceInterface; + public darkMode: ResourceInterface; + public onClickIcon: () => void; + + constructor(lightMode: ResourceInterface, onClickIcon: () => void, index: number, darkMode?: ResourceInterface) { + this.lightMode = lightMode; + this.darkMode = darkMode || lightMode; + this.onClickIcon = onClickIcon; + this.id = index; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/component/ColorPickerComponent.ets b/features/componentlibrary/src/main/ets/component/ColorPickerComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..2785ed874c1930e9c55b1b093ea9a829c27bdec7 --- /dev/null +++ b/features/componentlibrary/src/main/ets/component/ColorPickerComponent.ets @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024 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 { ColorPickerUtil } from '@ohos/common'; +import type { ColorPickerAttribute } from '../viewmodel/ComponentDetailState'; + +/** + * Common Hue Component + */ +@Component +export struct ColorPickerComponent { + @ObjectLink attribute: ColorPickerAttribute; + callback: (name: string, value: string) => void = (name: string, value: string) => { + }; + + build() { + Column() { + Text(this.attribute.disPlayName) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Subtitle_M')) + .fontColor($r('sys.color.font_primary')) + .margin({ + top: $r('sys.float.padding_level8'), + bottom: $r('sys.float.padding_level8'), + left: $r('sys.float.padding_level6') + }) + + Slider({ + value: ColorPickerUtil.getColorFromRgb(this.attribute.currentValue), + style: SliderStyle.InSet, + }) + .onChange((value: number, mode: SliderChangeMode) => { + const curentValue: string = ColorPickerUtil.getBlockColor(value); + if (this.attribute.currentValue !== String(curentValue)) { + this.attribute.currentValue = curentValue; + this.callback(this.attribute.name, curentValue); + } + }) + .selectedColor(Color.Transparent) + .trackColor(new LinearGradient([ + { color: ColorPickerUtil.setRgba(255, 0, 0, 1.00), offset: 0 }, + { color: ColorPickerUtil.setRgba(255, 255, 0, 1.00), offset: 1 / 6 }, + { color: ColorPickerUtil.setRgba(0, 255, 0, 1.00), offset: 2 / 6 }, + { color: ColorPickerUtil.setRgba(8, 255, 255, 1.00), offset: 3 / 6 }, + { color: ColorPickerUtil.setRgba(0, 0, 255, 1.00), offset: 4 / 6 }, + { color: ColorPickerUtil.setRgba(255, 0, 255, 1.00), offset: 5 / 6 }, + { color: ColorPickerUtil.setRgba(255, 0, 0, 1.00), offset: 1 }, + ])) + .sliderInteractionMode(SliderInteraction.SLIDE_AND_CLICK_UP) + .trackThickness($r('app.float.slider_track_thick_large')) + .blockBorderColor(Color.White) + .blockBorderWidth($r('app.float.slider_block_border_size')) + .blockSize({ + width: $r('app.float.slider_block_size_large'), + height: $r('app.float.slider_block_size_large'), + }) + .blockColor(Color.Transparent) + .height($r('app.float.common_component_height')) + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .padding({ left: $r('sys.float.padding_level4'), right: $r('sys.float.padding_level4') }) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/component/ComponentItem.ets b/features/componentlibrary/src/main/ets/component/ComponentItem.ets new file mode 100644 index 0000000000000000000000000000000000000000..a53cbb0915a399c4831c815860a0a4b6f23a9437 --- /dev/null +++ b/features/componentlibrary/src/main/ets/component/ComponentItem.ets @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024 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 { BreakpointTypeEnum, CommonConstants, GlobalInfoModel } from '@ohos/common'; +import type { ComponentContent } from '../model/ComponentData'; + +@Component +export struct ComponentItem { + @Prop componentContent: ComponentContent; + @Prop showDivider: boolean; + @Prop buttonColor: ResourceColor = $r('sys.color.interactive_active'); + private globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + + build() { + Column() { + Divider() + .visibility(this.showDivider ? Visibility.Visible : Visibility.Hidden) + .color($r('sys.color.comp_background_tertiary')) + .margin({ left: $r('sys.float.padding_level36'), right: $r('sys.float.padding_level6') }) + Row() { + Image($rawfile(this.componentContent.mediaUrl)) + .draggable(false) + .alt($r('app.media.ic_placeholder')) + .borderRadius($r('sys.float.corner_radius_level5')) + .width($r('app.float.tip_image_height')) + .aspectRatio(1) + .margin({ right: $r('sys.float.padding_level8') }) + + Column({ space: CommonConstants.SPACE_6 }) { + Text(this.componentContent.subTitle) + .fontSize($r('sys.float.Subtitle_M')) + .fontWeight(FontWeight.Medium) + .fontColor($r('sys.color.font_primary')) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + Text(this.componentContent.title) + .fontSize($r('sys.float.Body_S')) + .fontColor($r('sys.color.font_secondary')) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + } + .alignItems(HorizontalAlign.Start) + .layoutWeight(1) + + Button($r('app.string.open'), { buttonStyle: ButtonStyleMode.NORMAL, controlSize: ControlSize.SMALL }) + .borderRadius(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? + $r('sys.float.corner_radius_level4') : 0) + .type(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? ButtonType.Normal : + ButtonType.Capsule) + .margin({ left: $r('sys.float.padding_level8') }) + .width($r('app.float.tip_button_width')) + .fontColor(this.buttonColor) + + } + .width('100%') + .layoutWeight(1) + .padding($r('sys.float.padding_level6')) + } + .renderGroup(true) + .alignItems(HorizontalAlign.Start) + .height($r('app.float.component_item_height')) + .width('100%') + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/component/DetailContentView.ets b/features/componentlibrary/src/main/ets/component/DetailContentView.ets new file mode 100644 index 0000000000000000000000000000000000000000..098066ffd77b97df3e3526e449f5b3c4dfd165a2 --- /dev/null +++ b/features/componentlibrary/src/main/ets/component/DetailContentView.ets @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2024 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 { curves } from '@kit.ArkUI'; +import type { GlobalInfoModel } from '@ohos/common'; +import { + BreakpointType, + BreakpointTypeEnum, + CommonConstants, + WebNodeController, + WebUtil, +} from '@ohos/common'; +import { CodePreviewComponent } from './CodePreviewComponent'; +import type { ConfigInterface } from '../componentdetailview/ComponentDetailConfig'; +import { ComponentDetailPageVM, TopNavigationChangeEvent } from '../viewmodel/ComponentDetailPageVM'; +import type { ComponentDetailState } from '../viewmodel/ComponentDetailState'; +import type { RecommendData } from '../model/ComponentDetailData'; +import { ComponentDetailManager } from '../viewmodel/ComponentDetailManager'; +import { AttributeChangeArea } from './AttributeChangeArea'; +import { RecommendListItem } from './RecommendListItem'; +import { DetailPageConstant } from '../constant/DetailPageConstant'; +import { CodePreviewJSUtil } from '../util/CodePreviewJSUtil'; + +const PREVIEW_HEIGHT_SM: number = DetailPageConstant.PREVIEW_HEIGHT_SM; + +@Component +export struct DetailContentView { + @ObjectLink componentDetailState: ComponentDetailState; + @Prop componentName: string; + @StorageProp('GlobalInfoModel') @Watch('handleBreakPointChange') globalInfoModel: GlobalInfoModel = + AppStorage.get('GlobalInfoModel')!; + @State isShowCodePreview: boolean = false; + @State scaleValue: number = 1; + @State showDivider: boolean = false; + @State isShowTopDivider: boolean = false; + @State webNodeController?: WebNodeController = new WebNodeController(); + scroller: Scroller = new Scroller(); + private viewModel?: ComponentDetailPageVM = + ComponentDetailManager.getInstance().getDetailViewModel(this.componentName); + private paddingToTop: number = 0; + private paddingToBottom: number = 36; + private detailConfig: Record = AppStorage.get('componentDetailConfig')! + + @Builder + textTip(text: ResourceStr) { + Text(text) + .fontSize($r('sys.float.Subtitle_S')) + .fontColor($r('sys.color.font_secondary')) + .fontWeight(FontWeight.Regular) + .margin({ + top: $r('sys.float.padding_level10'), + bottom: $r('sys.float.padding_level4'), + }) + } + + aboutToAppear(): void { + this.webNodeController = WebUtil.getWebNode(WebUtil.getComponentCodeUrl()) as WebNodeController; + this.paddingToBottom = + this.globalInfoModel.naviIndicatorHeight === 0 ? 36 : this.globalInfoModel.naviIndicatorHeight; + // In EntryAbility, set decorHeight 0. + const decorHeight: number = + this.globalInfoModel.currentBreakpoint !== BreakpointTypeEnum.XL ? this.globalInfoModel.decorHeight : 0; + this.paddingToTop = this.globalInfoModel.statusBarHeight === decorHeight ? 0 : this.globalInfoModel.statusBarHeight; + } + + aboutToDisappear(): void { + const webController = WebUtil.getWebController(WebUtil.getComponentCodeUrl()); + webController?.scrollTo(0, 0); + } + + jumpCodePreviewView() { + const code = this.componentDetailState.code; + const viewModel: ComponentDetailPageVM | undefined = + ComponentDetailManager.getInstance().getDetailViewModel(this.componentName); + this.webNodeController?.remove(); + this.webNodeController = undefined; + const toFullScreenMethod: string = 'toFullScreen()'; + CodePreviewJSUtil.codeViewRunJS(toFullScreenMethod); + animateTo({ curve: curves.interpolatingSpring(0, 1, 195, 23) }, () => { + viewModel?.jumpToCodePreview(code, () => { + this.backFromCodePreview(); + }); + }); + } + + backFromCodePreview(): void { + this.webNodeController = WebUtil.getWebNode(WebUtil.getComponentCodeUrl()) as WebNodeController; + this.webNodeController.add(); + } + + handleBreakPointChange() { + this.viewModel?.sendEvent(new TopNavigationChangeEvent(this.globalInfoModel.currentBreakpoint === + BreakpointTypeEnum.SM ? false : this.isShowTopDivider)); + } + + build() { + Flex({ + direction: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? FlexDirection.Column : + FlexDirection.Row, + }) { + Column() { + this.textTip($r('app.string.preview')) + Column() { + (this.detailConfig[this.componentName] as ConfigInterface).previewComponentBuilder.builder({ + descriptor: this.componentDetailState.descriptor, + }) + } + .margin({ + top: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? 0 : $r('sys.float.padding_level4'), + }) + .backgroundColor($r('sys.color.comp_background_primary')) + .justifyContent(FlexAlign.Center) + .width('100%') + .height(new BreakpointType({ + sm: DetailPageConstant.PREVIEW_SUB_HEIGHT_SM, + md: (this.globalInfoModel.deviceHeight - (CommonConstants.NAVIGATION_HEIGHT + this.paddingToTop + + this.paddingToBottom) - DetailPageConstant.TEXT_TIP_HEIGHT), + lg: (this.globalInfoModel.deviceHeight - (CommonConstants.NAVIGATION_HEIGHT + this.paddingToTop + + this.paddingToBottom) - DetailPageConstant.TEXT_TIP_HEIGHT), + }).getValue(this.globalInfoModel.currentBreakpoint)) + .borderRadius($r('sys.float.corner_radius_level8')) + } + .padding({ + top: CommonConstants.NAVIGATION_HEIGHT + this.globalInfoModel.statusBarHeight + DetailPageConstant.SPACE_NORMAL, + bottom: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? $r('sys.float.padding_level6') : 0, + left: new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level12'), + lg: $r('sys.float.padding_level16'), + }).getValue(this.globalInfoModel.currentBreakpoint), + right: new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level4'), + lg: $r('sys.float.padding_level6'), + }).getValue(this.globalInfoModel.currentBreakpoint), + }) + .size(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? { width: '100%' } : { width: '50%' }) + .alignItems(HorizontalAlign.Start) + + if (this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM && this.showDivider) { + Divider() + .width('100%') + } + Scroll(this.scroller) { + Column({ space: DetailPageConstant.SPACE_NORMAL }) { + if (this.componentDetailState.attributes.length !== 0) { + this.textTip($r('app.string.changeAttributes')) + AttributeChangeArea({ + attributes: this.componentDetailState.attributes, + componentName: this.componentName, + }) + } + this.textTip($r('app.string.code')) + CodePreviewComponent({ + webNodeController: this.webNodeController, + code: this.componentDetailState.code, + componentName: this.componentName, + pageContainer: false, + pushPage: () => { + this.jumpCodePreviewView(); + }, + }) + .width('100%') + .height($r('app.float.code_preview_height')) + .geometryTransition(CommonConstants.CODE_PREVIEW_GEOMETRY_ID, { follow: true }) + .borderRadius($r('sys.float.corner_radius_level8')) + .clip(true) + .margin({ + top: (this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM || + this.componentDetailState.attributes.length !== 0) ? 0 : $r('sys.float.padding_level4'), + }) + .onClick(() => { + this.jumpCodePreviewView(); + }) + if (this.componentDetailState.recommends.length > 0) { + this.textTip($r('app.string.recommend')) + Column() { + ForEach(this.componentDetailState.recommends, (item: RecommendData, index: number) => { + if (index !== 0) { + Divider() + .color($r('sys.color.comp_divider')) + .width('100%') + .padding({ left: $r('sys.float.padding_level6'), right: $r('sys.float.padding_level6') }) + } + RecommendListItem({ itemData: item }) + .height(DetailPageConstant.ATTRIBUTE_ITEM_HEIGHT) + }, (_item: RecommendData) => _item.id?.toString()) + } + .backgroundColor($r('sys.color.comp_background_primary')) + .width('100%') + .borderRadius($r('sys.float.corner_radius_level8')) + } + } + .margin({ + top: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? 0 : + $r('sys.float.padding_level4'), + }) + .alignItems(HorizontalAlign.Start) + .padding({ + top: new BreakpointType({ + sm: 0, + md: CommonConstants.NAVIGATION_HEIGHT + this.globalInfoModel.statusBarHeight, + lg: CommonConstants.NAVIGATION_HEIGHT + this.globalInfoModel.statusBarHeight, + }).getValue(this.globalInfoModel.currentBreakpoint), + bottom: this.paddingToBottom, + }) + } + .align(Alignment.Top) + .scrollBar(BarState.Off) + .edgeEffect(EdgeEffect.Spring) + .onDidScroll(() => { + if (this.scroller.currentOffset().yOffset > DetailPageConstant.SCROLL_OFFSET_Y) { + if (!this.showDivider) { + this.showDivider = true; + } + this.isShowTopDivider = this.globalInfoModel.currentBreakpoint !== BreakpointTypeEnum.SM ? true : false; + this.viewModel?.sendEvent(new TopNavigationChangeEvent(this.isShowTopDivider)); + } else if (this.scroller.currentOffset().yOffset <= DetailPageConstant.SCROLL_OFFSET_Y) { + this.showDivider = false; + this.isShowTopDivider = false; + this.viewModel?.sendEvent(new TopNavigationChangeEvent(this.isShowTopDivider)); + } + }) + .nestedScroll({ + scrollForward: NestedScrollMode.SELF_ONLY, + scrollBackward: NestedScrollMode.SELF_ONLY, + }) + .size(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? + { + width: '100%', + height: (this.globalInfoModel.deviceHeight - PREVIEW_HEIGHT_SM - + (CommonConstants.NAVIGATION_HEIGHT + this.paddingToTop)), + } : + { + width: '50%', + height: this.globalInfoModel.deviceHeight, + }) + .padding({ + left: new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level4'), + lg: $r('sys.float.padding_level6'), + }).getValue(this.globalInfoModel.currentBreakpoint), + right: new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level12'), + lg: $r('sys.float.padding_level16'), + }).getValue(this.globalInfoModel.currentBreakpoint), + }) + } + .width('100%') + .height('100%') + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/component/ListCard.ets b/features/componentlibrary/src/main/ets/component/ListCard.ets new file mode 100644 index 0000000000000000000000000000000000000000..700c16701dd4308409ab26b9ba2c085357f6ca3c --- /dev/null +++ b/features/componentlibrary/src/main/ets/component/ListCard.ets @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024 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 type { GlobalInfoModel } from '@ohos/common'; +import { BreakpointType, CommonConstants } from '@ohos/common'; +import type { ComponentCardData, ComponentContent } from '../model/ComponentData'; +import { ComponentItem } from './ComponentItem'; + +@Reusable +@Component +export struct ListCard { + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @State componentCardData?: ComponentCardData = undefined; + handleItemClick?: (componentContent: ComponentContent) => void; + + aboutToReuse(params: Record): void { + this.componentCardData = params.componentCardData as ComponentCardData; + this.handleItemClick = params.handleItemClick as (componentContent: ComponentContent) => void; + } + + build() { + Column() { + Column() { + Text(this.componentCardData?.cardSubTitle) + .fontSize($r('sys.float.Body_S')) + .fontColor($r('sys.color.font_secondary')) + .fontWeight(FontWeight.Regular) + .margin({ left: $r('sys.float.padding_level8'), bottom: $r('sys.float.padding_level2') }) + Text(this.componentCardData?.cardTitle) + .fontSize($r('sys.float.Title_M')) + .fontColor($r('sys.color.font_primary')) + .fontWeight(FontWeight.Bold) + .margin({ left: $r('sys.float.padding_level8'), bottom: $r('sys.float.padding_level4') }) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .maxLines(1) + } + .alignItems(HorizontalAlign.Start) + .height(CommonConstants.NAVIGATION_HEIGHT) + + Repeat(this.componentCardData?.cardContents) + .each((repeatItem: RepeatItem) => { + ComponentItem({ + componentContent: repeatItem.item, + showDivider: repeatItem.index !== 0, + }) + .onClick(() => { + this.handleItemClick?.(repeatItem.item); + }) + }) + .key((componentContent: ComponentContent) => componentContent.id.toString()) + } + .backgroundColor($r('sys.color.comp_background_primary')) + .border({ + width: $r('sys.float.border_none'), + color: $r('sys.color.comp_background_list_card'), + radius: $r('sys.float.corner_radius_level8'), + }) + .clickEffect({ level: ClickEffectLevel.MIDDLE }) + .alignItems(HorizontalAlign.Start) + .clip(true) + .padding({ + top: $r('sys.float.padding_level8'), + bottom: new BreakpointType({ + sm: $r('sys.float.padding_level2'), + md: $r('sys.float.padding_level4'), + lg: $r('sys.float.padding_level4') + }).getValue(this.globalInfoModel.currentBreakpoint), + }) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/component/MenuItemBuilder.ets b/features/componentlibrary/src/main/ets/component/MenuItemBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..8ca61bec97a5ef2dddd5df1efbccb9f8fd94b2b3 --- /dev/null +++ b/features/componentlibrary/src/main/ets/component/MenuItemBuilder.ets @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 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 MenuItemBuilder(configuration: MenuItemConfiguration) { + Row() { + Text(configuration.value) + Blank() + if (configuration.selected) { + SymbolGlyph($r('sys.symbol.checkmark')).fontSize($r('app.float.symbol_size_normal')).fontColor([$r('sys.color.ohos_id_color_foreground')]) + } + } + .height($r('app.float.menu_item_height')) + .width('100%') + .padding({ left: $r('sys.float.padding_level8'), right: $r('sys.float.padding_level8') }) + .onClick(() => { + configuration.triggerSelect(configuration.index, configuration.value.valueOf().toString()); + }) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/component/OpacityComponent.ets b/features/componentlibrary/src/main/ets/component/OpacityComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..fc810c8020f8b943f216c96a59f11081e7a09f1d --- /dev/null +++ b/features/componentlibrary/src/main/ets/component/OpacityComponent.ets @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024 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 { ColorPickerUtil } from '@ohos/common'; +import type { OpacityPickerAttribute } from '../viewmodel/ComponentDetailState'; + +/** + * Common Opacity Slider Component + */ +@Builder +export function OpacityComponent(attribute: OpacityPickerAttribute, + callback: (name: string, value: string, mode?: SliderChangeMode) => void) { + + Column() { + Text(attribute.disPlayName) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_primary')) + .margin({ + top: $r('sys.float.padding_level8'), + bottom: $r('sys.float.padding_level8'), + left: $r('sys.float.padding_level6') + }) + + Slider({ + value: Number(attribute.currentValue), + min: attribute.leftRange, + max: attribute.rightRange, + style: SliderStyle.InSet, + step: attribute.step, + }) + .onChange((value: number, mode: SliderChangeMode) => { + const val: string = value.toFixed(2); + if (attribute.currentValue !== val) { + attribute.currentValue = val; + callback(attribute.name, val, mode); + } + }) + .selectedColor(Color.Transparent) + .trackColor(new LinearGradient([ + { color: ColorPickerUtil.setRgba(0, 0, 0, 0.00), offset: 0 }, + { color: ColorPickerUtil.setRgba(0, 0, 0, 1.00), offset: 1 }, + ])) + .sliderInteractionMode(SliderInteraction.SLIDE_AND_CLICK_UP) + .trackThickness($r('app.float.slider_track_thick_large')) + .blockBorderColor(Color.White) + .blockBorderWidth($r('app.float.slider_block_border_size')) + .blockSize({ + width: $r('app.float.slider_block_size_large'), + height: $r('app.float.slider_block_size_large'), + }) + .blockColor(Color.Transparent) + .height($r('app.float.common_component_height')) + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .padding({ left: $r('sys.float.padding_level4'), right: $r('sys.float.padding_level4') }) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/component/PictureListCard.ets b/features/componentlibrary/src/main/ets/component/PictureListCard.ets new file mode 100644 index 0000000000000000000000000000000000000000..4050e8f14a317aaebfccd19bed900ae58a42ecd1 --- /dev/null +++ b/features/componentlibrary/src/main/ets/component/PictureListCard.ets @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024 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 { deviceInfo } from '@kit.BasicServicesKit'; +import { ImageUtil, ProductSeriesEnum } from '@ohos/common'; +import type { ComponentCardData, ComponentContent } from '../model/ComponentData'; +import { ComponentItem } from './ComponentItem'; + +@Reusable +@Component +export struct PictureListCard { + @State componentCardData?: ComponentCardData = undefined; + handleItemClick?: (componentContent: ComponentContent) => void; + @State buttonColor: ResourceColor = ''; + + aboutToAppear(): void { + this.getColorFromImg(); + } + + aboutToReuse(params: Record): void { + this.componentCardData = params.componentCardData as ComponentCardData; + this.handleItemClick = params.handleItemClick as (componentContent: ComponentContent) => void; + this.getColorFromImg(); + } + + getColorFromImg() { + ImageUtil.getColorFromImgUrl(this.componentCardData?.cardImage || '', false) + .then((colorArr: number[]) => { + this.buttonColor = `rgba(${colorArr[0]},${colorArr[1]},${colorArr[2]},1)`; + }); + } + + @Builder + textOverlay() { + Column() { + Text(this.componentCardData?.cardSubTitle) + .fontSize($r('sys.float.Body_S')) + .fontColor($r('sys.color.font_on_secondary')) + .fontWeight(FontWeight.Medium) + .margin({ bottom: $r('sys.float.padding_level2') }) + Text(this.componentCardData?.cardTitle) + .fontSize($r('sys.float.Title_M')) + .fontColor($r('sys.color.font_on_primary')) + .fontWeight(FontWeight.Bold) + } + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.End) + .padding($r('sys.float.padding_level6')) + .width('100%') + .height(deviceInfo.productSeries === ProductSeriesEnum.VDE ? $r('app.float.card_top_height_verde') : + $r('app.float.card_top_height')) + .margin({ bottom: $r('sys.float.padding_level2') }) + } + + build() { + Column() { + Image($rawfile(this.componentCardData?.cardImage)) + .width('100%') + .height(deviceInfo.productSeries === ProductSeriesEnum.VDE ? $r('app.float.card_top_height_verde') : + $r('app.float.card_top_height')) + .overlay(this.textOverlay()) + .objectFit(ImageFit.Cover) + .alt($r('app.media.ic_placeholder')) + .margin({ bottom: $r('sys.float.padding_level2') }) + + Repeat(this.componentCardData?.cardContents) + .each((repeatItem: RepeatItem) => { + ComponentItem({ + componentContent: repeatItem.item, + showDivider: repeatItem.index !== 0, + buttonColor: this.buttonColor + }) + .onClick(() => { + this.handleItemClick?.(repeatItem.item); + }) + }) + .key((componentContent: ComponentContent) => componentContent.id.toString()) + } + .clickEffect({ level: ClickEffectLevel.MIDDLE }) + .borderRadius($r('sys.float.corner_radius_level8')) + .clip(true) + .width('100%') + .backgroundColor($r('sys.color.comp_background_list_card')) + .padding({ bottom: $r('sys.float.padding_level2') }) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/component/RecommendListItem.ets b/features/componentlibrary/src/main/ets/component/RecommendListItem.ets new file mode 100644 index 0000000000000000000000000000000000000000..2ef1c0d3007c3d74cd02ea5ae03e39311847d26d --- /dev/null +++ b/features/componentlibrary/src/main/ets/component/RecommendListItem.ets @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 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 { + WebUtil, + WebSheetBuilder, + WebUrlType, + GlobalInfoModel, + BreakpointTypeEnum, + CommonConstants, +} from '@ohos/common'; +import { DetailPageConstant } from '../constant/DetailPageConstant'; +import type { RecommendData } from '../model/ComponentDetailData'; + +@Component +export struct RecommendListItem { + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @Prop itemData: RecommendData; + @State showSheet: boolean = false; + + build() { + Row() { + SymbolGlyph(this.itemData.articleType === 1 ? $r('sys.symbol.doc_text') : $r('sys.symbol.paintpalette')) + .fontSize($r('app.float.symbol_size_large')) + .margin({ right: $r('sys.float.padding_level8') }) + .fontColor([$r('sys.color.icon_emphasize')]) + Column() { + Text(this.itemData.title).fontSize($r('sys.float.Body_L')).fontColor($r('sys.color.font_primary')) + Text(this.itemData.subTitle).fontSize($r('sys.float.Body_M')).fontColor($r('sys.color.font_tertiary')) + } + .width('70%') + .alignItems(HorizontalAlign.Start) + + Blank() + SymbolGlyph($r('sys.symbol.chevron_right')) + .fontColor([$r('sys.color.icon_secondary')]) + .fontSize($r('app.float.symbol_size_large')) + .bindSheet(this.showSheet, WebSheetBuilder(this.itemData.url, WebUrlType.HARMONYOS), { + showClose: true, + onDisappear: () => { + this.showSheet = false; + WebUtil.getWebNode(this.itemData.url)?.remove(); + }, + preferType: SheetType.CENTER, + height: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? + ((this.globalInfoModel.deviceHeight - this.globalInfoModel.decorHeight) * + CommonConstants.SHEET_HEIGHT_RATIO_XL) : SheetSize.LARGE, + width: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? CommonConstants.SHEET_WIDTH_XL : + undefined, + title: { title: this.itemData.title }, + }) + } + .onClick(() => { + WebUtil.createWebNode(this.itemData.url, undefined, NestedScrollMode.SELF_ONLY); + WebUtil.addNode(this.itemData.url); + this.showSheet = true; + }) + .alignItems(VerticalAlign.Center) + .width('100%') + .height(DetailPageConstant.ATTRIBUTE_ITEM_HEIGHT) + .padding({ left: $r('sys.float.padding_level6'), right: $r('sys.float.padding_level6') }) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/component/SelectComponent.ets b/features/componentlibrary/src/main/ets/component/SelectComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..dbcece3fe35784e8df42117f279a3f9706b0b730 --- /dev/null +++ b/features/componentlibrary/src/main/ets/component/SelectComponent.ets @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024 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 type { SelectComAttribute } from '../viewmodel/ComponentDetailState'; +import { SelectMenuAttributeModifier } from './SelectMenuAttributeModifier'; + +/** + * Dropdown selection public component + */ +@Component +export struct SelectComponent { + @ObjectLink attribute: SelectComAttribute; + @State currentIndex: number = 0; + callback: (name: string, value: string) => void = (name: string, value: string) => { + }; + + aboutToAppear(): void { + this.attribute.selectOption.forEach((element, index) => { + if (element.value.toString() === this.attribute.currentValue) { + this.currentIndex = index; + } + }); + } + + build() { + Row() { + Text(this.attribute.disPlayName) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Subtitle_M')) + .fontColor($r('sys.color.font_primary')) + Blank() + Select(this.attribute.selectOption) + .id('select') + .selected(this.currentIndex) + .value(this.attribute.currentValue) + .font({ size: $r('sys.float.Body_M'), weight: FontWeight.Regular }) + .fontColor($r('sys.color.font_secondary')) + .optionFont({ size: $r('sys.float.Subtitle_M'), weight: FontWeight.Medium }) + .optionFontColor($r('sys.color.font_primary')) + .optionWidth($r('app.float.detail_common_component_option_width')) + .menuAlign(MenuAlignType.END, { dx: 0, dy: 0 } as Offset) + .menuItemContentModifier(new SelectMenuAttributeModifier()) + .menuBackgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK) + .optionBgColor(Color.White) + .onSelect((_index: number, value: string) => { + if (this.attribute.currentValue !== value) { + this.currentIndex = _index; + this.attribute.currentValue = value; + this.callback(this.attribute.name, value); + } + }) + } + .padding({ left: $r('sys.float.padding_level6'), right: $r('sys.float.padding_level6') }) + .height($r('app.float.common_component_height')) + .width('100%') + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/component/SelectMenuAttributeModifier.ets b/features/componentlibrary/src/main/ets/component/SelectMenuAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..fd0da9e8067a76237e578e7dedd0ee0062b4ef3b --- /dev/null +++ b/features/componentlibrary/src/main/ets/component/SelectMenuAttributeModifier.ets @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 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 { MenuItemBuilder } from './MenuItemBuilder'; + +export class SelectMenuAttributeModifier implements ContentModifier { + public applyContent(): WrappedBuilder<[MenuItemConfiguration]> { + return wrapBuilder(MenuItemBuilder); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/component/SliderComponent.ets b/features/componentlibrary/src/main/ets/component/SliderComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..30fe24ac26b1adf1a876dad79c6826edc163113d --- /dev/null +++ b/features/componentlibrary/src/main/ets/component/SliderComponent.ets @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024 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 type { SliderComAttribute } from '../viewmodel/ComponentDetailState'; + +/** + * Slider public component + */ +@Component +export struct SliderComponent { + @ObjectLink attribute: SliderComAttribute; + callback: (name: string, value: string, mode?: SliderChangeMode) => void = (name: string, value: string) => { + }; + + build() { + Column() { + Row() { + Text(this.attribute.disPlayName) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Subtitle_M')) + .fontColor($r('sys.color.font_primary')) + Blank() + Text(this.attribute.currentValue) + .fontWeight(FontWeight.Regular) + .fontSize($r('sys.float.Body_M')) + .fontColor($r('sys.color.font_secondary')) + .padding({ right: $r('sys.float.padding_level2') }) + } + .height($r('app.float.common_component_height')) + .alignItems(VerticalAlign.Center) + .margin({ bottom: $r('sys.float.padding_level1') }) + .width('100%') + .padding({ left: $r('sys.float.padding_level6'), right: $r('sys.float.padding_level6') }) + + Row() { + Slider({ + value: Number(this.attribute.currentValue), + min: this.attribute.leftRange, + max: this.attribute.rightRange, + style: SliderStyle.OutSet, + step: this.attribute.step, + }) + .onChange((value: number, mode: SliderChangeMode) => { + if (this.attribute.currentValue !== String(value)) { + this.attribute.currentValue = String(value); + this.callback(this.attribute.name, String(value), mode); + } + }) + .sliderInteractionMode(SliderInteraction.SLIDE_AND_CLICK_UP) + .blockSize({ + width: $r('app.float.common_component_block_size'), + height: $r('app.float.common_component_block_size'), + }) + .selectedColor($r('sys.color.icon_emphasize')) + .trackThickness($r('app.float.common_component_track_thickness')) + .padding({ left: $r('sys.float.padding_level4'), right: $r('sys.float.padding_level4') }) + .width('100%') + } + .height($r('app.float.common_component_height')) + .width('100%') + } + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Start) + .width('100%') + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/component/ToggleButtonComponent.ets b/features/componentlibrary/src/main/ets/component/ToggleButtonComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..052da4ac5d71546dbd8de109c1980b90cee5a645 --- /dev/null +++ b/features/componentlibrary/src/main/ets/component/ToggleButtonComponent.ets @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 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 { ItemRestriction, SegmentButton, SegmentButtonOptions, SegmentButtonTextItem } from '@kit.ArkUI'; +import type { ToggleButtonAttribute } from '../viewmodel/ComponentDetailState'; + +@Component +export struct ToggleButtonComponent { + @ObjectLink attribute: ToggleButtonAttribute; + @State @Watch('onChangeSelectIndex') selectIndex: number[] = + [this.attribute.selectOption.findIndex((item) => item?.text === this.attribute.currentValue)]; + @State singleSelectCapsuleOptions: SegmentButtonOptions = SegmentButtonOptions.capsule({ + buttons: this.attribute.selectOption as ItemRestriction, + multiply: false, + selectedFontColor: $r('sys.color.font_primary'), + fontColor: $r('sys.color.font_secondary'), + selectedBackgroundColor: $r('sys.color.ohos_id_color_foreground_contrary_disable'), + }); + callback: (name: string, value: string) => void = (name: string, value: string) => { + }; + + onChangeSelectIndex() { + if (this.selectIndex) { + const index = this.selectIndex[0]; + this.callback(this.attribute.name, this.attribute.selectOption[index]?.text as string); + } + } + + build() { + Row() { + Text(this.attribute.disPlayName) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_primary')) + Blank() + SegmentButton({ + options: this.singleSelectCapsuleOptions, + selectedIndexes: $selectIndex + }).width('60%') + } + .height($r('app.float.common_component_height')) + .padding({ left: $r('sys.float.padding_level6'), right: $r('sys.float.padding_level6') }) + .alignItems(VerticalAlign.Center) + .width('100%') + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/component/ToggleComponent.ets b/features/componentlibrary/src/main/ets/component/ToggleComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..2de02e05dd8d8d37829b968a4e269b4d239e3f74 --- /dev/null +++ b/features/componentlibrary/src/main/ets/component/ToggleComponent.ets @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 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 type { ToggleComAttribute } from '../viewmodel/ComponentDetailState'; + +/** + * Toggle Public Component + */ +@Component +export struct ToggleComponent { + @ObjectLink attribute: ToggleComAttribute; + callback: (name: string, value: string) => void = (name: string, value: string) => { + }; + + build() { + Row() { + Text(this.attribute.disPlayName) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_primary')) + Blank() + Toggle({ type: ToggleType.Switch, isOn: JSON.parse(this.attribute.currentValue) }) + .selectedColor($r('sys.color.comp_background_emphasize')) + .onChange((isOn: boolean) => { + this.callback(this.attribute.name, String(isOn)); + }) + } + .height($r('app.float.common_component_height')) + .padding({ left: $r('sys.float.padding_level6'), right: $r('sys.float.padding_level6') }) + .alignItems(VerticalAlign.Center) + .width('100%') + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/ComponentDetailConfig.ets b/features/componentlibrary/src/main/ets/componentdetailview/ComponentDetailConfig.ets new file mode 100644 index 0000000000000000000000000000000000000000..4013a3433cd98d812db408d48544dcd72680b22f --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/ComponentDetailConfig.ets @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2024 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 { ActionSheetBuilder } from './actionsheetview/component/ActionSheetBuilder' +import { ActionSheetCodeGenerator } from './actionsheetview/viewmodel/ActionSheetCodeGenerator' +import { ActionSheetDescriptor } from './actionsheetview/viewmodel/ActionSheetDescriptor' +import { AICaptionBuilder } from './aicaption/component/AICaptionBuilder' +import { AICaptionCodeGenerator } from './aicaption/viewmodel/AICaptionCodeGenerator' +import { AlertDialogBuilder } from './alertdialogview/component/AlertDialogBuilder' +import { AlertDialogCodeGenerator } from './alertdialogview/viewmodel/AlertDialogCodeGenerator' +import { AlertDialogDescriptor } from './alertdialogview/viewmodel/AlertDialogDescriptor' +import { AppLinkingBuilder } from './applinking/component/AppLinkingBuilder' +import { AppLinkingCodeGenerator } from './applinking/viewmodel/AppLinkingCodeGenerator' +import { AppLinkingDescriptor } from './applinking/viewmodel/AppLinkingDescriptor' +import { ButtonBuilder } from './buttonview/component/ButtonBuilder' +import { ButtonCodeGenerator } from './buttonview/viewmodel/ButtonCodeGenerator' +import { ButtonDescriptor } from './buttonview/viewmodel/ButtonDescriptor' +import { ColumnBuilder } from './columnview/component/ColumnBuilder' +import { ColumnCodeGenerator } from './columnview/viewmodel/ColumnCodeGenerator' +import { ColumnDescriptor } from './columnview/viewmodel/ColumnDescriptor' +import { CommonCodeGenerator } from '../viewmodel/CommonCodeGenerator' +import { CommonDescriptor } from '../viewmodel/CommonDescriptor' +import { DescriptorWrapper } from '../viewmodel/DescriptorWrapper' +import { CustomDialogBuilder } from './customdialog/component/CustomDialogBuilder' +import { CustomDialogCodeGenerator } from './customdialog/viewmodel/CustomDialogCodeGenerator' +import { CustomDialogDescriptor } from './customdialog/viewmodel/CustomDialogDescriptor' +import { DocumentViewPickerBuilder } from './documentviewpicker/component/DocumentViewPickerBuilder' +import { DocumentViewPickerCodeGenerator } from './documentviewpicker/viewmodel/DocumentViewPickerCodeGenerator' +import { FlexBuilder } from './flex/component/FlexBuilder' +import { FlexCodeGenerator } from './flex/viewmodel/FlexCodeGenerator' +import { FlexDescriptor } from './flex/viewmodel/FlexDescriptor' +import { GridBuilder } from './grid/component/GridBuilder' +import { GridCodeGenerator } from './grid/viewmodel/GridCodeGenerator' +import { GridDescriptor } from './grid/viewmodel/GridDescriptor' +import { ImageBuilder } from './Image/component/ImageBuilder' +import { ImageCodeGenerator } from './Image/viewmodel/ImageCodeGenerator' +import { ImageDescriptor } from './Image/viewmodel/ImageDescriptor' +import { AIImageBuilder } from './imageaianalyzer/component/AIImageBuilder' +import { AIImageCodeGenerator } from './imageaianalyzer/viewmodel/AIImageCodeGenerator' +import { ListBuilder } from './list/component/ListBuilder' +import { ListCodeGenerator } from './list/viewmodel/ListCodeGenerator' +import { ListDescriptor } from './list/viewmodel/ListDescriptor' +import { PenKitBuilder } from './penkit/component/PenKitBuilder' +import { PenKitCodeGenerator } from './penkit/viewmodel/PenKitCodeGenerator' +import { PhotoViewPickerBuilder } from './photopicker/component/PhotoViewPickerBuilder' +import { PhotoViewPickerCodeGenerator } from './photopicker/viewmodel/PhotoViewPickerCodeGenerator' +import { CalendarPickerBuilder } from './picker/calendarPicker/component/CalendarPickerBuilder' +import { CalendarPickerCodeGenerator } from './picker/calendarPicker/viewmodel/CalendarPickerCodeGenerator' +import { CalendarPickerDescriptor } from './picker/calendarPicker/viewmodel/CalendarPickerDescriptor' +import { CameraPickerBuilder } from './picker/camerapicker/component/CameraPickerBuilder' +import { CameraPickerCodeGenerator } from './picker/camerapicker/viewmodel/CameraPickerCodeGenerator' +import { DatePickerBuilder } from './picker/datepicker/component/DatePickerBuilder' +import { DatePickerCodeGenerator } from './picker/datepicker/viewmodel/DatePickerCodeGenerator' +import { DatePickerDescriptor } from './picker/datepicker/viewmodel/DatePickerDescriptor' +import { PopupBuilder } from './popup/component/PopupBuilder' +import { PopupCodeGenerator } from './popup/viewmodel/PopupCodeGenerator' +import { PopupDescriptor } from './popup/viewmodel/PopupDescriptor' +import { ProgressBuilder } from './progress/component/ProgressBuilder' +import { ProgressCodeGenerator } from './progress/viewmodel/ProgressCodeGenerator' +import { ProgressDescriptor } from './progress/viewmodel/ProgressDescriptor' +import { RatingBuilder } from './rating/component/RatingBuilder' +import { RatingCodeGenerator } from './rating/viewmodel/RatingCodeGenerator' +import { RatingDescriptor } from './rating/viewmodel/RatingDescriptor' +import { RowBuilder } from './rowview/component/RowBuilder' +import { RowCodeGenerator } from './rowview/viewmodel/RowCodeGenerator' +import { RowDescriptor } from './rowview/viewmodel/RowDescriptor' +import { StackBuilder } from './stackview/component/StackBuilder' +import { StackCodeGenerator } from './stackview/viewmodel/StackCodeGenerator' +import { StackDescriptor } from './stackview/viewmodel/StackDescriptor' +import { StyleTextBuilder } from './styletext/component/StyleTextBuilder' +import { StyleTextCodeGenerator } from './styletext/viewmodel/StyleTextCodeGenerator' +import { StyleTextDescriptor } from './styletext/viewmodel/StyleTextDescriptor' +import { SwiperBuilder } from './swiperView/component/SwiperBuilder' +import { SwiperCodeGenerator } from './swiperView/viewmodel/SwiperCodeGenerator' +import { SwiperDescriptor } from './swiperView/viewmodel/SwiperDescriptor' +import { TabBuilder } from './tabview/component/TabBuilder' +import { TabCodeGenerator } from './tabview/viewmodel/TabCodeGenerator' +import { TabDescriptor } from './tabview/viewmodel/TabDescriptor' +import { TextAreaBuilder } from './textarea/component/TextAreaBuilder' +import { TextAreaCodeGenerator } from './textarea/viewmodel/TextAreaCodeGenerator' +import { TextAreaDescriptor } from './textarea/viewmodel/TextAreaDescriptor' +import { TextInputBuilder } from './textinput/component/TextInputBuilder' +import { TextInputCodeGenerator } from './textinput/viewmodel/TextInputCodeGenerator' +import { TextInputDescriptor } from './textinput/viewmodel/TextInputDescriptor' +import { TextPickerDialogBuilder } from './textpickerdialogview/component/TextPickerDialogBuilder' +import { TextPickerDialogCodeGenerator } from './textpickerdialogview/viewmodel/TextPickerDialogCodeGenerator' +import { TextPickerDialogDescriptor } from './textpickerdialogview/viewmodel/TextPickerDialogDescriptor' +import { TextToSpeechBuilder } from './texttospeech/component/TextToSpeechBuilder' +import { TextToSpeechCodeGenerator } from './texttospeech/viewmodel/TextToSpeechCodeGenerator' +import { TextToSpeechDescriptor } from './texttospeech/viewmodel/TextToSpeechDescriptor' +import { TextBuilder } from './textview/component/TextBuilder' +import { TextCodeGenerator } from './textview/viewmodel/TextCodeGenerator' +import { TextDescriptor } from './textview/viewmodel/TextDescriptor' +import { ToggleBuilder } from './toggleview/component/ToggleBuilder' +import { ToggleCodeGenerator } from './toggleview/viewmodel/ToggleCodeGenerator' +import { ToggleDescriptor } from './toggleview/viewmodel/ToggleDescriptor' +import { WaterFlowBuilder } from './waterflow/component/WaterFlowBuilder' +import { WaterFlowCodeGenerator } from './waterflow/viewmodel/WaterFlowCodeGenerator' +import { WaterFlowDescriptor } from './waterflow/viewmodel/WaterFlowDescriptor' +import { CommonAttributeFilter } from '../viewmodel/CommonAttributeFilter' +import { StackAttributeFilter } from './stackview/viewmodel/StackAttributeFilter' +import { RatingAttributeFilter } from './rating/viewmodel/RatingAttributeFilter' +import { ButtonAttributeFilter } from './buttonview/viewmodel/ButtonAttributeFilter' +import { WaterFlowAttributeFilter } from './waterflow/viewmodel/WaterflowAttributeFilter' +import { FlexAttributeFilter } from './flex/viewmodel/FlexAttributeFilter' +import { ProgressAttributeFilter } from './progress/viewmodel/ProgressAttributeFilter' +import { ToggleAttributeFilter } from './toggleview/viewmodel/ToggleAttributeFilter' +import { ColumnAttributeFilter } from './columnview/viewmodel/ColumnAttributeFilter' +import { RowAttributeFilter } from './rowview/viewmodel/RowAttributeFilter' +import { CameraPickerDescriptor } from './picker/camerapicker/viewmodel/CameraPickerDescriptor' + +export const componentDetailConfig: Record = { + 'Button': { + previewComponentBuilder: wrapBuilder(ButtonBuilder), + descriptor: () => new ButtonDescriptor(), + codeGenerate: new ButtonCodeGenerator(), + attributeFilter: new ButtonAttributeFilter(), + }, + 'Column': { + previewComponentBuilder: wrapBuilder(ColumnBuilder), + descriptor: () => new ColumnDescriptor(), + codeGenerate: new ColumnCodeGenerator(), + attributeFilter: new ColumnAttributeFilter(), + }, + 'Row': { + previewComponentBuilder: wrapBuilder(RowBuilder), + descriptor: () => new RowDescriptor(), + codeGenerate: new RowCodeGenerator(), + attributeFilter: new RowAttributeFilter(), + }, + 'Stack': { + previewComponentBuilder: wrapBuilder(StackBuilder), + descriptor: () => new StackDescriptor(), + codeGenerate: new StackCodeGenerator(), + attributeFilter: new StackAttributeFilter(), + }, + 'Grid': { + previewComponentBuilder: wrapBuilder(GridBuilder), + descriptor: () => new GridDescriptor(), + codeGenerate: new GridCodeGenerator(), + }, + 'Progress': { + previewComponentBuilder: wrapBuilder(ProgressBuilder), + descriptor: () => new ProgressDescriptor(), + codeGenerate: new ProgressCodeGenerator(), + attributeFilter: new ProgressAttributeFilter(), + }, + 'Text': { + previewComponentBuilder: wrapBuilder(TextBuilder), + descriptor: () => new TextDescriptor(), + codeGenerate: new TextCodeGenerator(), + }, + 'TextArea': { + previewComponentBuilder: wrapBuilder(TextAreaBuilder), + descriptor: () => new TextAreaDescriptor(), + codeGenerate: new TextAreaCodeGenerator(), + }, + 'TextInput': { + previewComponentBuilder: wrapBuilder(TextInputBuilder), + descriptor: () => new TextInputDescriptor(), + codeGenerate: new TextInputCodeGenerator(), + }, + 'TextStyle': { + previewComponentBuilder: wrapBuilder(StyleTextBuilder), + descriptor: () => new StyleTextDescriptor(), + codeGenerate: new StyleTextCodeGenerator(), + }, + 'Image': { + previewComponentBuilder: wrapBuilder(ImageBuilder), + descriptor: () => new ImageDescriptor(), + codeGenerate: new ImageCodeGenerator() + }, + 'Rating': { + previewComponentBuilder: wrapBuilder(RatingBuilder), + descriptor: () => new RatingDescriptor(), + codeGenerate: new RatingCodeGenerator(), + attributeFilter: new RatingAttributeFilter(), + }, + 'Toggle': { + previewComponentBuilder: wrapBuilder(ToggleBuilder), + descriptor: () => new ToggleDescriptor(), + codeGenerate: new ToggleCodeGenerator(), + attributeFilter: new ToggleAttributeFilter(), + }, + 'TextToSpeech': { + previewComponentBuilder: wrapBuilder(TextToSpeechBuilder), + descriptor: () => new TextToSpeechDescriptor(), + codeGenerate: new TextToSpeechCodeGenerator(), + }, + 'AICaptionComponent': { + previewComponentBuilder: wrapBuilder(AICaptionBuilder), + descriptor: () => new CommonDescriptor(), + codeGenerate: new AICaptionCodeGenerator(), + }, + 'Flex': { + previewComponentBuilder: wrapBuilder(FlexBuilder), + descriptor: () => new FlexDescriptor(), + codeGenerate: new FlexCodeGenerator(), + attributeFilter: new FlexAttributeFilter(), + }, + 'List': { + previewComponentBuilder: wrapBuilder(ListBuilder), + descriptor: () => new ListDescriptor(), + codeGenerate: new ListCodeGenerator(), + }, + 'WaterFlow': { + previewComponentBuilder: wrapBuilder(WaterFlowBuilder), + descriptor: () => new WaterFlowDescriptor(), + codeGenerate: new WaterFlowCodeGenerator(), + attributeFilter: new WaterFlowAttributeFilter(), + }, + 'Tabs': { + previewComponentBuilder: wrapBuilder(TabBuilder), + descriptor: () => new TabDescriptor(), + codeGenerate: new TabCodeGenerator(), + }, + 'Swiper': { + previewComponentBuilder: wrapBuilder(SwiperBuilder), + descriptor: () => new SwiperDescriptor(), + codeGenerate: new SwiperCodeGenerator(), + }, + 'Penkit': { + previewComponentBuilder: wrapBuilder(PenKitBuilder), + descriptor: () => new CommonDescriptor(), + codeGenerate: new PenKitCodeGenerator(), + }, + 'AlertDialog': { + previewComponentBuilder: wrapBuilder(AlertDialogBuilder), + descriptor: () => new AlertDialogDescriptor(), + codeGenerate: new AlertDialogCodeGenerator(), + }, + 'TextPickerDialog': { + previewComponentBuilder: wrapBuilder(TextPickerDialogBuilder), + descriptor: () => new TextPickerDialogDescriptor(), + codeGenerate: new TextPickerDialogCodeGenerator(), + }, + 'CustomDialog': { + previewComponentBuilder: wrapBuilder(CustomDialogBuilder), + descriptor: () => new CustomDialogDescriptor(), + codeGenerate: new CustomDialogCodeGenerator(), + }, + 'ActionSheet': { + previewComponentBuilder: wrapBuilder(ActionSheetBuilder), + descriptor: () => new ActionSheetDescriptor(), + codeGenerate: new ActionSheetCodeGenerator(), + }, + 'Popup': { + previewComponentBuilder: wrapBuilder(PopupBuilder), + descriptor: () => new PopupDescriptor(), + codeGenerate: new PopupCodeGenerator(), + }, + 'CalendarPicker': { + previewComponentBuilder: wrapBuilder(CalendarPickerBuilder), + descriptor: () => new CalendarPickerDescriptor(), + codeGenerate: new CalendarPickerCodeGenerator(), + }, + 'DatePicker': { + previewComponentBuilder: wrapBuilder(DatePickerBuilder), + descriptor: () => new DatePickerDescriptor(), + codeGenerate: new DatePickerCodeGenerator(), + }, + 'DocumentViewPicker': { + previewComponentBuilder: wrapBuilder(DocumentViewPickerBuilder), + descriptor: () => new CommonDescriptor(), + codeGenerate: new DocumentViewPickerCodeGenerator(), + }, + 'AppLinking': { + previewComponentBuilder: wrapBuilder(AppLinkingBuilder), + descriptor: () => new AppLinkingDescriptor(), + codeGenerate: new AppLinkingCodeGenerator(), + }, + 'CameraPicker': { + previewComponentBuilder: wrapBuilder(CameraPickerBuilder), + descriptor: () => new CameraPickerDescriptor(), + codeGenerate: new CameraPickerCodeGenerator(), + }, + 'PhotoViewPicker': { + previewComponentBuilder: wrapBuilder(PhotoViewPickerBuilder), + descriptor: () => new CommonDescriptor(), + codeGenerate: new PhotoViewPickerCodeGenerator(), + }, + 'AI Matting': { + previewComponentBuilder: wrapBuilder(AIImageBuilder), + descriptor: () => new CommonDescriptor(), + codeGenerate: new AIImageCodeGenerator(), + }, +} + +export interface ConfigInterface { + previewComponentBuilder: WrappedBuilder<[DescriptorWrapper]>; + descriptor: () => CommonDescriptor; + codeGenerate: CommonCodeGenerator; + attributeFilter?: CommonAttributeFilter; +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/Image/component/ImageBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/Image/component/ImageBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..34c341e5b9911b49f13cb47069301cb63d4bf422 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/Image/component/ImageBuilder.ets @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024 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 { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import { ImageAttributeModifier } from '../viewmodel/ImageAttributeModifier'; +import type { ImageDescriptor } from '../viewmodel/ImageDescriptor'; + +@Builder +export function ImageBuilder($$: DescriptorWrapper) { + Stack() { + Image($r('app.media.image_src340')) + .width($r('app.float.image_component_width')) + .height((($$.descriptor as ImageDescriptor).objectFit === ImageFit.Cover || + ($$.descriptor as ImageDescriptor).objectFit === ImageFit.Auto) ? 'auto' : + $r('app.float.image_component_height')) + .colorFilter(new ColorFilter(($$.descriptor as ImageDescriptor).colorFilterMatrix)) + .attributeModifier(new ImageAttributeModifier($$.descriptor as ImageDescriptor)) + .opacity(DetailPageConstant.IMAGE_OPACITY) + Stack() { + Column() + .width('100%') + .height('100%') + .opacity(DetailPageConstant.IMAGE_OPACITY_BG) + .borderRadius($r('sys.float.corner_radius_level8')) + .backgroundColor($r('sys.color.multi_color_02')) + + Column() { + Image($r('app.media.image_src340')) + .colorFilter(new ColorFilter(($$.descriptor as ImageDescriptor).colorFilterMatrix)) + .attributeModifier(new ImageAttributeModifier($$.descriptor as ImageDescriptor)) + } + .borderRadius($r('sys.float.corner_radius_level8')) + .clip(($$.descriptor as ImageDescriptor).clip) + } + .width($r('app.float.image_component_width')) + .height($r('app.float.image_component_height')) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/Image/entity/ImageAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/Image/entity/ImageAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..564cea23ed8704a2216cd52362da38dae35b018c --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/Image/entity/ImageAttributeMapping.ets @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 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. + */ + +class ImageFitStyleMap { + public code: string; + public value: ImageFit; + + constructor(code: string, value: ImageFit) { + this.code = code; + this.value = value; + } +} + +export const imageFitStyleMapData: Map = new Map([ + ['Default', new ImageFitStyleMap('ImageFit.Cover', ImageFit.Cover)], + ['Contain', new ImageFitStyleMap('ImageFit.Contain', ImageFit.Contain)], + ['Cover', new ImageFitStyleMap('ImageFit.Cover', ImageFit.Cover)], + ['Auto', new ImageFitStyleMap('ImageFit.Auto', ImageFit.Auto)], + ['Fill', new ImageFitStyleMap('ImageFit.Fill', ImageFit.Fill)], + ['ScaleDown', new ImageFitStyleMap('ImageFit.ScaleDown', ImageFit.ScaleDown)], + ['None', new ImageFitStyleMap('ImageFit.None', ImageFit.None)], + ['TopStart', new ImageFitStyleMap('ImageFit.TOP_START', ImageFit.TOP_START)], + ['Top', new ImageFitStyleMap('ImageFit.TOP', ImageFit.TOP)], + ['TopEnd', new ImageFitStyleMap('ImageFit.TOP_END', ImageFit.TOP_END)], + ['Start', new ImageFitStyleMap('ImageFit.START', ImageFit.START)], + ['Center', new ImageFitStyleMap('ImageFit.CENTER', ImageFit.CENTER)], + ['End', new ImageFitStyleMap('ImageFit.END', ImageFit.END)], + ['Bottom', new ImageFitStyleMap('ImageFit.BOTTOM', ImageFit.BOTTOM)], + ['BottomStart', new ImageFitStyleMap('ImageFit.BOTTOM_START', ImageFit.BOTTOM_START)], + ['BottomEnd', new ImageFitStyleMap('ImageFit.BOTTOM_END', ImageFit.BOTTOM_END)], +]); + +class FilterStyleMap { + public code: string; + public value: string; + + constructor(code: string, value: string) { + this.code = code; + this.value = value; + } +} + +export const filterStyleMapData: Map = new Map([ + // The input parameter is a 4x5 RGBA conversion matrix. + ['无滤镜', new FilterStyleMap('1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0', '1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0')], + ['色彩旋转', + new FilterStyleMap('0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0', '0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0')], + ['灰色滤镜', + new FilterStyleMap('0.3086,0.6094,0.0820,0,0,0.3086,0.6094,0.0820,0,0,0.3086,0.6094,0.0820,0,0,0,0,0,1,0', + '0.3086,0.6094,0.0820,0,0,0.3086,0.6094,0.0820,0,0,0.3086,0.6094,0.0820,0,0,0,0,0,1,0')], + ['增色滤镜', + new FilterStyleMap('1,0,0,0,0,0,1,0,0,255,0,0,1,0,0,0,0,0,1,0', '1,0,0,0,0,0,1,0,0,255,0,0,1,0,0,0,0,0,1,0')], + ['Default', new FilterStyleMap('1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0', '1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0')] +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/Image/viewmodel/ImageAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/Image/viewmodel/ImageAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..99eb736ba672eb6be302ed2989dc84a481da7b74 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/Image/viewmodel/ImageAttributeModifier.ets @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 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 { CommonAttributeModifier } from '../../../viewmodel/CommonAttributeModifier'; +import type { ImageDescriptor } from './ImageDescriptor'; + +@Observed +export class ImageAttributeModifier extends CommonAttributeModifier { + public applyNormalAttribute(instance: ImageAttribute): void { + this.assignAttribute((descriptor => descriptor.objectFit), (val) => instance.objectFit(val)); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/Image/viewmodel/ImageCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/Image/viewmodel/ImageCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..b2ffc114c115f906ce8fb24b886f2f70d7844910 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/Image/viewmodel/ImageCodeGenerator.ets @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 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 { StringUtil } from '../../../util/StringUtil'; +import type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { filterStyleMapData, imageFitStyleMapData } from '../entity/ImageAttributeMapping'; + +export class ImageCodeGenerator implements CommonCodeGenerator { + private objectFit: string = imageFitStyleMapData.get('Default')!.code; + private clip: boolean = false; + private colorFilterMatrixStr: string = filterStyleMapData.get('Default')!.code; + private colorFilterMatrix: number[] = StringUtil.stringToArray(this.colorFilterMatrixStr); + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'objectFit': + this.objectFit = + imageFitStyleMapData.get(attribute.currentValue)?.code ?? imageFitStyleMapData.get('Default')!.code; + break; + case 'colorFilterMatrixStr': + this.colorFilterMatrixStr = + filterStyleMapData.get(attribute.currentValue)?.code ?? filterStyleMapData.get('Default')!.code; + this.colorFilterMatrix = StringUtil.stringToArray(this.colorFilterMatrixStr); + break; + case 'clip': + this.clip = JSON.parse(attribute.currentValue); + break; + default: + break; + } + }); + return `@Component +struct ImageComponent { + build() { + Column() { + // You need to place a PNG format image in the media folder. + Image($r('app.media.image_src340')) + .colorFilter(new ColorFilter([${this.colorFilterMatrix}])) + .objectFit(${this.objectFit}) + } + .borderRadius($r('sys.float.corner_radius_level8')) + .width('200vp') + .height('140vp') + .clip(${this.clip}) + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/Image/viewmodel/ImageDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/Image/viewmodel/ImageDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..edbebaf8deaff88622663d36c7434f64096bef60 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/Image/viewmodel/ImageDescriptor.ets @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 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 { StringUtil } from '../../../util/StringUtil'; +import type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { filterStyleMapData, imageFitStyleMapData } from '../entity/ImageAttributeMapping'; + +@Observed +export class ImageDescriptor extends CommonDescriptor { + private colorFilterMatrixStr: string = filterStyleMapData.get('Default')!.value; + public objectFit: ImageFit = imageFitStyleMapData.get('Default')!.value; + public colorFilterMatrix: number[] = StringUtil.stringToArray(this.colorFilterMatrixStr); + public clip: boolean = false; + + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'objectFit': + this.objectFit = + imageFitStyleMapData.get(attribute.currentValue)?.value ?? imageFitStyleMapData.get('Default')!.value; + break; + case 'colorFilterMatrixStr': + this.colorFilterMatrixStr = + filterStyleMapData.get(attribute.currentValue)?.value ?? filterStyleMapData.get('Default')!.value; + this.colorFilterMatrix = StringUtil.stringToArray(this.colorFilterMatrixStr); + break; + case 'clip': + this.clip = JSON.parse(attribute.currentValue); + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/actionsheetview/component/ActionSheetBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/actionsheetview/component/ActionSheetBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..a07b59842d1197f2a5f2d82b01d67089b942c1e0 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/actionsheetview/component/ActionSheetBuilder.ets @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 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'; +import { Logger } from '@ohos/common'; +import { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import type { ActionSheetDescriptor } from '../viewmodel/ActionSheetDescriptor'; + +const TAG: string = '[ActionSheetBuilder]'; + +@Builder +export function ActionSheetBuilder($$: DescriptorWrapper) { + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { + Button($r('app.string.action_sheet_tip'), { buttonStyle: ButtonStyleMode.NORMAL }) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_emphasize')) + .onClick(() => { + ActionSheet.show({ + title: $r('app.string.title'), + subtitle: $r('app.string.subtitle'), + message: $r('app.string.content'), + autoCancel: ($$.descriptor as ActionSheetDescriptor).autoCancel, + transition: TransitionEffect.asymmetric( + ($$.descriptor as ActionSheetDescriptor).transitionAppear, + ($$.descriptor as ActionSheetDescriptor).transitionDisappear + ), + confirm: { + defaultFocus: true, + value: $r('app.string.dialog_confirm'), + action: () => { + try { + promptAction.showToast({ + message: $r('app.string.confirm_click'), + duration: DetailPageConstant.LONG_DURATION, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Show toast error, the code is ${error.code}}, the message is ${error.message}`); + } + }, + }, + alignment: DialogAlignment.Center, + offset: { dx: 0, dy: DetailPageConstant.ACTION_SHEET_OFFSET_Y }, + sheets: ($$.descriptor as ActionSheetDescriptor).sheetInfo, + }); + }) + } + .width('100%') + .height('100%') +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/actionsheetview/entity/ActionSheetAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/actionsheetview/entity/ActionSheetAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..4a744f07e75b39731b33af6000ca9aef87795f66 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/actionsheetview/entity/ActionSheetAttributeMapping.ets @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2024 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'; +import { Logger } from '@ohos/common'; +import { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import { CommonBoolMapping } from '../../common/entity/CommonMapData'; + +const TAG: string = '[ActionSheetBuilder]'; + +class ActionSheetInfoMapping { + public code: string; + public value: SheetInfo[]; + + public constructor(code: string, value: SheetInfo[]) { + this.code = code; + this.value = value; + } +} + +export const autoCancelMapData: Map = new Map([ + ['Default', new CommonBoolMapping('true', true)], +]); + +export const transitionMapData: Map = new Map([ + ['Default', new CommonBoolMapping('true', true)], +]); + +const sheetOne: SheetInfo[] = [ + { + title: $r('app.string.item1'), + action: () => { + try { + promptAction.showToast({ + message: $r('app.string.item_click', 'item1'), + duration: DetailPageConstant.LONG_DURATION, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Show toast error, the code is ${error.code}}, the message is ${error.message}`); + } + }, + }, + { + title: $r('app.string.item2'), + action: () => { + try { + promptAction.showToast({ + message: $r('app.string.item_click', 'item2'), + duration: DetailPageConstant.LONG_DURATION, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Show toast error, the code is ${error.code}}, the message is ${error.message}`); + } + }, + }, + { + title: $r('app.string.item3'), + action: () => { + try { + promptAction.showToast({ + message: $r('app.string.item_click', 'item3'), + duration: DetailPageConstant.LONG_DURATION, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Show toast error, the code is ${error.code}}, the message is ${error.message}`); + } + }, + }, +]; + +const sheetOneStr: string = `[{ + title: 'item1', + action: () => { + try { + promptAction.showToast({ + message: 'item1 is clicked', + duration: 2000, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`Show toast error, the code is \${error.code}, the message is \${error.message}\`); + } + }, + }, + { + title: 'item2', + action: () => { + try { + promptAction.showToast({ + message: 'item2 is clicked', + duration: 2000, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`Show toast error, the code is \${error.code}, the message is \${error.message}\`); + } + }, + }, + { + title: 'item3', + action: () => { + try { + promptAction.showToast({ + message: 'item3 is clicked', + duration: 2000, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`Show toast error, the code is \${error.code}, the message is \${error.message}\`); + } + }, + },]`; + +const sheetTwo: SheetInfo[] = [ + { + title: $r('app.string.apples'), + action: () => { + try { + promptAction.showToast({ + message: $r('app.string.item_click', 'apples'), + duration: DetailPageConstant.LONG_DURATION, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Show toast error, the code is ${error.code}, the message is ${error.message}`); + } + }, + }, + { + title: $r('app.string.bananas'), + action: () => { + try { + promptAction.showToast({ + message: $r('app.string.item_click', 'bananas'), + duration: DetailPageConstant.LONG_DURATION, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Show toast error, the code is ${error.code}, the message is ${error.message}`); + } + }, + }, + { + title: $r('app.string.pears'), + action: () => { + try { + promptAction.showToast({ + message: $r('app.string.item_click', 'pears'), + duration: DetailPageConstant.LONG_DURATION, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Show toast error, the code is ${error.code}, the message is ${error.message}`); + } + }, + }, +]; + +const sheetTwoStr: string = `[{ + title: 'apples', + action: () => { + try { + promptAction.showToast({ + message: 'apples is clicked', + duration: 2000, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`Show toast error, the code is \${error.code}, the message is \${error.message}\`); + } + }, + }, + { + title: 'bananas', + action: () => { + try { + promptAction.showToast({ + message: 'bananas is clicked', + duration: 2000, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`Show toast error, the code is \${error.code}, the message is \${error.message}\`); + } + }, + }, + { + title: 'pears', + action: () => { + try { + promptAction.showToast({ + message: 'pears is clicked', + duration: 2000, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`Show toast error, the code is \${error.code}, the message is \${error.message}\`); + } + }, + }]`; + +export const actionSheetInfoMapData: Map = new Map([ + ['Default', new ActionSheetInfoMapping(sheetOneStr, sheetOne)], + ['sheetInfo1', new ActionSheetInfoMapping(sheetTwoStr, sheetTwo)], + ['sheetInfo2', new ActionSheetInfoMapping(sheetOneStr, sheetOne)], +]); + +const transitionAppearCode: string = `TransitionEffect.OPACITY + .animation({ duration: 500, curve: Curve.Sharp }) + .combine(TransitionEffect.scale({ x: 1.5, y: 1.5 }) + .animation({ duration: 500, curve: Curve.Sharp }))`; + +const transitionDisappearCode: string = `TransitionEffect.OPACITY + .animation({ duration: 300, curve: Curve.Smooth }) + .combine(TransitionEffect.scale({ x: 0.5, y: 0.5 }) + .animation({ duration: 300, curve: Curve.Smooth }))`; + +class TransitionMap { + public code: string; + public value: TransitionEffect; + + constructor(code: string, value: TransitionEffect) { + this.code = code; + this.value = value; + } +} + +export const transitionAppearMapData: Map = new Map([ + ['Default', + new TransitionMap(transitionAppearCode, + TransitionEffect.OPACITY.animation({ duration: DetailPageConstant.ANIMATION_DURATION, curve: Curve.Sharp }) + .combine(TransitionEffect.scale({ x: 1.5, y: 1.5 }) + .animation({ duration: DetailPageConstant.ANIMATION_DURATION, curve: Curve.Sharp })))], +]); + +export const transitionDisAppearMapData: Map = new Map([ + ['Default', + new TransitionMap(transitionDisappearCode, + TransitionEffect.OPACITY.animation({ duration: DetailPageConstant.ANIMATION_DURATION_SHORT, curve: Curve.Smooth }) + .combine(TransitionEffect.scale({ x: 0.5, y: 0.5 }) + .animation({ duration: DetailPageConstant.ANIMATION_DURATION_SHORT, curve: Curve.Smooth })))], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/actionsheetview/viewmodel/ActionSheetCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/actionsheetview/viewmodel/ActionSheetCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..51c40b6c8961c8f30c3c1aff3f4bdc3cd1010187 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/actionsheetview/viewmodel/ActionSheetCodeGenerator.ets @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { + actionSheetInfoMapData, + autoCancelMapData, + transitionAppearMapData, + transitionDisAppearMapData, + transitionMapData, +} from '../entity/ActionSheetAttributeMapping'; + +export class ActionSheetCodeGenerator implements CommonCodeGenerator { + private autoCancel: string = autoCancelMapData.get('Default')!.code; + private transition: string = transitionMapData.get('Default')!.code; + private sheetInfo: string = actionSheetInfoMapData.get('Default')!.code; + private transitionAppear: string = transitionAppearMapData.get('Default')!.code; + private transitionDisappear: string = transitionDisAppearMapData.get('Default')!.code; + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'autoCancel': + this.autoCancel = attribute.currentValue; + break; + case 'transition': + this.transition = attribute.currentValue; + if (this.transition === 'true') { + this.transitionAppear = transitionAppearMapData.get('Default')!.code; + this.transitionDisappear = transitionDisAppearMapData.get('Default')!.code; + } else { + this.transitionAppear = 'undefined'; + this.transitionDisappear = 'undefined'; + } + break; + case 'sheetInfo': + this.sheetInfo = actionSheetInfoMapData.get(attribute.currentValue)?.code ?? this.sheetInfo; + break; + default: + break; + } + }); + return `import { promptAction } from '@kit.ArkUI'; + +@Component +struct ActionSheetComponent { + build() { + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { + Button('列表选择器弹窗', { buttonStyle: ButtonStyleMode.NORMAL }) + .buttonStyle(ButtonStyleMode.NORMAL) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_emphasize')) + .onClick(() => { + ActionSheet.show({ + title: '标题', + subtitle: '副标题', + message: '内容', + autoCancel: ${this.autoCancel}, + transition: TransitionEffect.asymmetric( + ${this.transitionAppear}, + ${this.transitionDisappear} + ), + confirm: { + defaultFocus: true, + value: '确定', + action: () => { + try { + promptAction.showToast({ + message: 'confirm is clicked', + duration: 2000, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`Show toast error, the code is \${error.code}, the message is \${error.message}\`); + } + } + }, + alignment: DialogAlignment.Center, + offset: { dx: 0, dy: -10 }, + sheets: ${this.sheetInfo} + }); + }) + } + .width('100%') + .height('100%') + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/actionsheetview/viewmodel/ActionSheetDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/actionsheetview/viewmodel/ActionSheetDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..36fe739e53d9b6cb0eb2d931ce8b99e1df09b35c --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/actionsheetview/viewmodel/ActionSheetDescriptor.ets @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { + actionSheetInfoMapData, + autoCancelMapData, + transitionAppearMapData, + transitionDisAppearMapData, + transitionMapData, +} from '../entity/ActionSheetAttributeMapping'; + +@Observed +export class ActionSheetDescriptor extends CommonDescriptor { + public autoCancel: boolean = autoCancelMapData.get('Default')!.value; + public transition: boolean = transitionMapData.get('Default')!.value; + public sheetInfo: SheetInfo[] = actionSheetInfoMapData.get('Default')!.value; + public transitionAppear?: TransitionEffect = transitionAppearMapData.get('Default')!.value; + public transitionDisappear?: TransitionEffect = transitionDisAppearMapData.get('Default')!.value; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'autoCancel': + this.autoCancel = JSON.parse(attribute.currentValue); + break; + case 'transition': + this.transition = JSON.parse(attribute.currentValue); + if (this.transition) { + this.transitionAppear = transitionAppearMapData.get('Default')!.value; + this.transitionDisappear = transitionDisAppearMapData.get('Default')!.value; + } else { + this.transitionAppear = undefined; + this.transitionDisappear = undefined; + } + break; + case 'sheetInfo': + this.sheetInfo = actionSheetInfoMapData.get(attribute.currentValue)?.value ?? this.sheetInfo; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/aicaption/component/AICaptionBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/aicaption/component/AICaptionBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..415c8ed05460f195864cd32d7885e45018bb6000 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/aicaption/component/AICaptionBuilder.ets @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apach 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 { AICaptionComponent, AICaptionController, AICaptionOptions, AudioData } from '@kit.SpeechKit'; +import type { BusinessError } from '@kit.BasicServicesKit'; +import { BreakpointType, GlobalInfoModel, Logger } from '@ohos/common'; +import { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; + +const TAG: string = '[CaptionComponent]'; + +@Builder +export function AICaptionBuilder(_$$: DescriptorWrapper) { + CaptionComponent() +} + +@Component +struct CaptionComponent { + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @State isShown: boolean = true; + isReading: boolean = false; + componentWidth: number = 0; + private captionOption?: AICaptionOptions; + private controller: AICaptionController = new AICaptionController(); + + aboutToAppear(): void { + this.captionOption = { + initialOpacity: DetailPageConstant.OPACITY_SMALL, + onPrepared: () => { + Logger.info(TAG, 'OnPrepared'); + }, + onError: (error: BusinessError) => { + Logger.error(TAG, `AICaption component error. Error code: ${error.code}, message: ${error.message}`); + }, + }; + } + + // Asynchronously read PCM file and write to buffer + async readPcmAudio() { + let fileData: Uint8Array; + this.isReading = true; + try { + fileData = await getContext(this).resourceManager.getMediaContent($r('app.media.16k')); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `GetMediaContent error, the code is ${error.code}}, the message is ${error.message}`); + this.isReading = false; + return; + } + const bufferSize = 640; + const byteLength = fileData.byteLength; + let offset = 0; + Logger.info(TAG, `Pcm data total bytes: ${byteLength.toString()}`); + const startTime = new Date().getTime(); + while (offset < byteLength) { + const nextOffset = offset + bufferSize; + if (offset > byteLength) { + this.isReading = false; + return; + } + const arrayBuffer = fileData.buffer.slice(offset, nextOffset); + const data = new Uint8Array(arrayBuffer); + const audioData: AudioData = { + data: data, + }; + if (this.controller) { + this.controller.writeAudio(audioData); + } + offset = offset + bufferSize; + const waitTime = bufferSize / 32; + await this.sleep(waitTime); + } + const endTime = new Date().getTime(); + this.isReading = false; + Logger.info(TAG, `Audio play time: ${endTime - startTime}`); + } + + sleep(time: number): Promise { + return new Promise(resolve => setTimeout(resolve, time)); + } + + build() { + Column({ space: DetailPageConstant.SPACE_LARGE }) { + AICaptionComponent({ + isShown: this.isShown, + controller: this.controller, + options: this.captionOption, + }) + .width(new BreakpointType({ + xs: $r('app.float.ai_caption_width'), + sm: $r('app.float.ai_caption_width'), + md: $r('app.float.ai_caption_width'), + lg: $r('app.float.ai_caption_width'), + xl: $r('app.float.ai_caption_width_xl'), + }).getValue(this.globalInfoModel.currentBreakpoint)) + .height($r('app.float.ai_caption_height')) + .margin({ + left: new BreakpointType({ + xs: DetailPageConstant.MARGIN_NEGATIVE_LARGER, + sm: DetailPageConstant.MARGIN_NEGATIVE_LARGER, + md: DetailPageConstant.MARGIN_NEGATIVE_LARGER, + lg: DetailPageConstant.MARGIN_NEGATIVE_LARGER, + xl: 0, + }).getValue(this.globalInfoModel.currentBreakpoint) + }) + + Button($r('app.string.read_pcm_audio')) + .backgroundColor($r('sys.color.background_secondary')) + .height($r('app.float.button_height_normal')) + .fontColor($r('sys.color.font_emphasize')) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .onClick(() => { + if (!this.isReading) { + this.readPcmAudio(); + } + }) + } + .height($r('app.float.column_size_middle_one')) + .margin({ top: $r('sys.float.padding_level10') }) + .onAreaChange((_oldValue: Area, newValue: Area) => { + this.componentWidth = newValue.width as number; + }) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/aicaption/viewmodel/AICaptionCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/aicaption/viewmodel/AICaptionCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..a43543a3996bb5910027869176e9602bc1976fc9 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/aicaption/viewmodel/AICaptionCodeGenerator.ets @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; + +export class AICaptionCodeGenerator implements CommonCodeGenerator { + public generate(_attributes: OriginAttribute[]): string { + return `import { AICaptionComponent, AICaptionController, AICaptionOptions, AudioData } from '@kit.SpeechKit'; +import type { BusinessError } from '@kit.BasicServicesKit'; + +@Component +struct CaptionComponent { + @State isShown: boolean = true; + isReading: boolean = false; + private captionOption?: AICaptionOptions; + private controller: AICaptionController = new AICaptionController(); + componentWidth:number = 0; + + aboutToAppear(): void { + this.captionOption = { + initialOpacity: 0.2, + onPrepared: () => { + console.log('OnPrepared'); + }, + onError: (error: BusinessError) => { + console.error('AICaption component error.'); + } + } + } + + async readPcmAudio() { + let fileData: Uint8Array; + this.isReading = true; + try { + fileData = await getContext(this).resourceManager.getMediaContent($r('app.media.16k')); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`GetMediaContent error, the code is \${error.code}, the message is \${error.message}\`); + this.isReading = false; + return; + } + // Here you need a voice file in PCM format, one that can clearly convey the text. + const bufferSize = 640; + const byteLength = fileData.byteLength; + let offset = 0; + const startTime = new Date().getTime(); + while (offset < byteLength) { + let nextOffset = offset + bufferSize + if (offset > byteLength) { + this.isReading = false; + return; + } + const arrayBuffer = fileData.buffer.slice(offset, nextOffset); + let data = new Uint8Array(arrayBuffer); + const audioData: AudioData = { + data: data + } + + if (this.controller) { + this.controller.writeAudio(audioData); + } + offset = offset + bufferSize; + const waitTime = bufferSize / 32; + await this.sleep(waitTime); + } + const endTime = new Date().getTime(); + this.isReading = false; + } + + sleep(time: number): Promise { + return new Promise(resolve => setTimeout(resolve, time)); + } + + build() { + Column({ space: 20 }) { + AICaptionComponent({ + isShown: this.isShown, + controller: this.controller, + options: this.captionOption + }) + .width(240) + .height(100) + .margin({ left: -95 }) + + Button('读取PCM音频') + .backgroundColor($r('sys.color.background_secondary')) + .height(40) + .fontColor($r('sys.color.font_emphasize')) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .onClick(() => { + if (!this.isReading) { + this.readPcmAudio(); + } + }) + } + .width(240) + .height(200) + .margin({ top: 40 }) + .onAreaChange((_oldValue: Area, newValue: Area) => { + this.componentWidth = newValue.width as number; + }) + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/alertdialogview/component/AlertDialogBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/alertdialogview/component/AlertDialogBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..d3f8c5b44235d7618e330ae9052ae302ae25cb8d --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/alertdialogview/component/AlertDialogBuilder.ets @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024 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'; +import { Logger } from '@ohos/common'; +import { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import type { AlertDialogDescriptor } from '../viewmodel/AlertDialogDescriptor'; + +const TAG: string = '[AlertDialogBuilder]'; + +@Builder +export function AlertDialogBuilder($$: DescriptorWrapper) { + Column({ space: DetailPageConstant.SPACE_SMALL }) { + Button($r('app.string.alert_dialog_tip')) + .buttonStyle(ButtonStyleMode.NORMAL) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_emphasize')) + .onClick(() => { + AlertDialog.show( + { + title: $r('app.string.title'), + subtitle: $r('app.string.subtitle'), + message: $r('app.string.content'), + autoCancel: true, + alignment: ($$.descriptor as AlertDialogDescriptor).alertDialogAlignment, + buttonDirection: DialogButtonDirection.HORIZONTAL, + buttons: [ + { + value: $r('app.string.button_one'), + action: () => { + try { + promptAction.showToast({ + message: 'Callback when button1 is clicked', + duration: DetailPageConstant.LONG_DURATION, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Show toast error, the code is ${error.code}}, the message is ${error.message}`); + } + }, + }, + { + value: $r('app.string.button_two'), + action: () => { + try { + promptAction.showToast({ + message: 'Callback when button2 is clicked', + duration: DetailPageConstant.LONG_DURATION, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Show toast error, the code is ${error.code}}, the message is ${error.message}`); + } + }, + } + ], + cancel: () => { + Logger.info(TAG, 'Closed callbacks'); + }, + onWillDismiss: (dismissDialogAction: DismissDialogAction) => { + if (dismissDialogAction.reason === DismissReason.PRESS_BACK) { + dismissDialogAction.dismiss(); + } + if (dismissDialogAction.reason === DismissReason.TOUCH_OUTSIDE) { + dismissDialogAction.dismiss(); + } + }, + }); + }) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/alertdialogview/entity/AlertDialogAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/alertdialogview/entity/AlertDialogAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..43dc249a9b1b228135f87a3c138ed74094b9d076 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/alertdialogview/entity/AlertDialogAttributeMapping.ets @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 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 { CommonNumberMapping } from '../../common/entity/CommonMapData'; + +class DialogAlignmentMapping { + public readonly code: string; + public readonly value: DialogAlignment; + + constructor(code: string, value: DialogAlignment) { + this.code = code; + this.value = value; + } +} + +export const alertDialogAlignmentMapData: Map = new Map([ + ['Default', new DialogAlignmentMapping('DialogAlignment.Default', DialogAlignment.Default)], + ['Top', new DialogAlignmentMapping('DialogAlignment.Top', DialogAlignment.Top)], + ['Center', new DialogAlignmentMapping('DialogAlignment.Center', DialogAlignment.Center)], + ['Bottom', new DialogAlignmentMapping('DialogAlignment.Bottom', DialogAlignment.Bottom)], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/alertdialogview/viewmodel/AlertDialogCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/alertdialogview/viewmodel/AlertDialogCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..ca01f2e24cb9b849d2d3e688435ac600fd87757b --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/alertdialogview/viewmodel/AlertDialogCodeGenerator.ets @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { alertDialogAlignmentMapData } from '../entity/AlertDialogAttributeMapping'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; + +export class AlertDialogCodeGenerator implements CommonCodeGenerator { + private alertDialogAlignment: string = alertDialogAlignmentMapData.get('Default')!.code; + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'dialogAlignment': + this.alertDialogAlignment = + alertDialogAlignmentMapData.get(attribute.currentValue)?.code ?? this.alertDialogAlignment; + break; + default: + break; + } + }); + return `import { promptAction } from '@kit.ArkUI'; + +@Component +struct AlertDialogComponent { + build() { + Column({ space: 5 }) { + Button('点击警告弹窗') + .buttonStyle(ButtonStyleMode.NORMAL) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_emphasize')) + .onClick(() => { + AlertDialog.show( + { + title: '标题', + subtitle: '副标题', + message: '内容', + autoCancel: true, + alignment: ${this.alertDialogAlignment}, + offset: { dx: 0, dy: -20 }, + buttonDirection: DialogButtonDirection.HORIZONTAL, + buttons: [ + { + value: '按钮1', + action: () => { + try { + promptAction.showToast({ + message: 'Callback when button1 is clicked', + duration: 2000 + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`Show toast error, the code is \${error.code}, the message is \${error.message}\`); + } + } + }, + { + value: '按钮2', + action: () => { + try { + promptAction.showToast({ + message: 'Callback when button2 is clicked', + duration: 2000 + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`Show toast error, the code is \${error.code}, the message is \${error.message}\`); + } + } + } + ], + cancel: () => { + console.log('Closed callbacks'); + }, + onWillDismiss: (dismissDialogAction: DismissDialogAction) => { + if (dismissDialogAction.reason === DismissReason.PRESS_BACK) { + dismissDialogAction.dismiss(); + } + if (dismissDialogAction.reason === DismissReason.TOUCH_OUTSIDE) { + dismissDialogAction.dismiss(); + } + } + }) + }) + } + .width('100%') + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/alertdialogview/viewmodel/AlertDialogDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/alertdialogview/viewmodel/AlertDialogDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..1018259588fbf4754f5832317f4f110bccf05aa3 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/alertdialogview/viewmodel/AlertDialogDescriptor.ets @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { alertDialogAlignmentMapData } from '../entity/AlertDialogAttributeMapping'; + +@Observed +export class AlertDialogDescriptor extends CommonDescriptor { + public alertDialogAlignment: DialogAlignment = alertDialogAlignmentMapData.get('Default')!.value as DialogAlignment; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'dialogAlignment': + this.alertDialogAlignment = + alertDialogAlignmentMapData.get(attribute.currentValue)?.value as DialogAlignment ?? + this.alertDialogAlignment; + break; + default: + break; + } + }) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/applinking/component/AppLinkingBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/applinking/component/AppLinkingBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..3bb8a851ddea119594960cbb9bc16ff16251d4c1 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/applinking/component/AppLinkingBuilder.ets @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apach 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 type { common, Want } from '@kit.AbilityKit'; +import type { BusinessError } from '@kit.BasicServicesKit'; +import { call } from '@kit.TelephonyKit'; +import { ConfigMapKey, Logger, ResourceUtil } from '@ohos/common'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import type { AppLinkingDescriptor } from '../viewmodel/AppLinkingDescriptor'; +import { LinkType, typeResourcesMapData, wantParam } from '../entity/AppLinkingAttributeMapping'; + +const TAG: string = '[AppLinkingComponent]'; + +@Builder +export function AppLinkingBuilder($$: DescriptorWrapper) { + AppLinkingComponent({ appLinkingDescriptor: $$.descriptor as AppLinkingDescriptor }) +} + +@Component +struct AppLinkingComponent { + @Prop appLinkingDescriptor: AppLinkingDescriptor; + private galleryUrl: string = ''; + + build() { + Column() { + Button($r('app.string.pull_up_page', typeResourcesMapData.get(this.appLinkingDescriptor.type))) + .backgroundColor($r('sys.color.background_secondary')) + .height($r('app.float.button_height_normal')) + .fontColor($r('sys.color.font_emphasize')) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .onClick(() => { + if (this.appLinkingDescriptor.type === LinkType.TYPE_GALLERY) { + this.jumpToGallery(); + } else if (this.appLinkingDescriptor.type === LinkType.TYPE_MAP) { + this.jumpToMap(); + } else if (this.appLinkingDescriptor.type === LinkType.TYPE_SETTINGS) { + this.jumpToSettings(); + } else if (this.appLinkingDescriptor.type === LinkType.TYPE_DAIL) { + this.jumpToDial(); + } + }) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + } + + jumpToGallery(): void { + const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; + if (!this.galleryUrl) { + this.galleryUrl = ResourceUtil.getRawFileStringByKey(context, ConfigMapKey.GALLERY_URL); + } + try { + context.openLink(this.galleryUrl, { appLinkingOnly: false }) + .then(() => { + Logger.info(TAG, 'OpenLink success.'); + }) + .catch((error: BusinessError) => { + Logger.error(TAG, `Openlink failed. Code: ${error.code}, message is ${error.message}`); + }); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, `Openlink failed., error code: ${err.code}, message: ${err.message}.`); + } + } + + jumpToMap(): void { + const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; + const abilityStartCallback: common.AbilityStartCallback = { + onError: (code: number, name: string, message: string) => { + Logger.error(TAG, `Fail start ability, name is ${name}, code is ${code}, message is ${message}`); + }, + onResult: (result) => { + Logger.debug(TAG, `Success in start ability, result is ${JSON.stringify(result)}`); + } + } + try { + context.startAbilityByType('navigation', wantParam, abilityStartCallback); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `StartAbilityByType error, the code is ${error.code}, the message is ${error.message}`); + } + } + + jumpToSettings(): void { + const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; + const want: Want = { + bundleName: 'com.huawei.hmos.settings', + abilityName: 'com.huawei.hmos.settings.MainAbility', + uri: '', + }; + try { + context.startAbility(want); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `StartAbility error, the code is ${error.code}, the message is ${error.message}`); + } + } + + jumpToDial(): void { + // Check whether support call function + let isSupport = call.hasVoiceCapability(); + if (isSupport) { + if (canIUse('SystemCapability.Applications.Contacts')) { + call.makeCall('', (err: BusinessError) => { + if (err) { + Logger.error(TAG, `MakeCall fail, error code ${err.code}, message: ${err.message}`); + } else { + Logger.info(TAG, `MakeCall success`); + } + }); + } + } else { + AlertDialog.show({ + title: '', + message: $r('app.string.not_support_dial'), + primaryButton: { + value: $r('app.string.sure'), + action: () => { + Logger.info(TAG, 'The device does not support dial-up.'); + } + } + }); + } + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/applinking/entity/AppLinkingAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/applinking/entity/AppLinkingAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..81768e7fa93b3ccc5b7cbfa2572be4efe52562f9 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/applinking/entity/AppLinkingAttributeMapping.ets @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2024 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 enum LinkType { + TYPE_GALLERY = '应用市场', + TYPE_MAP = '地图', + TYPE_SETTINGS = '设置', + TYPE_DAIL = '拨号', +} + +export const typeMapData: Map = new Map([ + ['Default', LinkType.TYPE_GALLERY], + ['应用市场', LinkType.TYPE_GALLERY], + ['地图', LinkType.TYPE_MAP], + ['设置', LinkType.TYPE_SETTINGS], + ['拨号', LinkType.TYPE_DAIL], +]); + +export const typeImportCodeMapData: Map = new Map([ + [LinkType.TYPE_GALLERY, `import { BusinessError } from '@kit.BasicServicesKit'; +import { common } from '@kit.AbilityKit';`], + [LinkType.TYPE_MAP, `import { common } from '@kit.AbilityKit';`], + [LinkType.TYPE_SETTINGS, `import { common, Want } from '@kit.AbilityKit';`], + [LinkType.TYPE_DAIL, `import { BusinessError } from '@kit.BasicServicesKit'; +import { call } from '@kit.TelephonyKit';`], +]); + +export const typeResourcesMapData: Map = new Map([ + [LinkType.TYPE_GALLERY, $r('app.string.pull_up_gallery')], + [LinkType.TYPE_MAP, $r('app.string.pull_up_map')], + [LinkType.TYPE_SETTINGS, $r('app.string.pull_up_settings')], + [LinkType.TYPE_DAIL, $r('app.string.pull_up_dail')], +]); + +export const wantParam: Record = { + 'sceneType': 1, + 'destinationLatitude': 40.00, + 'destinationLongitude': 116.19, + 'destinationName': '北京市香山公园', + 'originName': '华为北研所', + 'originLatitude': 40.06, + 'originLongitude': 116.18, + 'vehicleType': 0, +}; + +export const wantParamCode: string = `{ + 'sceneType': 1, + 'destinationLatitude': 40.00, + 'destinationLongitude': 116.19, + 'destinationName': '北京市香山公园', + 'originName': '华为北研所', + 'originLatitude': 40.06, + 'originLongitude': 116.18, + 'vehicleType': 0, + }`; + +export const linkCodeGallery: string = `const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; + const link: string = '%s'; + context.openLink(link, { appLinkingOnly: false }) + .then(() => { + console.log('OpenLink success.'); + }) + .catch((error: BusinessError) => { + console.log(\`Openlink failed. Code: \${error.code}, message is \${error.message}\`); + });`; + +export const linkCodeMap: string = `const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; + const abilityStartCallback: common.AbilityStartCallback = { + onError: (code: number, name: string, message: string) => { + console.log('Fail start ability'); + }, + onResult: (result) => { + console.log('Success in start ability.'); + } + } + try { + context.startAbilityByType('navigation',${wantParamCode}, abilityStartCallback); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`StartAbilityByType error, the code is \${error.code}, the message is \${error.message}\`); + }` + +export const linkCodeSettings: string = `const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; + const want: Want = { + bundleName: 'com.huawei.hmos.settings', + abilityName: 'com.huawei.hmos.settings.MainAbility', + uri: '', + }; + try { + context.startAbility(want);; + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`StartAbility error, the code is \${error.code}, the message is \${error.message}\`); + }` + +export const linkCodeDail: string = `call.makeCall('', (err: BusinessError) => { + if (err) { + console.log('MakeCall fail'); + } else { + console.log('MakeCall success'); + } + });`; + +export const typeInvokeCodeMapData: Map = new Map([ + [LinkType.TYPE_GALLERY, linkCodeGallery], + [LinkType.TYPE_MAP, linkCodeMap], + [LinkType.TYPE_SETTINGS, linkCodeSettings], + [LinkType.TYPE_DAIL, linkCodeDail], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/applinking/viewmodel/AppLinkingCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/applinking/viewmodel/AppLinkingCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..72a1acff26aa8447b3af44104d11fb054faf7379 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/applinking/viewmodel/AppLinkingCodeGenerator.ets @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 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 { ConfigMapKey, ResourceUtil } from '@ohos/common'; +import type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { + LinkType, + typeImportCodeMapData, + typeInvokeCodeMapData, + typeMapData, +} from '../entity/AppLinkingAttributeMapping'; + +export class AppLinkingCodeGenerator implements CommonCodeGenerator { + private type: LinkType = typeMapData.get('Default')!; + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'type': + this.type = typeMapData.get(attribute.currentValue) ?? this.type; + break; + default: + break; + } + }); + let codeStr: string = typeInvokeCodeMapData.get(this.type)!; + if (this.type === LinkType.TYPE_GALLERY) { + const linkUrl: string = ResourceUtil.getRawFileStringByKey(getContext(), ConfigMapKey.GALLERY_URL); + codeStr = codeStr.replace('%s', linkUrl); + } + return `${typeImportCodeMapData.get(this.type)!} + +@Component +struct AppLinkingComponent { + build() { + Column() { + Button('拉起${this.type}页') + .backgroundColor($r('sys.color.background_secondary')) + .height(40) + .fontColor($r('sys.color.font_emphasize')) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .onClick(() => { + ${codeStr} + }) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/applinking/viewmodel/AppLinkingDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/applinking/viewmodel/AppLinkingDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..9314108bac5aea17f7a194e01aa2921ac7577324 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/applinking/viewmodel/AppLinkingDescriptor.ets @@ -0,0 +1,35 @@ +/* +* Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { LinkType, typeMapData } from '../entity/AppLinkingAttributeMapping'; + +@Observed +export class AppLinkingDescriptor extends CommonDescriptor { + public type: LinkType = typeMapData.get('Default')!; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'type': + this.type = typeMapData.get(attribute.currentValue) ?? this.type; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/buttonview/component/ButtonBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/buttonview/component/ButtonBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..2943cbc314d6f932ff3c8c48d4607398afd98332 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/buttonview/component/ButtonBuilder.ets @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 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'; +import { Logger } from '@ohos/common'; +import { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import { ButtonAttributeModifier } from '../viewmodel/ButtonAttributeModifier'; +import type { ButtonDescriptor } from '../viewmodel/ButtonDescriptor'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; + +const TAG: string = '[ButtonBuilder]'; + +@Builder +export function ButtonBuilder($$: DescriptorWrapper) { + Button($r('app.string.button_text')) + .onClick(() => { + if (($$.descriptor as ButtonDescriptor).operation === 'Click') { + try { + promptAction.showToast({ + message: $r('app.string.btn_click'), + duration: DetailPageConstant.LONG_DURATION, + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Show toast error, the code is ${error.code}}, the message is ${error.message}`); + } + } + }) + .gesture( + LongPressGesture({ repeat: true }) + .onActionEnd((_event: GestureEvent) => { + if (($$.descriptor as ButtonDescriptor).operation === 'LongGesture') { + try { + promptAction.showToast({ + message: $r('app.string.button_text2'), + duration: DetailPageConstant.LONG_DURATION + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Show toast error, the code is ${error.code}}, the message is ${error.message}`); + } + } + }) + ) + .attributeModifier(new ButtonAttributeModifier($$.descriptor as ButtonDescriptor)) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/buttonview/entity/ButtonAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/buttonview/entity/ButtonAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..24fa1e4b91557fdac1f7d9e93ee5c5d308b933d1 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/buttonview/entity/ButtonAttributeMapping.ets @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024 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 { CommonColorMapping } from '../../common/entity/CommonMapData'; + +class ButtonStyleMapping { + public code: string; + public value: ButtonStyleMode; + + constructor(code: string, value: ButtonStyleMode) { + this.code = code; + this.value = value; + } +} + +class ControlSizeMapping { + public code: string; + public value: ControlSize; + + constructor(code: string, value: ControlSize) { + this.code = code; + this.value = value; + } +} + +class ButtonTypeMapping { + public code: string; + public value: ButtonType; + + constructor(code: string, value: ButtonType) { + this.code = code; + this.value = value; + } +} + +class ButtonActionMapping { + public code: string; + public value: string; + + constructor(code: string, value: string) { + this.code = code; + this.value = value; + } +} + +export const styleMapData: Map = new Map([ + ['Normal', new ButtonStyleMapping('ButtonStyleMode.NORMAL', ButtonStyleMode.NORMAL)], + ['Emphasized', new ButtonStyleMapping('ButtonStyleMode.EMPHASIZED', ButtonStyleMode.EMPHASIZED)], + ['Textual', new ButtonStyleMapping('ButtonStyleMode.TEXTUAL', ButtonStyleMode.TEXTUAL)], + ['Default', new ButtonStyleMapping('ButtonStyleMode.EMPHASIZED', ButtonStyleMode.EMPHASIZED)], +]); + +export const sizeMapData: Map = new Map([ + ['Normal', new ControlSizeMapping('ControlSize.NORMAL', ControlSize.NORMAL)], + ['Small', new ControlSizeMapping('ControlSize.SMALL', ControlSize.SMALL)], + ['Default', new ControlSizeMapping('ControlSize.SMALL', ControlSize.SMALL)], +]); + +export const buttonTypeMapData: Map = new Map([ + ['Capsule', new ButtonTypeMapping('ButtonType.Capsule', ButtonType.Capsule)], + ['Normal', new ButtonTypeMapping('ButtonType.Normal', ButtonType.Normal)], + ['Default', new ButtonTypeMapping('ButtonType.Capsule', ButtonType.Capsule)], +]); + +export const buttonBgColorMap: Map = new Map([ + ['Default', new CommonColorMapping('rgb(255, 255, 255)', 'rgb(255, 255, 255)')], +]); + +export const buttonActionMap: Map = new Map([ + ['Click', new ButtonActionMapping('Click', 'Click')], + ['LongGesture', new ButtonActionMapping('LongGesture', 'LongGesture')], + ['Default', new ButtonActionMapping('Click', 'Click')], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/buttonview/viewmodel/ButtonAttributeFilter.ets b/features/componentlibrary/src/main/ets/componentdetailview/buttonview/viewmodel/ButtonAttributeFilter.ets new file mode 100644 index 0000000000000000000000000000000000000000..e597fe4bcfc6c1eecce756e73af1855e6753043e --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/buttonview/viewmodel/ButtonAttributeFilter.ets @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 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 type { ObservedArray } from '@ohos/common'; +import type { Attribute } from '../../../viewmodel/Attribute'; +import { CommonAttributeFilter } from '../../../viewmodel/CommonAttributeFilter'; + +export class ButtonAttributeFilter implements CommonAttributeFilter { + public filter(attributes: ObservedArray): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'buttonStyle': + const buttonTypeIndex = attributes.findIndex((item) => item.name === 'buttonType'); + const operationIndex = attributes.findIndex((item) => item.name === 'operation'); + const backgroundColorIndex = attributes.findIndex((item) => item.name === 'backgroundColor'); + if (buttonTypeIndex === -1 || operationIndex === -1 || backgroundColorIndex === -1) { + return; + } + if (attribute.currentValue === 'Textual') { + attributes[buttonTypeIndex].enable = false; + attributes[operationIndex].enable = true; + attributes[backgroundColorIndex].enable = false; + } else if (attribute.currentValue === 'Emphasized') { + attributes[buttonTypeIndex].enable = true; + attributes[operationIndex].enable = true; + attributes[backgroundColorIndex].enable = true; + } else { + attributes[buttonTypeIndex].enable = true; + attributes[operationIndex].enable = true; + attributes[backgroundColorIndex].enable = false; + } + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/buttonview/viewmodel/ButtonAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/buttonview/viewmodel/ButtonAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..2fa4bb98cfcf75e1d53d269527b6dcc1b7b55bbe --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/buttonview/viewmodel/ButtonAttributeModifier.ets @@ -0,0 +1,27 @@ +/* +* Copyright (c) 2024 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 type { ButtonDescriptor } from './ButtonDescriptor'; +import { CommonAttributeModifier } from '../../../viewmodel/CommonAttributeModifier'; + +@Observed +export class ButtonAttributeModifier extends CommonAttributeModifier { + public applyNormalAttribute(instance: ButtonAttribute): void { + this.assignAttribute((descriptor => descriptor.buttonStyle), (val) => instance.buttonStyle(val)); + this.assignAttribute((descriptor => descriptor.controlSize), (val) => instance.controlSize(val)); + this.assignAttribute((descriptor => descriptor.buttonType), (val) => instance.type(val)); + this.assignAttribute((descriptor => descriptor.backgroundColor), (val) => instance.backgroundColor(val)); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/buttonview/viewmodel/ButtonCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/buttonview/viewmodel/ButtonCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..381de8e49ad23ab619401876f6a0b1896fccea8d --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/buttonview/viewmodel/ButtonCodeGenerator.ets @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { + buttonActionMap, + buttonBgColorMap, + buttonTypeMapData, + sizeMapData, + styleMapData, +} from '../entity/ButtonAttributeMapping'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; + +export class ButtonCodeGenerator implements CommonCodeGenerator { + private buttonStyle: string = styleMapData.get('Default')!.code; + private controlSize: string = sizeMapData.get('Default')!.code; + private buttonType: string = buttonTypeMapData.get('Default')!.code; + private backgroundColor: string = buttonBgColorMap.get('Default')!.code; + private operation: string = buttonActionMap.get('Default')!.code; + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'buttonStyle': + this.buttonStyle = styleMapData.get(attribute.currentValue)?.code ?? styleMapData.get('Default')!.code; + break; + case 'controlSize': + this.controlSize = sizeMapData.get(attribute.currentValue)?.code ?? sizeMapData.get('Default')!.code; + break; + case 'buttonType': + this.buttonType = + buttonTypeMapData.get(attribute.currentValue)?.code ?? buttonTypeMapData.get('Default')!.code; + break; + case 'backgroundColor': + this.backgroundColor = attribute.currentValue; + if (this.buttonStyle === styleMapData.get('Emphasized')!.code) { + this.backgroundColor = `'${attribute.currentValue}'`; + } else if (this.buttonStyle === styleMapData.get('Normal')!.code) { + this.backgroundColor = `$r('sys.color.comp_background_tertiary')`; + } else { + this.backgroundColor = `Color.Transparent`; + } + break; + case 'operation': + this.operation = attribute.currentValue; + break; + default: + break; + } + }); + let operationCode: string = ''; + if (this.operation === 'LongGesture') { + operationCode = `.gesture( + LongPressGesture({ repeat: true }) + .onActionEnd((event: GestureEvent) => { + try { + promptAction.showToast({ + message: '按钮被长按', + duration: 2000 + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`Show toast error, the code is \${error.code}, the message is \${error.message}\`); + } + }) + )`; + } else if (this.operation === 'Click') { + operationCode = `.onClick(() => { + try { + promptAction.showToast({ + message: '按钮被点击', + duration: 2000 + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`Show toast error, the code is \${error.code}, the message is \${error.message}\`); + } + })`; + } else { + operationCode = ``; + } + return `import { promptAction } from '@kit.ArkUI'; + +@Component +struct ButtonComponent { + build() { + Button('按钮示例', { type: ${this.buttonType} }) + .buttonStyle(${this.buttonStyle}) + .controlSize(${this.controlSize}) + .backgroundColor(${this.backgroundColor}) + ${operationCode} + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/buttonview/viewmodel/ButtonDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/buttonview/viewmodel/ButtonDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..dcbb927855680b1528c6b7efd3fd8be6148b4cb9 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/buttonview/viewmodel/ButtonDescriptor.ets @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { + buttonActionMap, + buttonBgColorMap, + buttonTypeMapData, + sizeMapData, + styleMapData, +} from '../entity/ButtonAttributeMapping'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; + +@Observed +export class ButtonDescriptor extends CommonDescriptor { + public buttonStyle: ButtonStyleMode = styleMapData.get('Default')!.value; + public controlSize: ControlSize = sizeMapData.get('Default')!.value; + public buttonType: ButtonType = buttonTypeMapData.get('Default')!.value; + public backgroundColor: ResourceColor = buttonBgColorMap.get('Default')!.value; + public operation: string = buttonActionMap.get('Default')!.value; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'buttonStyle': + this.buttonStyle = styleMapData.get(attribute.currentValue)?.value ?? styleMapData.get('Default')!.value; + break; + case 'controlSize': + this.controlSize = sizeMapData.get(attribute.currentValue)?.value ?? sizeMapData.get('Default')!.value; + break; + case 'buttonType': + this.buttonType = + buttonTypeMapData.get(attribute.currentValue)?.value ?? buttonTypeMapData.get('Default')!.value; + break; + case 'backgroundColor': + if (this.buttonStyle === ButtonStyleMode.EMPHASIZED) { + this.backgroundColor = attribute.currentValue; + } else if (this.buttonStyle === ButtonStyleMode.NORMAL) { + this.backgroundColor = $r('sys.color.comp_background_tertiary'); + } else { + this.backgroundColor = Color.Transparent; + } + break; + case 'operation': + this.operation = attribute.currentValue; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/columnview/component/ColumnBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/columnview/component/ColumnBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..6e4395fbd04d8a91239cdfaf75c743860224794d --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/columnview/component/ColumnBuilder.ets @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 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 { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import { ColumnAttributeModifier } from '../viewmodel/ColumnAttributeModifier'; +import type { ColumnDescriptor } from '../viewmodel/ColumnDescriptor'; + +@Builder +export function ColumnBuilder($$: DescriptorWrapper) { + Column({ space: ($$.descriptor as ColumnDescriptor).space }) { + Column() + .size({ width: $r('app.float.container_size_1'), height: $r('app.float.container_size_1') }) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .borderRadius($r('sys.float.corner_radius_level4')) + Column() + .size({ width: $r('app.float.container_size_2'), height: $r('app.float.container_size_2') }) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .borderRadius($r('sys.float.corner_radius_level4')) + Column() + .size({ width: $r('app.float.container_size_3'), height: $r('app.float.container_size_3') }) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .borderRadius($r('sys.float.corner_radius_level4')) + } + .width(($$.descriptor as ColumnDescriptor).padding === 'None' ? '50%' : 'auto') + .height(($$.descriptor as ColumnDescriptor).padding === 'None' ? '90%' : 'auto') + .padding(($$.descriptor as ColumnDescriptor).padding === 'Vertical' ? + { + top: ($$.descriptor as ColumnDescriptor).paddingNum, + bottom: ($$.descriptor as ColumnDescriptor).paddingNum, + } : ($$.descriptor as ColumnDescriptor).padding === 'Horizontal' ? { + left: ($$.descriptor as ColumnDescriptor).paddingNum, + right: ($$.descriptor as ColumnDescriptor).paddingNum, + } : ($$.descriptor as ColumnDescriptor).paddingNum) + .attributeModifier(new ColumnAttributeModifier($$.descriptor as ColumnDescriptor)) + .borderRadius($r('sys.float.corner_radius_level6')) + .border({ width: DetailPageConstant.CONTAINER_BORDER, color: $r('sys.color.comp_background_emphasize') }) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/columnview/entity/ColumnAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/columnview/entity/ColumnAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..4e2b07b9b40eaf18d8f4bb5f6642485f8db25364 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/columnview/entity/ColumnAttributeMapping.ets @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 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 { CommonNumberMapping, CommonStringMapping } from '../../common/entity/CommonMapData'; + +class ColumnAlignMapping { + public readonly code: string; + public readonly value: HorizontalAlign; + + constructor(code: string, value: HorizontalAlign) { + this.code = code; + this.value = value; + } +} + +export const columnAlignMapData: Map = new Map([ + ['Start', new ColumnAlignMapping('HorizontalAlign.Start', HorizontalAlign.Start)], + ['Center', new ColumnAlignMapping('HorizontalAlign.Center', HorizontalAlign.Center)], + ['End', new ColumnAlignMapping('HorizontalAlign.End', HorizontalAlign.End)], + ['Default', new ColumnAlignMapping('HorizontalAlign.Center', HorizontalAlign.Center)], +]); + +export const columnSpaceMapData: Map = new Map([ + ['Default', new CommonNumberMapping('3', 3)], +]); + +export const columnPaddingMapData: Map = new Map([ + ['Vertical', new CommonStringMapping('Vertical', 'Vertical')], + ['Horizontal', new CommonStringMapping('Horizontal', 'Horizontal')], + ['All', new CommonStringMapping('All', 'All')], + ['None', new CommonStringMapping('None', 'None')], + ['Default', new CommonStringMapping('All', 'All')], +]); + +export const paddingNumMapData: Map = new Map([ + ['Default', new CommonNumberMapping('3', 3)], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/columnview/viewmodel/ColumnAttributeFilter.ets b/features/componentlibrary/src/main/ets/componentdetailview/columnview/viewmodel/ColumnAttributeFilter.ets new file mode 100644 index 0000000000000000000000000000000000000000..317b8740273ea55fcf2388fb6055583e79f04ff0 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/columnview/viewmodel/ColumnAttributeFilter.ets @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 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 type { ObservedArray } from '@ohos/common'; +import type { Attribute } from '../../../viewmodel/Attribute'; +import { CommonAttributeFilter } from '../../../viewmodel/CommonAttributeFilter'; + +export class ColumnAttributeFilter implements CommonAttributeFilter { + public filter(attributes: ObservedArray): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'padding': + const paddingIndex = attributes.findIndex((item) => item.name === 'paddingNum'); + const flexAlignIndex = attributes.findIndex((item) => item.name === 'flexAlign'); + if (paddingIndex !== -1 && flexAlignIndex !== -1) { + if (attribute.currentValue === 'None') { + attributes[paddingIndex].enable = false; + attributes[flexAlignIndex].enable = true; + } else { + attributes[paddingIndex].enable = true; + attributes[flexAlignIndex].enable = false; + } + } + break; + case 'flexAlign': + const spaceIndex = attributes.findIndex((item) => item.name === 'space'); + if (spaceIndex !== -1) { + if (attribute.currentValue === 'SpaceBetween') { + attributes[spaceIndex].enable = false; + } else { + attributes[spaceIndex].enable = true; + } + } + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/columnview/viewmodel/ColumnAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/columnview/viewmodel/ColumnAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..5c8e56da99c5cecf0abb364e209a31a0304617ee --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/columnview/viewmodel/ColumnAttributeModifier.ets @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 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 { CommonAttributeModifier } from '../../../viewmodel/CommonAttributeModifier'; +import { ColumnDescriptor } from './ColumnDescriptor'; + +@Observed +export class ColumnAttributeModifier extends CommonAttributeModifier { + public applyNormalAttribute(instance: ColumnAttribute): void { + this.assignAttribute((descriptor => descriptor.alignItems), (val) => instance.alignItems(val)); + this.assignAttribute((descriptor => descriptor.flexAlign), (val) => instance.justifyContent(val)); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/columnview/viewmodel/ColumnCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/columnview/viewmodel/ColumnCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..46ff67c687c9a33bff36f068cf1f3862b8a20642 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/columnview/viewmodel/ColumnCodeGenerator.ets @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { rowJustifyContentMapData } from '../../rowview/entity/RowAttributeMapping'; +import { + columnAlignMapData, + columnPaddingMapData, + columnSpaceMapData, + paddingNumMapData, +} from '../entity/ColumnAttributeMapping'; + +export class ColumnCodeGenerator implements CommonCodeGenerator { + private alignItems: string = columnAlignMapData.get('Default')!.code; + private flexAlign: string = rowJustifyContentMapData.get('Default')!.code; + private space: string = columnSpaceMapData.get('Default')!.code; + private padding: string = columnPaddingMapData.get('Default')!.code; + private paddingNum: string = paddingNumMapData.get('Default')!.code; + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'alignItems': + this.alignItems = columnAlignMapData.get(attribute.currentValue)?.code ?? this.alignItems; + break; + case 'flexAlign': + this.flexAlign = rowJustifyContentMapData.get(attribute.currentValue)?.code ?? this.flexAlign; + break; + case 'space': + this.space = attribute.currentValue; + break; + case 'padding': + this.padding = attribute.currentValue; + break; + case 'paddingNum': + this.paddingNum = attribute.currentValue; + break; + default: + break; + } + }); + + let codeOne = ''; + if (this.padding === 'Vertical') { + codeOne = `.padding({ + top: ${this.paddingNum}, + bottom: ${this.paddingNum} + })`; + } else if (this.padding === 'Horizontal') { + codeOne = `.padding({ + left: ${this.paddingNum}, + right: ${this.paddingNum} + })`; + } else { + codeOne = `.padding(${this.paddingNum})`; + } + return `@Component +struct ColumnComponent { + build() { + Column({ space: ${this.space} }){ + Column() + .size({ width: 40, height: 40 }) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .borderRadius($r('sys.float.corner_radius_level4')) + Column() + .size({ width: 52, height: 52 }) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .borderRadius($r('sys.float.corner_radius_level4')) + Column() + .size({ width: 64, height: 64 }) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .borderRadius($r('sys.float.corner_radius_level4')) + } + .alignItems(${this.alignItems}) + .justifyContent(${this.flexAlign}) + .borderRadius($r('sys.float.corner_radius_level6')) + .width('${this.padding === 'None' ? '50%' : 'auto'}') + .height('${this.padding === 'None' ? '90%' : 'auto'}') + .border({ width: 1, color: $r('sys.color.comp_background_emphasize') }) + ${codeOne} + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/columnview/viewmodel/ColumnDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/columnview/viewmodel/ColumnDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..67293c71d9641bc56008ee8e7e44446ff5813a5b --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/columnview/viewmodel/ColumnDescriptor.ets @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { + columnAlignMapData, + columnPaddingMapData, + columnSpaceMapData, + paddingNumMapData, +} from '../entity/ColumnAttributeMapping'; +import { rowJustifyContentMapData } from '../../rowview/entity/RowAttributeMapping'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; + +@Observed +export class ColumnDescriptor extends CommonDescriptor { + public alignItems: HorizontalAlign = columnAlignMapData.get('Default')!.value; + public flexAlign: FlexAlign = rowJustifyContentMapData.get('Default')!.value; + public space: number = columnSpaceMapData.get('Default')!.value; + public padding: string = columnPaddingMapData.get('Default')!.value; + public paddingNum: number = paddingNumMapData.get('Default')!.value; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'alignItems': + this.alignItems = columnAlignMapData.get(attribute.currentValue)?.value ?? this.alignItems; + break; + case 'flexAlign': + this.flexAlign = rowJustifyContentMapData.get(attribute.currentValue)?.value ?? this.flexAlign; + break; + case 'space': + this.space = Number(attribute.currentValue); + break; + case 'padding': + this.padding = attribute.currentValue; + break; + case 'paddingNum': + this.paddingNum = Number(attribute.currentValue); + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/common/entity/CommonMapData.ets b/features/componentlibrary/src/main/ets/componentdetailview/common/entity/CommonMapData.ets new file mode 100644 index 0000000000000000000000000000000000000000..668a84b8dd06bc632adae009fa9e120034d3bb6a --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/common/entity/CommonMapData.ets @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024 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 CommonColorMapping { + public readonly code: string; + public readonly value: string; + + constructor(code: string, value: string) { + this.code = code; + this.value = value; + } +} + +export const commonFontColorMap: Map = new Map([ + ['Default', new CommonColorMapping('rgba(0, 246, 255, 1.0)', 'rgba(0, 246, 255, 1.0)')], +]); + +export const highlightColorMap: Map = new Map([ + ['Default', new CommonColorMapping('rgba(0, 85, 255, 1.0)', 'rgba(0, 85, 255, 1.0)')], +]); + +export class CommonFontWeightMapping { + public readonly code: string; + public readonly value: FontWeight; + + constructor(code: string, value: FontWeight) { + this.code = code; + this.value = value; + } +} + +export const fontWeightMapData: Map = new Map([ + ['Normal', new CommonFontWeightMapping('FontWeight.Normal', FontWeight.Normal)], + ['Lighter', new CommonFontWeightMapping('FontWeight.Lighter', FontWeight.Lighter)], + ['Bolder', new CommonFontWeightMapping('FontWeight.Bolder', FontWeight.Bolder)], + ['Default', new CommonFontWeightMapping('FontWeight.Normal', FontWeight.Normal)], +]); + +export class CommonNumberMapping { + public readonly code: string; + public readonly value: number; + + constructor(code: string, value: number) { + this.code = code; + this.value = value; + } +} + +export class CommonStringMapping { + public readonly code: string; + public readonly value: string; + + constructor(code: string, value: string) { + this.code = code; + this.value = value; + } +} + +export class CommonBoolMapping { + public readonly code: string; + public readonly value: boolean; + + constructor(code: string, value: boolean) { + this.code = code; + this.value = value; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/common/entity/CommonStorageKey.ets b/features/componentlibrary/src/main/ets/componentdetailview/common/entity/CommonStorageKey.ets new file mode 100644 index 0000000000000000000000000000000000000000..46c12dbafc796f6a9d3f7a0229aa3de4265907da --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/common/entity/CommonStorageKey.ets @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024 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 CommonStorageKey { + public static readonly KEY_MATTING_TIP: string = 'key.matting.tip'; + public static readonly KEY_GRID_TIP: string = 'key.grid.tip'; +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/customdialog/component/CustomDialogBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/customdialog/component/CustomDialogBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..710c1920133f6541abfb3b4bfbc34d637a3fef43 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/customdialog/component/CustomDialogBuilder.ets @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 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 type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import type { CustomDialogDescriptor } from '../viewmodel/CustomDialogDescriptor'; +import { CustomDialogComponent } from './CustomDialogComponent'; + +@Builder +export function CustomDialogBuilder($$: DescriptorWrapper) { + CustomDialogComponent({ customDialogDescriptor: $$.descriptor as CustomDialogDescriptor }) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/customdialog/component/CustomDialogComponent.ets b/features/componentlibrary/src/main/ets/componentdetailview/customdialog/component/CustomDialogComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..f34c03ecc7327f542550b2fb16f7331d5783a360 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/customdialog/component/CustomDialogComponent.ets @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2024 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 { CustomContentDialog, TipsDialog } from '@kit.ArkUI'; +import { Logger } from '@ohos/common'; +import { CustomDialogStyle, dialogResourceMapData } from '../entity/CustomDialogAttributeMapping'; +import type { CustomDialogDescriptor } from '../viewmodel/CustomDialogDescriptor'; + +const TAG: string = '[CustomDialogComponent]'; +const PROGRESS_VALUE: number = 20; + +@Component +export struct CustomDialogComponent { + @Prop customDialogDescriptor: CustomDialogDescriptor; + @State isChecked: boolean = true; + private tipDialogController?: CustomDialogController = new CustomDialogController({ + builder: this.tipDialogBuilder, + cancel: this.existApp, + onWillDismiss: (dismissDialogAction: DismissDialogAction) => { + if (dismissDialogAction.reason === DismissReason.PRESS_BACK) { + dismissDialogAction.dismiss(); + } + if (dismissDialogAction.reason === DismissReason.TOUCH_OUTSIDE) { + dismissDialogAction.dismiss(); + } + }, + alignment: DialogAlignment.Center, + autoCancel: true, + cornerRadius: $r('sys.float.padding_level10'), + }); + private progressDialogController?: CustomDialogController = new CustomDialogController({ + builder: this.progressDialogBuilder, + cancel: this.existApp, + onWillDismiss: (dismissDialogAction: DismissDialogAction) => { + if (dismissDialogAction.reason === DismissReason.PRESS_BACK) { + dismissDialogAction.dismiss(); + } + if (dismissDialogAction.reason === DismissReason.TOUCH_OUTSIDE) { + dismissDialogAction.dismiss(); + } + }, + alignment: DialogAlignment.Center, + autoCancel: true, + cornerRadius: $r('sys.float.padding_level10'), + }); + + @Builder + tipDialogBuilder() { + TipsDialog({ + imageRes: $r('app.media.image_dialog'), + imageSize: { + width: 'auto', + height: $r('app.float.dialog_contain_image_height'), + }, + title: $r('app.string.dialog_graphic_title'), + content: $r('app.string.dialog_graphic_content'), + isChecked: this.isChecked, + checkTips: $r('app.string.dialog_graphic_tip'), + onCheckedChange: () => { + this.isChecked = !this.isChecked; + }, + primaryButton: { + value: $r('app.string.dialog_cancel'), + action: () => { + this.onCancel(); + this.tipDialogController?.close(); + }, + }, + secondaryButton: { + value: $r('app.string.dialog_confirm'), + action: () => { + this.onAccept(); + this.tipDialogController?.close(); + }, + }, + }) + } + + @Builder + progressDialogBuilder() { + CustomContentDialog({ + contentBuilder: () => { + this.buildContent(); + }, + buttons: [ + { + value: $r('app.string.dialog_cancel'), + action: () => { + this.onCancel(); + this.progressDialogController?.close(); + }, + }, { + value: $r('app.string.dialog_confirm'), + action: () => { + this.onAccept(); + this.progressDialogController?.close(); + }, + }, + ], + }) + } + + @Builder + buildContent(): void { + Column() { + Row() { + Text($r('app.string.title')) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Subtitle_M')) + .textAlign(TextAlign.Start) + Blank() + Text($r('app.string.dialog_progress', PROGRESS_VALUE)) + .fontColor($r('sys.color.font_secondary')) + .fontSize($r('sys.float.Body_M')) + .width($r('app.float.text_height_large')) + .textAlign(TextAlign.Center) + } + .width('100%') + .height($r('app.float.dialog_text_height')) + .alignItems(VerticalAlign.Center) + + Progress({ value: PROGRESS_VALUE }) + .height($r('app.float.dialog_progress_height')) + .margin({ top: $r('sys.float.padding_level4'), bottom: $r('sys.float.padding_level2') }) + } + } + + aboutToDisappear() { + this.tipDialogController = undefined; + this.progressDialogController = undefined; + } + + onCancel() { + Logger.info(TAG, 'Callback when the first button is clicked'); + } + + onAccept() { + Logger.info(TAG, 'Callback when the second button is clicked'); + } + + existApp() { + Logger.info(TAG, 'Click the callback in the blank area'); + } + + build() { + Column() { + Button(dialogResourceMapData.get(this.customDialogDescriptor.style)) + .buttonStyle(ButtonStyleMode.NORMAL) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_emphasize')) + .onClick(() => { + if (this.customDialogDescriptor.style === CustomDialogStyle.StyleProgress) { + this.progressDialogController?.open(); + } else { + this.tipDialogController?.open(); + } + }) + } + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/customdialog/entity/CustomDialogAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/customdialog/entity/CustomDialogAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..583ac057c5d9d4e08d65ce3f50fdd48c0b329a75 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/customdialog/entity/CustomDialogAttributeMapping.ets @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2024 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 enum CustomDialogStyle { + StyleImage = '图文弹窗', + StyleProgress = '进度弹窗', +} + +export const dialogStyleMapData: Map = new Map([ + ['Default', CustomDialogStyle.StyleImage], + ['图文弹窗', CustomDialogStyle.StyleImage], + ['进度弹窗', CustomDialogStyle.StyleProgress], +]); + +export const dialogResourceMapData: Map = new Map([ + [CustomDialogStyle.StyleImage, $r('app.string.custom_graphic_dialog')], + [CustomDialogStyle.StyleProgress, $r('app.string.custom_progress_dialog')], +]); + +export const dialogImportCodeMapData: Map = new Map([ + [CustomDialogStyle.StyleImage, `import { TipsDialog } from '@kit.ArkUI';`], + [CustomDialogStyle.StyleProgress, `import { CustomContentDialog } from '@kit.ArkUI';`], +]); + +export const styleGraphicBuilderCode = ` @Builder + tipDialogBuilder() { + TipsDialog({ + // 替换自己项目src/main/resources/base/media下的资源图片 + imageRes: $r('app.media.image_dialog'), + imageSize: { + width: 'auto', + height: 180 + }, + title: '带图形确认框', + content: '必要时可以通过图形化方式展现确认框,以使用户更好理解或认同确认内容', + isChecked: this.isChecked, + checkTips: '我已知晓上述内容,不再提醒', + onCheckedChange: () => { + this.isChecked = !this.isChecked; + }, + primaryButton: { + value: '取消', + action: () => { + this.onCancel(); + this.dialogController?.close(); + console.info('Callback when the first button is clicked'); + }, + }, + secondaryButton: { + value: '确认', + action: () => { + this.onAccept(); + this.dialogController?.close(); + console.info('Callback when the second button is clicked'); + } + } + }) + }`; + +export const styleProgressBuilderCode: string = ` @Builder + progressDialogBuilder() { + CustomContentDialog({ + contentBuilder: () => { + this.buildContent(); + }, + buttons: [{ + value: '取消', + action: () => { + this.onCancel(); + this.dialogController?.close(); + } + }, { + value: '确认', + action: () => { + this.onAccept(); + this.dialogController?.close(); + } + }] + }) + } + + @Builder + buildContent(): void { + Column() { + Row() { + Text('标题') + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Subtitle_M')) + .textAlign(TextAlign.Start) + Blank() + Text('20%') + .fontColor($r('sys.color.font_secondary')) + .fontSize($r('sys.float.Body_M')) + .width(32) + .textAlign(TextAlign.Center) + } + .width('100%') + .height(20) + .alignItems(VerticalAlign.Center) + + Progress({ value: 20 }) + .height(24) + .margin({ top: $r('sys.float.padding_level4'), bottom: $r('sys.float.padding_level2') }) + } + }`; + +export const dialogBuilderCodeMapData: Map = new Map([ + [CustomDialogStyle.StyleImage, styleGraphicBuilderCode], + [CustomDialogStyle.StyleProgress, styleProgressBuilderCode], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/customdialog/viewmodel/CustomDialogCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/customdialog/viewmodel/CustomDialogCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..943c4572668f4f907eb3f145b931ec95e9828f48 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/customdialog/viewmodel/CustomDialogCodeGenerator.ets @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { + CustomDialogStyle, + dialogBuilderCodeMapData, + dialogImportCodeMapData, + dialogStyleMapData, +} from '../entity/CustomDialogAttributeMapping'; + +export class CustomDialogCodeGenerator implements CommonCodeGenerator { + private style: CustomDialogStyle = dialogStyleMapData.get('Default')!; + + public generate(attributes: OriginAttribute[]): string { + let builderParamCode: string = 'this.tipDialogBuilder'; + let isCheckedParamCode: string = ''; + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'style': + this.style = dialogStyleMapData.get(attribute.currentValue) ?? this.style; + if (this.style === CustomDialogStyle.StyleProgress) { + builderParamCode = 'this.progressDialogBuilder'; + isCheckedParamCode = ''; + } else { + builderParamCode = 'this.tipDialogBuilder'; + isCheckedParamCode = ` + @State isChecked: boolean = true;`; + } + break; + default: + break; + } + }); + return `${dialogImportCodeMapData.get(this.style)} + +@Component +export struct CustomDialogComponent {${isCheckedParamCode} + private dialogController: CustomDialogController | undefined = new CustomDialogController({ + builder: ${builderParamCode}, + cancel: this.existApp, + onWillDismiss: (dismissDialogAction: DismissDialogAction) => { + if (dismissDialogAction.reason === DismissReason.PRESS_BACK) { + dismissDialogAction.dismiss(); + } + if (dismissDialogAction.reason === DismissReason.TOUCH_OUTSIDE) { + dismissDialogAction.dismiss(); + } + }, + alignment: DialogAlignment.Center, + autoCancel: true, + cornerRadius: $r('sys.float.padding_level10') + }); + +${dialogBuilderCodeMapData.get(this.style)} + + aboutToDisappear() { + this.dialogController = undefined; + } + + onCancel() { + console.info('Callback when the first button is clicked'); + } + + onAccept() { + console.info('Callback when the second button is clicked'); + } + + existApp() { + console.info('Click the callback in the blank area'); + } + + build() { + Column() { + Button('自定义${this.style}') + .buttonStyle(ButtonStyleMode.NORMAL) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_emphasize')) + .onClick(() => { + this.dialogController?.open(); + }) + } + .width('100%') + } +}` + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/customdialog/viewmodel/CustomDialogDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/customdialog/viewmodel/CustomDialogDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..11408eb1b52e391f8e33a9d75093a0d10c3c9fe6 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/customdialog/viewmodel/CustomDialogDescriptor.ets @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { CustomDialogStyle, dialogStyleMapData } from '../entity/CustomDialogAttributeMapping'; + +@Observed +export class CustomDialogDescriptor extends CommonDescriptor { + public style: CustomDialogStyle = dialogStyleMapData.get('Default')!; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'style': + this.style = dialogStyleMapData.get(attribute.currentValue) ?? this.style; + break; + default: + break; + } + }) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/documentviewpicker/component/DocumentViewPickerBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/documentviewpicker/component/DocumentViewPickerBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..290ac181cb13dde26566627c2d6e8e777ab6faf5 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/documentviewpicker/component/DocumentViewPickerBuilder.ets @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apach 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 type { common } from '@kit.AbilityKit'; +import { picker } from '@kit.CoreFileKit'; +import type { BusinessError } from '@kit.BasicServicesKit'; +import { Logger } from '@ohos/common'; +import { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; + +const TAG: string = '[DocumentViewPickerComponent]'; + +@Builder +export function DocumentViewPickerBuilder(_$$: DescriptorWrapper) { + DocumentViewPickerComponent() +} + +@Component +struct DocumentViewPickerComponent { + @State message: string = ''; + @State title: string = ''; + + readFile() { + const context = getContext(this) as common.Context; + try { + const documentSelectOptions = new picker.DocumentSelectOptions(); + const documentPicker = new picker.DocumentViewPicker(context); + documentPicker.select(documentSelectOptions).then((documentSelectResult: string[]) => { + if (documentSelectResult.length === 0) { + return; + } + this.message = JSON.stringify(documentSelectResult); + this.getUIContext().showAlertDialog( + { + title: $r('app.string.File_path'), + message: this.message, + autoCancel: true, + alignment: DialogAlignment.Center, + offset: { dx: 0, dy: DetailPageConstant.ALERT_DIALOG_OFFSET_Y }, + gridCount: 3, + width: DetailPageConstant.SELECT_RESULT_DIALOG_SIZE, + height: DetailPageConstant.SELECT_RESULT_DIALOG_SIZE, + cornerRadius: $r('sys.float.corner_radius_level7'), + borderWidth: $r('app.float.border_width_normal'), + borderStyle: BorderStyle.Dashed, + borderColor: Color.Blue, + backgroundColor: Color.White, + textStyle: { wordBreak: WordBreak.BREAK_ALL }, + confirm: { + value: $r('app.string.dialog_confirm'), + action: () => { + Logger.info(TAG, 'Confirm button is clicked.'); + }, + }, + onWillDismiss: (dismissDialogAction: DismissDialogAction) => { + if (dismissDialogAction.reason === DismissReason.PRESS_BACK) { + dismissDialogAction.dismiss(); + } + if (dismissDialogAction.reason === DismissReason.TOUCH_OUTSIDE) { + dismissDialogAction.dismiss(); + } + }, + } + ) + }).catch((err: BusinessError) => { + Logger.error(TAG, `DocumentViewPicker.select failed with err: ${err.code}, ${err.message}`); + }); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, `DocumentViewPicker failed with err: ${err.code}, ${err.message}`); + } + } + + build() { + Column() { + Button($r('app.string.select_document')) + .backgroundColor($r('sys.color.background_secondary')) + .width($r('app.float.document_select_button_width')) + .height($r('app.float.button_height_normal')) + .fontColor($r('sys.color.font_emphasize')) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .onClick(() => { + this.readFile(); + }) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/documentviewpicker/viewmodel/DocumentViewPickerCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/documentviewpicker/viewmodel/DocumentViewPickerCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..61d88d62d8c257153cf1b47457e8c21bbccecb8f --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/documentviewpicker/viewmodel/DocumentViewPickerCodeGenerator.ets @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; + +export class DocumentViewPickerCodeGenerator implements CommonCodeGenerator { + public generate(_attributes: OriginAttribute[]): string { + return `import { common } from '@kit.AbilityKit'; +import { picker } from '@kit.CoreFileKit'; +import type { BusinessError } from '@kit.BasicServicesKit'; + +@Component +struct DocumentViewPickerComponent { + @State message: string = ''; + @State title: string = ''; + + build() { + Column() { + Button('选择文件') + .backgroundColor($r('sys.color.background_secondary')) + .width(120) + .height(40) + .fontColor($r('sys.color.font_emphasize')) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .onClick(() => { + let context = getContext(this) as common.Context; + try { + const documentSelectOptions = new picker.DocumentSelectOptions(); + const documentPicker = new picker.DocumentViewPicker(context); + documentPicker.select(documentSelectOptions).then((documentSelectResult: Array) => { + this.message = JSON.stringify(documentSelectResult); + if (documentSelectResult.length === 0) { + return; + } + this.getUIContext().showAlertDialog( + { + title: '文件路径', + message: this.message, + autoCancel: true, + alignment: DialogAlignment.Center, + offset: { dx: 0, dy: -20 }, + gridCount: 3, + width: 300, + height: 300, + cornerRadius: $r('sys.float.corner_radius_level7'), + borderWidth: 1, + borderStyle: BorderStyle.Dashed, + borderColor: Color.Blue, + backgroundColor: Color.White, + textStyle: { wordBreak: WordBreak.BREAK_ALL }, + confirm: { + value: '确定', + action: () => { + console.log('Confirm button is clicked.'); + }, + }, + onWillDismiss: (dismissDialogAction: DismissDialogAction) => { + if (dismissDialogAction.reason === DismissReason.PRESS_BACK) { + dismissDialogAction.dismiss(); + } + if (dismissDialogAction.reason === DismissReason.TOUCH_OUTSIDE) { + dismissDialogAction.dismiss(); + } + } + } + ) + }).catch((err: BusinessError) => { + console.error(\`DocumentViewPicker.select failed with err: \${err.code}, \${err.message}\`); + }); + } catch (error) { + const err: BusinessError = error as BusinessError; + console.error(\`DocumentViewPicker failed with err: \${err.code}, \${err.message}\`); + } + }) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/flex/component/FlexBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/flex/component/FlexBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..e3bd23d66eb429a484af00698e7bcbd4a8572c04 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/flex/component/FlexBuilder.ets @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024 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 { LengthMetrics } from '@kit.ArkUI'; +import { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import type { FlexDescriptor } from '../viewmodel/FlexDescriptor'; +import { ElementsNums } from '../entity/FlexAttributeMapping'; + +@Builder +export function FlexBuilder($$: DescriptorWrapper) { + Flex({ + space: { + main: LengthMetrics.vp(DetailPageConstant.FLEX_SPACE), + cross: LengthMetrics.vp(DetailPageConstant.FLEX_SPACE), + }, + direction: ($$.descriptor as FlexDescriptor).direction, + wrap: ($$.descriptor as FlexDescriptor).wrap, + justifyContent: ($$.descriptor as FlexDescriptor).justifyContent, + alignItems: ($$.descriptor as FlexDescriptor).alignItems, + alignContent: ($$.descriptor as FlexDescriptor).alignContent, + }) { + Text('1') + .size({ width: $r('app.float.container_size_1'), height: $r('app.float.container_size_1') }) + .fontColor($r('sys.color.font_on_primary')) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .textAlign(TextAlign.Center) + .border({ radius: $r('sys.float.corner_radius_level4') }) + Text('2') + .size({ width: $r('app.float.container_size_2'), height: $r('app.float.container_size_2') }) + .fontColor($r('sys.color.font_on_primary')) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .textAlign(TextAlign.Center) + .border({ radius: $r('sys.float.corner_radius_level4') }) + .alignSelf(($$.descriptor as FlexDescriptor).alignSelf) + Text('3') + .size({ width: $r('app.float.container_size_3'), height: $r('app.float.container_size_3') }) + .fontColor($r('sys.color.font_on_primary')) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .textAlign(TextAlign.Center) + .border({ radius: $r('sys.float.corner_radius_level4') }) + .visibility(($$.descriptor as FlexDescriptor).elements === ElementsNums.FOUR ? Visibility.Visible : + Visibility.None) + Text('4') + .size({ width: $r('app.float.container_size_4'), height: $r('app.float.container_size_4') }) + .fontColor($r('sys.color.font_on_primary')) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .textAlign(TextAlign.Center) + .border({ radius: $r('sys.float.corner_radius_level4') }) + .visibility(($$.descriptor as FlexDescriptor).elements === ElementsNums.FOUR ? Visibility.Visible : + Visibility.None) + } + .height($r('app.float.container_height')) + .width($r('app.float.container_width')) + .padding($r('sys.float.padding_level3')) + .border({ + width: DetailPageConstant.CONTAINER_BORDER, + color: $r('sys.color.comp_background_emphasize'), + radius: $r('sys.float.corner_radius_level6'), + }) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/flex/entity/FlexAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/flex/entity/FlexAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..1bd2ec92358867d3ccc972559b09b964c2a9eeae --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/flex/entity/FlexAttributeMapping.ets @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 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 enum ElementsNums { + TWO = '2', + FOUR = '4', +} + +type FlexValueType = FlexDirection | FlexWrap | FlexAlign | ItemAlign | ElementsNums; + +class FlexDictionaryMapping { + public readonly code: string; + public readonly value: FlexValueType; + + constructor(code: string, value: FlexValueType) { + this.code = code; + this.value = value; + } +} + +export const flexDirectionMapData: Map = new Map([ + ['Default', new FlexDictionaryMapping('FlexDirection.Row', FlexDirection.Row)], + ['Row', new FlexDictionaryMapping('FlexDirection.Row', FlexDirection.Row)], + ['RowReverse', new FlexDictionaryMapping('FlexDirection.RowReverse', FlexDirection.RowReverse)], + ['Column', new FlexDictionaryMapping('FlexDirection.Column', FlexDirection.Column)], + ['ColumnReverse', new FlexDictionaryMapping('FlexDirection.ColumnReverse', FlexDirection.ColumnReverse)], +]); + +export const flexWrapMapData: Map = new Map([ + ['Default', new FlexDictionaryMapping('FlexWrap.NoWrap', FlexWrap.NoWrap)], + ['NoWrap', new FlexDictionaryMapping('FlexWrap.NoWrap', FlexWrap.NoWrap)], + ['Wrap', new FlexDictionaryMapping('FlexWrap.Wrap', FlexWrap.Wrap)], + ['WrapReverse', new FlexDictionaryMapping('FlexWrap.WrapReverse', FlexWrap.WrapReverse)], +]); + +export const flexContentMapData: Map = new Map([ + ['Default', new FlexDictionaryMapping('FlexAlign.Start', FlexAlign.Start)], + ['Start', new FlexDictionaryMapping('FlexAlign.Start', FlexAlign.Start)], + ['Center', new FlexDictionaryMapping('FlexAlign.Center', FlexAlign.Center)], + ['End', new FlexDictionaryMapping('FlexAlign.End', FlexAlign.End)], + ['SpaceBetween', new FlexDictionaryMapping('FlexAlign.SpaceBetween', FlexAlign.SpaceBetween)], + ['SpaceAround', new FlexDictionaryMapping('FlexAlign.SpaceAround', FlexAlign.SpaceAround)], + ['SpaceEvenly', new FlexDictionaryMapping('FlexAlign.SpaceEvenly', FlexAlign.SpaceEvenly)], +]); + +export const flexAlignItemMapData: Map = new Map([ + ['Default', new FlexDictionaryMapping('ItemAlign.Auto', ItemAlign.Auto)], + ['Auto', new FlexDictionaryMapping('ItemAlign.Auto', ItemAlign.Auto)], + ['Start', new FlexDictionaryMapping('ItemAlign.Start', ItemAlign.Start)], + ['Center', new FlexDictionaryMapping('ItemAlign.Center', ItemAlign.Center)], + ['End', new FlexDictionaryMapping('ItemAlign.End', ItemAlign.End)], + ['Stretch', new FlexDictionaryMapping('ItemAlign.Stretch', ItemAlign.Stretch)], + ['Baseline', new FlexDictionaryMapping('ItemAlign.Baseline', ItemAlign.Baseline)], +]); + +export const elementsNumsMapData: Map = new Map([ + ['Default', new FlexDictionaryMapping('2', ElementsNums.TWO)], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/flex/viewmodel/FlexAttributeFilter.ets b/features/componentlibrary/src/main/ets/componentdetailview/flex/viewmodel/FlexAttributeFilter.ets new file mode 100644 index 0000000000000000000000000000000000000000..aee3439d607963de2a26ce7fb340b5202aeda165 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/flex/viewmodel/FlexAttributeFilter.ets @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 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 { ObservedArray } from '@ohos/common'; +import type { Attribute } from '../../../viewmodel/Attribute'; +import { CommonAttributeFilter } from '../../../viewmodel/CommonAttributeFilter'; + +export class FlexAttributeFilter implements CommonAttributeFilter { + public filter(attributes: ObservedArray): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'wrap': + const alignSelfIndex = attributes.findIndex((item) => item.name === 'alignSelf'); + const alignContentIndex = attributes.findIndex((item) => item.name === 'alignContent'); + if (alignSelfIndex !== -1 && alignContentIndex !== -1) { + if (attribute.currentValue === 'Wrap' || attribute.currentValue === 'WrapReverse') { + attributes[alignSelfIndex].enable = false; + attributes[alignContentIndex].enable = true; + } else if (attribute.currentValue === 'NoWrap') { + attributes[alignSelfIndex].enable = true; + attributes[alignContentIndex].enable = false; + } + } + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/flex/viewmodel/FlexCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/flex/viewmodel/FlexCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..5e3d6f16ca6030a0a9f11e07e16e801e1e133802 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/flex/viewmodel/FlexCodeGenerator.ets @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { + ElementsNums, + flexAlignItemMapData, + flexContentMapData, + flexDirectionMapData, + flexWrapMapData, +} from '../entity/FlexAttributeMapping'; + +export class FlexCodeGenerator implements CommonCodeGenerator { + private direction: string = flexDirectionMapData.get('Default')!.code; + private wrap: string = flexWrapMapData.get('Default')!.code; + private justifyContent: string = flexContentMapData.get('Default')!.code; + private alignItems: string = flexAlignItemMapData.get('Default')!.code; + private alignSelf: string = flexAlignItemMapData.get('Default')!.code; + private alignContent: string = flexContentMapData.get('Default')!.code; + + public generate(attributes: OriginAttribute[]): string { + let codeFragment = ''; + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'elements': + codeFragment = attribute.currentValue === ElementsNums.FOUR ? ` + Text('3') + .width('64vp') + .height('64vp') + .fontColor($r('sys.color.font_on_primary')) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .textAlign(TextAlign.Center) + .border({ radius: $r('sys.float.corner_radius_level4') }) + Text('4') + .width('76vp') + .height('76vp') + .fontColor($r('sys.color.font_on_primary')) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .textAlign(TextAlign.Center) + .border({ radius: $r('sys.float.corner_radius_level4') })` : ``; + break; + case 'direction': + this.direction = + flexDirectionMapData.get(attribute.currentValue)?.code ?? flexDirectionMapData.get('Default')!.code; + break; + case 'wrap': + this.wrap = flexWrapMapData.get(attribute.currentValue)?.code ?? flexWrapMapData.get('Default')!.code; + break; + case 'justifyContent': + this.justifyContent = + flexContentMapData.get(attribute.currentValue)?.code ?? flexContentMapData.get('Default')!.code; + break; + case 'alignItems': + this.alignItems = + flexAlignItemMapData.get(attribute.currentValue)?.code ?? flexAlignItemMapData.get('Default')!.code; + break; + case 'alignContent': + this.alignContent = + flexContentMapData.get(attribute.currentValue)?.code ?? flexContentMapData.get('Default')!.code; + break; + case 'alignSelf': + this.alignSelf = + flexAlignItemMapData.get(attribute.currentValue)?.code ?? flexAlignItemMapData.get('Default')!.code; + break; + default: + break; + } + }); + return `import { LengthMetrics } from '@kit.ArkUI'; + +@Component +struct FlexComponent { + build() { + Flex({ + space: { main: LengthMetrics.vp(6), cross: LengthMetrics.vp(6) }, + direction: ${this.direction}, + wrap: ${this.wrap}, + justifyContent: ${this.justifyContent}, + alignItems: ${this.alignItems}, + alignContent: ${this.alignContent} + }) { + Text('1') + .width('40vp') + .height('40vp') + .fontColor($r('sys.color.font_on_primary')) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .textAlign(TextAlign.Center) + .border({ radius: $r('sys.float.corner_radius_level4') }) + Text('2') + .width('52vp') + .height('52vp') + .fontColor($r('sys.color.font_on_primary')) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .textAlign(TextAlign.Center) + .border({ radius: $r('sys.float.corner_radius_level4') }) + .alignSelf(${this.alignSelf})${codeFragment} + } + .height('180vp') + .width('262vp') + .padding($r('sys.float.padding_level3')) + .border({ width: 1, color: $r('sys.color.comp_background_emphasize'), radius: $r('sys.float.corner_radius_level6') }) + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/flex/viewmodel/FlexDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/flex/viewmodel/FlexDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..16cd0a65da6e62a923cc691ce58b061c3139fd80 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/flex/viewmodel/FlexDescriptor.ets @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { + ElementsNums, + elementsNumsMapData, + flexAlignItemMapData, + flexContentMapData, + flexDirectionMapData, + flexWrapMapData, +} from '../entity/FlexAttributeMapping'; + +@Observed +export class FlexDescriptor extends CommonDescriptor { + public elements: ElementsNums = elementsNumsMapData.get('Default')!.value as ElementsNums; + public direction: FlexDirection = flexDirectionMapData.get('Default')!.value as FlexDirection; + public wrap: FlexWrap = flexWrapMapData.get('Default')!.value as FlexWrap; + public justifyContent: FlexAlign = flexContentMapData.get('Default')!.value as FlexAlign; + public alignItems: ItemAlign = flexAlignItemMapData.get('Default')!.value as ItemAlign; + public alignSelf: ItemAlign = flexAlignItemMapData.get('Default')!.value as ItemAlign; + public alignContent: FlexAlign = flexContentMapData.get('Default')!.value as FlexAlign; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'elements': + this.elements = + attribute.currentValue === ElementsNums.FOUR.toString() ? ElementsNums.FOUR : ElementsNums.TWO; + break; + case 'direction': + this.direction = + flexDirectionMapData.get(attribute.currentValue)?.value as FlexDirection ?? FlexDirection.Row; + break; + case 'wrap': + this.wrap = flexWrapMapData.get(attribute.currentValue)?.value as FlexWrap ?? FlexWrap.NoWrap; + break; + case 'justifyContent': + this.justifyContent = + flexContentMapData.get(attribute.currentValue)?.value as FlexAlign ?? FlexAlign.Start; + break; + case 'alignItems': + this.alignItems = + flexAlignItemMapData.get(attribute.currentValue)?.value as ItemAlign ?? ItemAlign.Auto; + break; + case 'alignSelf': + this.alignSelf = flexAlignItemMapData.get(attribute.currentValue)?.value as ItemAlign ?? ItemAlign.Auto; + break; + case 'alignContent': + this.alignContent = + flexContentMapData.get(attribute.currentValue)?.value as FlexAlign ?? FlexAlign.Start; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/grid/component/GridBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/grid/component/GridBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..102bda462aec18885cd648f202e8fb66a58b0689 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/grid/component/GridBuilder.ets @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2024 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 { Popup } from '@kit.ArkUI'; +import { PreferenceManager } from '@ohos/common'; +import { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import { CommonStorageKey } from '../../common/entity/CommonStorageKey'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import { GridAttributeModifier } from '../viewmodel/GridAttributeModifier'; +import type { GridDescriptor } from '../viewmodel/GridDescriptor'; +import { pixelMapBuilder } from './PixelMapBuilder'; +import { getData, ItemData } from '../entity/GridAttributeMapping'; + +@Builder +export function GridBuilder($$: DescriptorWrapper) { + GridComponent({ preView: $$.descriptor as GridDescriptor }); +} + +const DRAG_RATIO: number = 1.1; + +@Component +struct GridComponent { + @State listData: string[] = []; + @State showPopup: boolean = true; + @State itemData: ItemData = new ItemData(); + @Prop preView: GridDescriptor; + + aboutToAppear(): void { + PreferenceManager.getInstance().getValue(CommonStorageKey.KEY_GRID_TIP).then((value) => { + this.showPopup = value ?? true; + }); + this.listData = getData(); + } + + @Builder + MyPopup() { + Row() { + Popup({ + title: { + text: $r('app.string.edit'), + }, + message: { + text: $r('app.string.editTip') + }, + showClose: true, + onClose: () => { + this.showPopup = false; + }, + buttons: [ + { + text: $r('app.string.confirmTip'), + action: () => { + this.showPopup = false; + }, + }, + { + text: $r('app.string.cancelTip'), + action: () => { + PreferenceManager.getInstance().setValue(CommonStorageKey.KEY_GRID_TIP, false); + this.showPopup = false; + }, + }, + ], + }) + } + .onKeyPreIme((keyEvent: KeyEvent) => { + if ((keyEvent?.keyText === 'KEYCODE_DPAD_RIGHT' || keyEvent?.keyText === 'KEYCODE_DPAD_LEFT') && + keyEvent.type === KeyType.Down) { + return true; + } + return false; + }) + } + + build() { + Column() { + Grid((this.preView as GridDescriptor).scroller) { + ForEach(this.listData, (item: string, index: number) => { + GridItem() { + Text(item) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.icon_emphasize')) + .textAlign(TextAlign.Center) + .margin({ right: $r('sys.float.padding_level2') }) + } + .backgroundColor($r('sys.color.comp_background_primary')) + .onAreaChange((_oldValue: Area, newValue: Area) => { + this.itemData.width = (newValue.width as number) * DRAG_RATIO; + this.itemData.height = (newValue.height as number) * DRAG_RATIO; + }) + .borderRadius($r('sys.float.corner_radius_level4')) + .border({ width: $r('app.float.border_width_large'), color: $r('sys.color.comp_background_emphasize') }) + .bindPopup(this.showPopup && index === 0, { + builder: this.MyPopup(), + width: $r('app.float.water_flow_width'), + backgroundBlurStyle: BlurStyle.COMPONENT_ULTRA_THICK, + radius: ($r('sys.float.corner_radius_level4')), + mask: false, + focusable: true, + popupColor: $r('sys.color.ohos_id_blur_style_component_regular_color'), + offset: { y: DetailPageConstant.GRID_POPUP_OFFSET_Y }, + placement: Placement.Bottom + }) + }, (item: string) => item) + } + .onItemDragStart((_event: ItemDragInfo, itemIndex: number) => { + this.itemData.text = this.listData[itemIndex]; + return pixelMapBuilder(this.itemData); + }) + .onItemDrop((_event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean): void => { + if (!isSuccess || insertIndex >= this.listData.length) { + return; + } + const temp: string = this.listData[itemIndex]; + this.listData[itemIndex] = this.listData[insertIndex]; + this.listData[insertIndex] = temp; + }) + .attributeModifier(new GridAttributeModifier(this.preView as GridDescriptor)) + } + .height('100%') + .width('100%') + .padding($r('sys.float.padding_level3')) + .align(Alignment.Center) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/grid/component/PixelMapBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/grid/component/PixelMapBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..271216c511b7e5f094e74ef8722780f73907a07d --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/grid/component/PixelMapBuilder.ets @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 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 type { ItemData } from '../entity/GridAttributeMapping'; + +@Builder +export function pixelMapBuilder($$: ItemData) { + Text($$.text) + .fontSize($r('sys.float.Body_L')) + .size({ width: $$.width, height: $$.height }) + .fontColor($r('sys.color.icon_emphasize')) + .textAlign(TextAlign.Center) + .backgroundColor($r('sys.color.comp_background_primary')) + .shadow(ShadowStyle.OUTER_DEFAULT_SM) + .borderRadius($r('sys.float.corner_radius_level4')) + .border({ width: $r('app.float.border_width_large'), color: $r('sys.color.comp_background_emphasize') }) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/grid/entity/GridAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/grid/entity/GridAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..5a32a1d80f01e0c42b59c68e3e8af13e53f2cd75 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/grid/entity/GridAttributeMapping.ets @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024 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 { CommonBoolMapping, CommonNumberMapping, CommonStringMapping } from '../../common/entity/CommonMapData'; + +const DATA_SIZE = 30; + +@Observed +export class ItemData { + public text: ResourceStr; + public width: Resource | number; + public height: Resource | number; + + constructor(text: ResourceStr = '', width: Resource | number = 0, height: Resource | number = 0) { + this.text = text; + this.width = width; + this.height = height; + } +} + +export function getData() { + const numbers: string[] = []; + for (let i = 1; i <= DATA_SIZE; i++) { + numbers.push(`item${i}`); + } + return numbers; +} + +class ScrollMapping { + public readonly code: string; + public readonly value: Scroller; + + constructor(code: string, value: Scroller) { + this.code = code; + this.value = value; + } +} + +export const scrollerMapData: Map = new Map([ + ['default', new ScrollMapping('new Scroller()', new Scroller())], +]); + +export const columnsGapMapData: Map = new Map([ + ['default', new CommonNumberMapping('10', 10)], +]); + +export const rowsGapMapData: Map = new Map([ + ['default', new CommonNumberMapping('10', 10)], +]); + +export const columnsTemplateMapData: Map = new Map([ + ['default', new CommonStringMapping('1fr 1fr 1fr', '1fr 1fr 1fr')], +]); + +export const rowsTemplateMapData: Map = new Map([ + ['default', new CommonStringMapping('1fr 1fr 1fr', '1fr 1fr 1fr')], +]); + +export const operationModeMapData: Map = new Map([ + ['default', new CommonBoolMapping('true', true)], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/grid/viewmodel/GridAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/grid/viewmodel/GridAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..3393410f7d5482c19538fcca7d8703378548af4a --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/grid/viewmodel/GridAttributeModifier.ets @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 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 { CommonAttributeModifier } from '../../../viewmodel/CommonAttributeModifier'; +import type { GridDescriptor } from './GridDescriptor'; + +@Observed +export class GridAttributeModifier extends CommonAttributeModifier { + public applyNormalAttribute(instance: GridAttribute): void { + this.assignAttribute((descriptor => descriptor.columnsGap), (val) => instance.columnsGap(val)); + this.assignAttribute((descriptor => descriptor.rowsGap), (val) => instance.rowsGap(val)); + this.assignAttribute((descriptor => descriptor.columnsTemplate), (val) => instance.columnsTemplate(val)); + this.assignAttribute((descriptor => descriptor.rowsTemplate), (val) => instance.rowsTemplate(val)); + this.assignAttribute((descriptor => descriptor.operationMode), (val) => instance.editMode(val)); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/grid/viewmodel/GridCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/grid/viewmodel/GridCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..796a07f15d468215ba25f720fe37ad3a4cb656fc --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/grid/viewmodel/GridCodeGenerator.ets @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2024 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 { StringUtil } from '../../../util/StringUtil'; +import type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { + columnsGapMapData, + columnsTemplateMapData, + operationModeMapData, + rowsGapMapData, + rowsTemplateMapData, + scrollerMapData, +} from '../entity/GridAttributeMapping'; + +export class GridCodeGenerator implements CommonCodeGenerator { + private scroller: string = scrollerMapData.get('default')!.code; + private columnsGap: string = columnsGapMapData.get('default')!.code; + private rowsGap: string = rowsGapMapData.get('default')!.code; + private columnsTemplate: string = columnsTemplateMapData.get('default')!.code; + private rowsTemplate: string = rowsTemplateMapData.get('default')!.code; + private operationMode: string = operationModeMapData.get('default')!.code; + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'columnsGap': + this.columnsGap = attribute.currentValue; + break; + case 'rowsGap': + this.rowsGap = attribute.currentValue; + break; + case 'columnsNum': + this.columnsTemplate = StringUtil.getTemplateString(Number(attribute.currentValue)); + break; + case 'rowsNum': + this.rowsTemplate = StringUtil.getTemplateString(Number(attribute.currentValue)); + break; + case 'operationMode': + this.operationMode = JSON.parse(attribute.currentValue); + break; + default: + break; + } + }); + + return ` +@Observed +export class ItemData { + public text: ResourceStr; + public width: Resource | number; + public height: Resource | number; + + constructor(text: ResourceStr = '', width: Resource | number = 0, height: Resource | number = 0) { + this.text = text; + this.width = width; + this.height = height; + } +} + +@Builder +export function pixelMapBuilder($$: ItemData) { + Text($$.text) + .fontSize($r('sys.float.Body_L')) + .size({ width: $$.width, height: $$.height }) + .fontColor($r('sys.color.icon_emphasize')) + .textAlign(TextAlign.Center) + .backgroundColor($r('sys.color.comp_background_primary')) + .shadow(ShadowStyle.OUTER_DEFAULT_SM) + .borderRadius($r('sys.float.corner_radius_level4')) + .border({ width: 1.5, color: $r('sys.color.comp_background_emphasize') }) +} + +@Component +struct GridComponent { + @State listData: string[] = []; + @State itemData: ItemData = new ItemData(); + + aboutToAppear(): void { + for (let i = 1; i <= 30; i++) { + this.listData.push('item' + i); + } + } + + build() { + Column() { + Grid(${this.scroller}) { + ForEach(this.listData, (item: string) => { + GridItem() { + Text(item) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.icon_emphasize')) + .textAlign(TextAlign.Center) + .margin({ right: $r('sys.float.padding_level2') }) + } + .backgroundColor($r('sys.color.comp_background_primary')) + .onAreaChange((oldValue: Area, newValue: Area) => { + this.itemData.width = (newValue.width as number) * 1.1; + this.itemData.height = (newValue.height as number) * 1.1; + }) + .borderRadius($r('sys.float.corner_radius_level4')) + .border({ width: 1.5, color: $r('sys.color.comp_background_emphasize') }) + }, (item: string) => item) + } + .editMode(${this.operationMode}) + .onItemDragStart((_event: ItemDragInfo, itemIndex: number) => { + this.itemData.text = this.listData[itemIndex]; + return pixelMapBuilder(this.itemData); + }) + .onItemDrop((_event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean): void => { + if (!isSuccess || insertIndex >= this.listData.length) { + return; + } + const temp: string = this.listData[itemIndex]; + this.listData[itemIndex] = this.listData[insertIndex]; + this.listData[insertIndex] = temp; + }) + .columnsGap(${this.columnsGap}) + .rowsGap(${this.rowsGap}) + .rowsTemplate('${this.rowsTemplate}') + .columnsTemplate('${this.columnsTemplate}') + } + .height('100%') + .width('100%') + .padding($r('sys.float.padding_level3')) + .align(Alignment.Center) + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/grid/viewmodel/GridDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/grid/viewmodel/GridDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..7f8f3937ac3e440415eb1d22f231f89961cd1e60 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/grid/viewmodel/GridDescriptor.ets @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 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 { StringUtil } from '../../../util/StringUtil'; +import type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { + columnsGapMapData, + columnsTemplateMapData, + operationModeMapData, + rowsGapMapData, + rowsTemplateMapData, + scrollerMapData, +} from '../entity/GridAttributeMapping'; + +@Observed +export class GridDescriptor extends CommonDescriptor { + public scroller: Scroller = scrollerMapData.get('default')!.value; + public columnsGap: number = columnsGapMapData.get('default')!.value; + public rowsGap: number = rowsGapMapData.get('default')!.value; + public columnsTemplate: string = columnsTemplateMapData.get('default')!.value; + public rowsTemplate: string = rowsTemplateMapData.get('default')!.value; + public operationMode: boolean = operationModeMapData.get('default')!.value; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'columnsGap': + this.columnsGap = Number(attribute.currentValue); + break; + case 'rowsGap': + this.rowsGap = Number(attribute.currentValue); + break; + case 'columnsNum': + this.columnsTemplate = StringUtil.getTemplateString(Number(attribute.currentValue)); + break; + case 'rowsNum': + this.rowsTemplate = StringUtil.getTemplateString(Number(attribute.currentValue)); + break; + case 'operationMode': + this.operationMode = JSON.parse(attribute.currentValue); + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/imageaianalyzer/component/AIImageBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/imageaianalyzer/component/AIImageBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..27f45c997e1d214d52f6b37bd823b8fec8cd7f7a --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/imageaianalyzer/component/AIImageBuilder.ets @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 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 type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import { AIImageComponent } from './AIImageComponent'; + +@Builder +export function AIImageBuilder($$: DescriptorWrapper) { + AIImageComponent() +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/imageaianalyzer/component/AIImageComponent.ets b/features/componentlibrary/src/main/ets/componentdetailview/imageaianalyzer/component/AIImageComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..56273c46680c4e74f0d559b018bbb9b5fc5784b6 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/imageaianalyzer/component/AIImageComponent.ets @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2024 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 { Popup } from '@kit.ArkUI'; +import { image } from '@kit.ImageKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { BreakpointTypeEnum, GlobalInfoModel, Logger, PreferenceManager } from '@ohos/common'; +import { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import { CommonStorageKey } from '../../common/entity/CommonStorageKey'; + +const TAG: string = '[AIImageComponent]'; + +@Component +export struct AIImageComponent { + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @State imagePixelMap?: image.PixelMap = undefined; + @State showPopup: boolean = true; + @State imageWidth: number = 0; + @State imageHeight: number = 0; + + async aboutToAppear() { + PreferenceManager.getInstance().getValue(CommonStorageKey.KEY_MATTING_TIP).then((value) => { + this.showPopup = value ?? true; + }); + this.imagePixelMap = await this.getPixmapFromMedia($r('app.media.image_ai')); + } + + aboutToDisappear(): void { + this.imagePixelMap?.release(); + } + + @Builder + MyPopup() { + Row() { + Popup({ + title: { + text: $r('app.string.aiMatting'), + }, + message: { + text: $r('app.string.aiMatting_tip') + }, + showClose: true, + onClose: () => { + this.showPopup = false; + }, + buttons: [ + { + text: $r('app.string.confirmTip'), + action: () => { + this.showPopup = false; + }, + }, + { + text: $r('app.string.cancelTip'), + action: () => { + PreferenceManager.getInstance().setValue(CommonStorageKey.KEY_MATTING_TIP, false); + this.showPopup = false; + }, + } + ], + }) + } + .onKeyPreIme((keyEvent: KeyEvent) => { + if ((keyEvent?.keyText === 'KEYCODE_DPAD_RIGHT' || keyEvent?.keyText === 'KEYCODE_DPAD_LEFT') && + keyEvent.type === KeyType.Down) { + return true; + } + return false; + }) + } + + build() { + Stack({ alignContent: Alignment.Center }) { + Image(this.imagePixelMap ?? $r('app.media.image_ai')) + .enableAnalyzer(true) + .width(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? '100%' : '90%') + .height(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? '100%' : this.imageHeight) + .objectFit(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? ImageFit.Fill : ImageFit.Contain) + .borderRadius($r('sys.float.corner_radius_level8')) + .draggable(false) + .onAreaChange((_oldValue: Area, newValue: Area) => { + this.imageWidth = newValue.width as number; + this.imageHeight = this.imageWidth * 0.56 as number; + }) + .bindPopup(this.showPopup, { + builder: this.MyPopup(), + width: $r('app.float.popup_width_large'), + backgroundBlurStyle: BlurStyle.COMPONENT_ULTRA_THICK, + radius: ($r('sys.float.corner_radius_level4')), + offset: { y: DetailPageConstant.IMAGE_POPUP_OFFSET_Y }, + placement: Placement.Bottom, + showInSubWindow: true, + onStateChange: (event) => { + if (!event.isVisible) { + this.showPopup = false; + } + }, + focusable: true, + }) + } + .width('100%') + .height('100%') + } + + private async getPixmapFromMedia(resource: Resource) { + let createPixelMap: image.PixelMap; + try { + const unit8Array = await getContext(this)?.resourceManager?.getMediaContent({ + bundleName: resource.bundleName, + moduleName: resource.moduleName, + id: resource.id, + }); + const imageSource = image.createImageSource(unit8Array.buffer.slice(0, unit8Array.buffer.byteLength)); + createPixelMap = await imageSource.createPixelMap({ + desiredPixelFormat: image.PixelMapFormat.RGBA_8888, + }); + await imageSource.release(); + this.imagePixelMap?.release(); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Get pixmapFromMedia error, the code is ${error.code}, the message is ${error.message}`); + return; + } + return createPixelMap; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/imageaianalyzer/viewmodel/AIImageCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/imageaianalyzer/viewmodel/AIImageCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..b95dfeb94dc966030d8ef3f5e4026bf496c809fa --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/imageaianalyzer/viewmodel/AIImageCodeGenerator.ets @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; + +export class AIImageCodeGenerator implements CommonCodeGenerator { + public generate(_attributes: OriginAttribute[]): string { + return `import { image } from '@kit.ImageKit'; + +@Component +export struct AIImageComponent { + @State imagePixelMap?: image.PixelMap = undefined; + + async aboutToAppear() { + // 图片资源替换项目src/main/resources/base/media路径下资源 + this.imagePixelMap = await this.getPixmapFromMedia($r("app.media.image_ai")); + } + + aboutToDisappear(): void { + this.imagePixelMap?.release(); + } + + build() { + Column() { + Stack({ alignContent: Alignment.Center }) { + // 图片资源替换项目src/main/resources/base/media路径下资源 + Image(this.imagePixelMap ?? $r("app.media.image_ai")) + .enableAnalyzer(true) + .width('100%') + .height('100%') + .objectFit(ImageFit.Contain) + .borderRadius($r('sys.float.corner_radius_level8')) + .draggable(false) + } + .width('80%') + } + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .width('100%') + .height('100%') + } + + private async getPixmapFromMedia(resource: Resource) { + let createPixelMap: image.PixelMap; + try { + const unit8Array = await getContext(this)?.resourceManager?.getMediaContent({ + bundleName: resource.bundleName, + moduleName: resource.moduleName, + id: resource.id, + }); + const imageSource = image.createImageSource(unit8Array.buffer.slice(0, unit8Array.buffer.byteLength)); + createPixelMap = await imageSource.createPixelMap({ + desiredPixelFormat: image.PixelMapFormat.RGBA_8888, + }); + await imageSource.release(); + this.imagePixelMap?.release(); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`Get pixmapFromMedia error, the code is \${error.code}, the message is \${error.message}\`); + return; + } + return createPixelMap; + } +}` + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/list/component/ItemHead.ets b/features/componentlibrary/src/main/ets/componentdetailview/list/component/ItemHead.ets new file mode 100644 index 0000000000000000000000000000000000000000..7496ba917234f1640a02be7eac1faf0e84a2cda6 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/list/component/ItemHead.ets @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 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 { CommonConstants } from '@ohos/common'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; + +@Builder +export function itemHead(text: string, $$: DescriptorWrapper) { + Row() { + Text(text) + .textAlign(TextAlign.Center) + .fontSize($r('sys.float.Body_L')) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .fontColor($r('sys.color.font_on_primary')) + .width('100%') + .height($r('app.float.text_height_large')) + .margin({ bottom: $r('sys.float.padding_level4') }) + } + .margin({ left: -CommonConstants.SPACE_8, right: -CommonConstants.SPACE_8 }) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/list/component/ListBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/list/component/ListBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..023cd3e82b225aee880f3dbf22b325c2b55902dc --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/list/component/ListBuilder.ets @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024 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 { CommonConstants } from '@ohos/common'; +import { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import { timeTable, TimeTable } from '../entity/ListAttributeMapping'; +import { ListAttributeModifier } from '../viewmodel/ListAttributeModifier'; +import type { ListDescriptor } from '../viewmodel/ListDescriptor'; +import { itemHead } from './ItemHead'; + +@Builder +export function ListBuilder($$: DescriptorWrapper) { + Column() { + List({ space: DetailPageConstant.SPACE_NORMAL }) { + ForEach(timeTable, (item: TimeTable) => { + ListItemGroup({ header: itemHead(item.title, $$), space: CommonConstants.SPACE_8 }) { + ForEach(item.projects, (project: string) => { + listItemBuilder(project, $$); + }, (item: string) => item) + } + .height('100%') + .width('100%') + .padding({ + left: $r('sys.float.padding_level4'), + right: $r('sys.float.padding_level4'), + bottom: $r('sys.float.padding_level2'), + }) + }, (item: TimeTable, _index: number) => item.title.toString()) + } + .sticky(($$.descriptor as ListDescriptor).sticky ? StickyStyle.Header : StickyStyle.None) + .scrollBar(BarState.Off) + .attributeModifier(new ListAttributeModifier($$.descriptor as ListDescriptor)) + .width('100%') + .height('100%') + } + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Center) + .width('100%') + .height('100%') + .clip(true) + .borderRadius($r('sys.float.corner_radius_level8')) +} + +@Builder +function listItemBuilder(param: string, $$: DescriptorWrapper) { + ListItem() { + Column() { + Text(param) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_emphasize')) + .textAlign(TextAlign.Center) + } + .width('100%') + .height('100%') + .borderRadius($r('sys.float.corner_radius_level4')) + .justifyContent(FlexAlign.Center) + .border({ width: $r('app.float.border_width_large'), color: $r('sys.color.comp_background_emphasize') }) + } + .width('100%') + .height(($$.descriptor as ListDescriptor).lanes.value >= DetailPageConstant.LIST_LANES_THRESHOLD ? + $r('app.float.component_item_height') : $r('app.float.component_item_height_max')) + .aspectRatio(($$.descriptor as ListDescriptor).lanes.value >= DetailPageConstant.LIST_LANES_THRESHOLD ? + DetailPageConstant.ASPECT_RATIO_SQUARE : DetailPageConstant.ASPECT_RATIO_INVALID) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/list/entity/ListAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/list/entity/ListAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..32b6d5e78b8867fcc37e0a1e73d559a1ba205589 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/list/entity/ListAttributeMapping.ets @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024 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. + */ + +class ListDirectionType { + public readonly code: string; + public readonly value: Axis; + + constructor(code: string, value: Axis) { + this.code = code; + this.value = value; + } +} + +export const listDirectionMapData: Map = new Map([ + ['Vertical', new ListDirectionType('Axis.Vertical', Axis.Vertical)], + ['Horizontal', new ListDirectionType('Axis.Horizontal', Axis.Horizontal)], + ['Default', new ListDirectionType('Axis.Vertical', Axis.Vertical)], +]); + +class EdgeEffectMap { + public readonly code: string; + public readonly value: EdgeEffect; + + constructor(code: string, value: EdgeEffect) { + this.code = code; + this.value = value; + } +} + +export const edgeEffectMapData: Map = new Map([ + ['Spring', new EdgeEffectMap('EdgeEffect.Spring', EdgeEffect.Spring)], + ['Fade', new EdgeEffectMap('EdgeEffect.Fade', EdgeEffect.Fade)], + ['None', new EdgeEffectMap('EdgeEffect.None', EdgeEffect.None)], + ['Default', new EdgeEffectMap('EdgeEffect.Spring', EdgeEffect.Spring)], +]); + +export interface TimeTable { + title: string; + projects: string[]; +} + +export interface LanesStyle { + value: number; + gutter: Dimension; +} + +export const timeTable: TimeTable[] = [ + { + title: 'ONE', + projects: ['item 1', 'item 2', 'item 3', 'item 4'], + }, + { + title: 'TWO', + projects: ['item 5', 'item 6', 'item 7', 'item 8'], + }, + { + title: 'THREE', + projects: ['item 9', 'item 10', 'item 11', 'item 12'], + }, + { + title: 'FOUR', + projects: ['item 13', 'item 14', 'item 15', 'item 16'], + }, +]; diff --git a/features/componentlibrary/src/main/ets/componentdetailview/list/viewmodel/ListAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/list/viewmodel/ListAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..60b46b20aa5613a0c14c8b76d60b618f0591c7ff --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/list/viewmodel/ListAttributeModifier.ets @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 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 { CommonAttributeModifier } from '../../../viewmodel/CommonAttributeModifier'; +import type { ListDescriptor } from './ListDescriptor'; + +@Observed +export class ListAttributeModifier extends CommonAttributeModifier { + public applyNormalAttribute(instance: ListAttribute): void { + this.assignAttribute((descriptor => descriptor.listDirection), (val) => instance.listDirection(val)); + this.assignAttribute((descriptor => descriptor.lanes), (val) => instance.lanes(val?.value, val?.gutter)); + this.assignAttribute((descriptor => descriptor.edgeEffect), (val) => instance.edgeEffect(val)); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/list/viewmodel/ListCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/list/viewmodel/ListCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..ab4d0676e4b43694d5b5604b58fed2ac26d9c932 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/list/viewmodel/ListCodeGenerator.ets @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2024 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 { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { edgeEffectMapData, LanesStyle, listDirectionMapData } from '../entity/ListAttributeMapping'; + +export class ListCodeGenerator implements CommonCodeGenerator { + private listDirection: string = listDirectionMapData.get('Default')!.code; + private lanes: LanesStyle = { + value: 1, + gutter: 0, + }; + private edgeEffect: string = edgeEffectMapData.get('Default')!.code; + private sticky: boolean = true; + private stickyName: string = 'StickyStyle.Header'; + + public generate(attributes: OriginAttribute[]): string { + let height: string = ''; + let aspectRatio: number = DetailPageConstant.ASPECT_RATIO_SQUARE; + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'listDirection': + this.listDirection = listDirectionMapData.get(attribute.currentValue)?.code ?? this.listDirection; + break; + case 'lanesNum': + this.lanes = { + value: Number(attribute.currentValue), + gutter: this.lanes.gutter + }; + if (this.lanes.value >= DetailPageConstant.LIST_LANES_THRESHOLD) { + height = '64vp'; + aspectRatio = DetailPageConstant.ASPECT_RATIO_SQUARE; + } else { + height = '92vp'; + aspectRatio = DetailPageConstant.ASPECT_RATIO_INVALID; + } + break; + case 'gutter': + this.lanes = { + value: this.lanes.value, + gutter: Number(attribute.currentValue ?? 0) + }; + break; + case 'edgeEffect': + this.edgeEffect = edgeEffectMapData.get(attribute.currentValue)?.code ?? this.edgeEffect; + break; + case 'sticky': + this.sticky = JSON.parse(attribute.currentValue); + this.sticky && (this.stickyName = 'StickyStyle.Header'); + !this.sticky && (this.stickyName = 'StickyStyle.None'); + break; + default: + break; + } + }); + const codeOne: string = ` + [ + { + title: 'ONE', + projects: ['item 1', 'item 2', 'item 3', 'item 4'] + }, + { + title: 'TWO', + projects: ['item 5', 'item 6', 'item 7', 'item 8'] + }, + { + title: 'THREE', + projects: ['item 9', 'item 10', 'item 11', 'item 12'] + }, + { + title: 'FOUR', + projects: ['item 13', 'item 14', 'item 15', 'item 16'] + } + ]`; + return `interface TimeTable { + title: string; + projects: string[]; +} + +@Component +struct ListComponent { + @State listData: TimeTable[] = ${codeOne}; + + @Builder + itemHead(text: string) { + Text(text) + .textAlign(TextAlign.Center) + .fontSize($r('sys.float.Body_L')) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .fontColor($r('sys.color.font_on_primary')) + .width('100%') + .height(32) + .margin({ bottom: $r('sys.float.padding_level4') }) + } + + build() { + Column() { + List({ space: 8 }) { + ForEach(this.listData, (item: TimeTable) => { + ListItemGroup({ header: this.itemHead(item.title) }) { + ForEach(item.projects, (project: string) => { + ListItem() { + Column() { + Text(project) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_emphasize')) + .textAlign(TextAlign.Center) + } + .width('100%') + .height('100%') + .borderRadius($r('sys.float.corner_radius_level4')) + .border({ width: 1.5, color: $r('sys.color.comp_background_emphasize') }) + .justifyContent(FlexAlign.Center) + } + .width('100%') + .height('${height}') + .aspectRatio(${aspectRatio}) + }, (item: string) => item) + } + }, (item: TimeTable, _index: number) => item.title.toString()) + } + .width('100%') + .height('100%') + .sticky(${this.stickyName}) + .scrollBar(BarState.Off) + .lanes(${this.lanes.value}, ${this.lanes.gutter}) + .listDirection(${this.listDirection}) + .edgeEffect(${this.edgeEffect}) + } + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Center) + .width('100%') + .height('100%') + .clip(true) + .borderRadius($r('sys.float.corner_radius_level8')) + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/list/viewmodel/ListDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/list/viewmodel/ListDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..b0b1863a800d2f83d9f929f3aec1192d23fcf799 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/list/viewmodel/ListDescriptor.ets @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { edgeEffectMapData, LanesStyle, listDirectionMapData } from '../entity/ListAttributeMapping'; + +@Observed +export class ListDescriptor extends CommonDescriptor { + public listDirection: Axis = listDirectionMapData.get('Default')!.value; + public lanes: LanesStyle = { + value: 1, + gutter: 0, + }; + public edgeEffect: EdgeEffect = edgeEffectMapData.get('Default')!.value; + public sticky: boolean = true; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'listDirection': + this.listDirection = + listDirectionMapData.get(attribute.currentValue)?.value ?? listDirectionMapData.get('Default')!.value; + break; + case 'lanesNum': + this.lanes = { + value: Number(attribute.currentValue), + gutter: this.lanes.gutter, + }; + break; + case 'gutter': + this.lanes = { + value: this.lanes.value, + gutter: Number(attribute.currentValue), + }; + break; + case 'edgeEffect': + this.edgeEffect = + edgeEffectMapData.get(attribute.currentValue)?.value ?? edgeEffectMapData.get('Default')!.value; + break; + case 'sticky': + this.sticky = JSON.parse(attribute.currentValue); + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/penkit/component/HandWritingComponent.ets b/features/componentlibrary/src/main/ets/componentdetailview/penkit/component/HandWritingComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..6f4b4b8ed2f54cee3f540fd91359d977db32c4db --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/penkit/component/HandWritingComponent.ets @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024 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 { HandwriteComponent, HandwriteController } from '@kit.Penkit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { BreakpointTypeEnum, GlobalInfoModel, Logger } from '@ohos/common'; +import { ComponentDetailManager } from '../../../viewmodel/ComponentDetailManager'; +import { DetailPageConstant } from '../../../constant/DetailPageConstant'; + +const TAG: string = '[HandWritingComponent]'; +const BUTTON_OFFSET: number = 16; + +@Component +export struct HandWritingComponent { + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + private controller: HandwriteController = new HandwriteController(); + private initPath: string = 'savePath'; + private closeButtonTop: number = BUTTON_OFFSET; + + aboutToAppear(): void { + // Close button offset. + if (this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG || + this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL) { + this.closeButtonTop = DetailPageConstant.PEN_CLOSE_TOP; + } else { + this.closeButtonTop = BUTTON_OFFSET; + } + } + + aboutToDisappear() { + // Invoke the saving interface when the HandWriteDemo exits. + try { + this.controller?.save(this.initPath); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `HandwriteController save error, the code is ${error.code}, the message is ${error.message}`); + } + } + + build() { + NavDestination() { + Stack({ alignContent: Alignment.TopEnd }) { + HandwriteComponent({ + handwriteController: this.controller, + onInit: () => { + try { + this.controller?.load(this.initPath); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, + `HandwriteController load error, the code is ${error.code}, the message is ${error.message}`); + } + } + }) + Button({ type: ButtonType.Circle }) { + SymbolGlyph($r('sys.symbol.xmark')) + .fontColor([$r('sys.color.icon_primary')]) + .fontSize($r('sys.float.ohos_id_textfield_icon_size')) + } + .width($r('sys.float.ohos_id_button_height')) + .height($r('sys.float.ohos_id_button_height')) + .backgroundColor($r('sys.color.comp_background_tertiary')) + .margin({ top: this.closeButtonTop + this.globalInfoModel.statusBarHeight, right: $r('sys.float.padding_level8') }) + .onClick(() => { + ComponentDetailManager.getInstance().getDetailViewModel('Penkit')?.pop(); + }) + } + .width('100%') + .height('100%') + } + .hideTitleBar(true) + .height('100%') + .width('100%') + } +} + +@Builder +export function PenKitViewBuilder() { + HandWritingComponent() +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/penkit/component/PenKitBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/penkit/component/PenKitBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..70c5cafcea091a7e0a1f88f6cae9d988fe516235 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/penkit/component/PenKitBuilder.ets @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 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'; +import { Logger } from '@ohos/common'; +import { ComponentDetailManager } from '../../../viewmodel/ComponentDetailManager'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; + +const TAG: string = '[PenKitBuilder]'; + +@Builder +export function PenKitBuilder($$: DescriptorWrapper) { + Button($r('app.string.penKit'), { buttonStyle: ButtonStyleMode.NORMAL }) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_emphasize')) + .onClick(() => { + if (canIUse('SystemCapability.Stylus.Handwrite')) { + ComponentDetailManager.getInstance().getDetailViewModel('Penkit')?.jumpToPenKitView(); + } else { + try { + promptAction.showToast({ message: $r('app.string.function_handwrite_not_support') }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Show toast error, the code is ${error.code}}, the message is ${error.message}`); + } + } + }) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/penkit/viewmodel/PenKitCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/penkit/viewmodel/PenKitCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..c49c446503446932d7c0b1c52ff2fe9aa4f2e129 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/penkit/viewmodel/PenKitCodeGenerator.ets @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; + +export class PenKitCodeGenerator implements CommonCodeGenerator { + public generate(_attributes: OriginAttribute[]): string { + return `// In the src/main/ets/util path, create a ContextConfig.ts file. +import { common } from '@kit.AbilityKit'; + +declare namespace globalThis { + let _brushEngineContext: common.UIAbilityContext; +}; + +export default class GlobalUIAbilityContext { + static getContext(): common.UIAbilityContext { + return globalThis._brushEngineContext; + } + + static setContext(context: common.UIAbilityContext): void { + globalThis._brushEngineContext = context; + } +} + +// In the src/main/ets/entryability/EntryAbility.ets file, import GlobalUIAbilityContext. +import GlobalUIAbilityContext from '../util/ContextConfig'; + +export default class EntryAbility extends UIAbility { + onWindowStageCreate(windowStage: window.WindowStage): void { + GlobalUIAbilityContext.setContext(this.context); + // Other code + // ... + } +} + +// Create a HandWritingComponent.ets file in the src/main/ets/ path to be used as a component. +import { HandwriteComponent, HandwriteController } from '@kit.Penkit'; + +@Component +struct HandWritingComponent { + private controller: HandwriteController = new HandwriteController(); + private initPath: string = 'savePath'; + + aboutToAppear() { + if (canIUse('SystemCapability.Stylus.Handwrite')) { + console.log('This device supports SystemCapability.Stylus.Handwrite'); + } else { + console.log('This device does not support SystemCapability.Stylus.Handwrite'); + } + } + + aboutToDisappear() { + try { + this.controller?.save(this.initPath); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`HandwriteController save error, the code is \${error.code}, the message is \${error.message}\`); + } + } + + build() { + Row() { + Column() { + HandwriteComponent({ + handwriteController: this.controller, + onInit: () => { + try { + this.controller?.load(this.initPath); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`HandwriteController load error, the code is \${error.code}, the message is \${error.message}\`); + } + } + }) + } + .width('100%') + } + .height('100%') + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/photopicker/component/PhotoViewPickerBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/photopicker/component/PhotoViewPickerBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..6ae2797598a878102675aaf8b3de580289a0d3d5 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/photopicker/component/PhotoViewPickerBuilder.ets @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apach 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 type { BusinessError } from '@kit.BasicServicesKit'; +import { photoAccessHelper } from '@kit.MediaLibraryKit'; +import { Logger } from '@ohos/common'; +import { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; + +const TAG = '[PhotoViewPickerBuilder]'; + +@Builder +export function PhotoViewPickerBuilder(_$$: DescriptorWrapper) { + PhotoViewPickerComponent() +} + +@Component +struct PhotoViewPickerComponent { + @State imageUri: string = ''; + @State imgUris: string[] = []; + @State selectedImageUri: ResourceStr = ''; + @State backBlur: boolean = false; + + build() { + Stack({ alignContent: Alignment.Center }) { + Image(this.selectedImageUri) + .objectFit(ImageFit.Contain) + .borderRadius($r('sys.float.corner_radius_level5')) + .width('100%') + .height('100%') + + Button($r('app.string.photoViewPicker_text')) + .buttonStyle(ButtonStyleMode.NORMAL) + .backgroundBlurStyle(this.backBlur ? BlurStyle.BACKGROUND_THICK : BlurStyle.NONE, + { + colorMode: ThemeColorMode.SYSTEM, + adaptiveColor: AdaptiveColor.DEFAULT, + scale: DetailPageConstant.SCALE_LEVEL1, + }) + .fontColor(this.backBlur ? $r('sys.color.font_on_primary') : $r('sys.color.font_emphasize')) + .height($r('app.float.button_height_normal')) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .onClick(() => { + try { + const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions(); + photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; + photoSelectOptions.maxSelectNumber = 1; + const photoPicker = new photoAccessHelper.PhotoViewPicker(); + photoPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => { + this.imgUris = photoSelectResult.photoUris; + this.imageUri = this.imgUris[0]; + this.selectedImageUri = this.imgUris[0]; + if (this.selectedImageUri.length > 0) { + this.backBlur = true; + } + Logger.info(TAG, `PhotoViewPicker.select successfully}`); + }).catch((err: BusinessError) => { + Logger.error(TAG, `PhotoViewPicker.select failed with err: ${err.code}, ${err.message}`); + }); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, `PhotoViewPicker failed with err: ${err.code}, ${err.message}`); + } + }) + } + .width('100%') + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/photopicker/viewmodel/PhotoViewPickerCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/photopicker/viewmodel/PhotoViewPickerCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..0dc96b6a46375f69ef5d7384c783fff946c31a5a --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/photopicker/viewmodel/PhotoViewPickerCodeGenerator.ets @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; + +export class PhotoViewPickerCodeGenerator implements CommonCodeGenerator { + public generate(_attributes: OriginAttribute[]): string { + return `import type { BusinessError } from '@kit.BasicServicesKit'; +import { photoAccessHelper } from '@kit.MediaLibraryKit'; + +@Component +struct PhotoViewPickerComponent { + @State imageUri: string = ''; + @State imgDatas: string[] = []; + @State selectedImage: ResourceStr = ''; + @State backBlur: boolean = false; + + build() { + Stack({ alignContent: Alignment.Bottom }) { + Image(this.selectedImage) + .objectFit(ImageFit.Contain) + .borderRadius($r('sys.float.corner_radius_level5')) + .width('100%') + .height('100%') + + Button('选择图片') + .buttonStyle(ButtonStyleMode.NORMAL) + .backgroundBlurStyle(this.backBlur ? BlurStyle.BACKGROUND_THICK : BlurStyle.NONE, + { + colorMode: ThemeColorMode.SYSTEM, + adaptiveColor: AdaptiveColor.DEFAULT, + scale: 1.0 + }) + .fontColor(this.backBlur ? $r('sys.color.font_on_primary') : $r('sys.color.font_emphasize')) + .height(40) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .margin({ bottom: 36 }) + .onClick(() => { + try { + const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions(); + photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; + photoSelectOptions.maxSelectNumber = 1; + const photoPicker = new photoAccessHelper.PhotoViewPicker(); + photoPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => { + this.imgDatas = photoSelectResult.photoUris; + this.imageUri = this.imgDatas[0]; + this.selectedImage = this.imgDatas[0]; + if (this.selectedImage.length > 0) { + this.backBlur = true; + } + console.info(\`PhotoViewPicker.select successfully, PhotoSelectResult uri: \${photoSelectResult.photoUris[0]}\`); + + }).catch((err: BusinessError) => { + console.info(\`PhotoViewPicker.select failed with err: \${err.code}, \${err.message}\`); + }); + } catch (error) { + const err: BusinessError = error as BusinessError; + console.error(\`PhotoViewPicker failed with err: \${err.code}, \${err.message}\`); + } + }) + } + .width('100%') + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/picker/calendarPicker/component/CalendarPickerBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/picker/calendarPicker/component/CalendarPickerBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..25595de11bd1b2a3a67c5a5ff49941614a107752 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/picker/calendarPicker/component/CalendarPickerBuilder.ets @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 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 { DetailPageConstant } from '../../../../constant/DetailPageConstant'; +import type { DescriptorWrapper } from '../../../../viewmodel/DescriptorWrapper'; +import { CalendarPickerAttributeModifier } from '../viewmodel/CalendarPickerAttributeModifier'; +import type { CalendarPickerDescriptor } from '../viewmodel/CalendarPickerDescriptor'; + +@Builder +export function CalendarPickerBuilder($$: DescriptorWrapper) { + Column() { + CalendarPicker({ hintRadius: DetailPageConstant.DATE_HINT_RADIUS }) + .width('30%') + .attributeModifier(new CalendarPickerAttributeModifier($$.descriptor as CalendarPickerDescriptor)) + } + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .width('100%') +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/picker/calendarPicker/entity/CalendarPickerAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/picker/calendarPicker/entity/CalendarPickerAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..edc866eab22856f0a550a2bb45a9e23701d7788d --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/picker/calendarPicker/entity/CalendarPickerAttributeMapping.ets @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 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 { DetailPageConstant } from '../../../../constant/DetailPageConstant'; + +class CalendarAlignMapping { + public readonly code: string; + public readonly value: CalendarAlign; + + constructor(code: string, value: CalendarAlign) { + this.code = code; + this.value = value; + } +} + +export const calendarAlignTypeMapData: Map = new Map([ + ['End', new CalendarAlignMapping('CalendarAlign.END', CalendarAlign.END)], + ['Start', new CalendarAlignMapping('CalendarAlign.START', CalendarAlign.START)], + ['Center', new CalendarAlignMapping('CalendarAlign.CENTER', CalendarAlign.CENTER)], + ['Default', new CalendarAlignMapping('CalendarAlign.END', CalendarAlign.END)], +]); + +export interface EdgeAlign { + alignType: CalendarAlign; + offset?: Offset; +} + +export const edgeAlignDefault: EdgeAlign = { alignType: CalendarAlign.END, offset: { dx: 0, dy: 0 } }; + +export const textStyleDefault: PickerTextStyle = { + color: $r('app.color.calender_default_text_color'), + font: { size: DetailPageConstant.CALENDAR_DEFAULT_FONT_SIZE, weight: FontWeight.Normal }, +}; + diff --git a/features/componentlibrary/src/main/ets/componentdetailview/picker/calendarPicker/viewmodel/CalendarPickerAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/picker/calendarPicker/viewmodel/CalendarPickerAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..6f96cf4f26d8beaeabef56967f213c050d126d5e --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/picker/calendarPicker/viewmodel/CalendarPickerAttributeModifier.ets @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 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 { CommonAttributeModifier } from '../../../../viewmodel/CommonAttributeModifier'; +import type { CalendarPickerDescriptor } from './CalendarPickerDescriptor'; + +@Observed +export class CalendarPickerAttributeModifier extends CommonAttributeModifier { + public applyNormalAttribute(instance: CalendarPickerAttribute): void { + this.assignAttribute((descriptor => descriptor.edgeAlign), + (val) => instance.edgeAlign(val?.alignType, val?.offset)); + this.assignAttribute((descriptor => descriptor.textStyle), (val) => instance.textStyle(val)); + } +} diff --git a/features/componentlibrary/src/main/ets/componentdetailview/picker/calendarPicker/viewmodel/CalendarPickerCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/picker/calendarPicker/viewmodel/CalendarPickerCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..62d192f25223ccf9ac502a7354d033cb12e9e8fe --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/picker/calendarPicker/viewmodel/CalendarPickerCodeGenerator.ets @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../../viewmodel/Attribute'; +import { commonFontColorMap, fontWeightMapData } from '../../../common/entity/CommonMapData'; +import { CommonCodeGenerator } from '../../../../viewmodel/CommonCodeGenerator'; +import { calendarAlignTypeMapData } from '../entity/CalendarPickerAttributeMapping'; + +export class CalendarPickerCodeGenerator implements CommonCodeGenerator { + private calendarOffsetX: number = 0; + private calendarOffsetY: number = 0; + private calendarAlignType: string = calendarAlignTypeMapData.get('Default')!.code; + private pickerFontColor: string = commonFontColorMap.get('Default')!.code; + private pickerFontSize: number = 16; + private pickerFontWeight: string = fontWeightMapData.get('Default')!.code; + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'calendarAlignType': + this.calendarAlignType = + calendarAlignTypeMapData.get(attribute.currentValue)?.code ?? this.calendarAlignType; + break; + case 'calendarOffsetX': + this.calendarOffsetX = Number(attribute.currentValue); + break; + case 'calendarOffsetY': + this.calendarOffsetY = Number(attribute.currentValue); + break; + case 'pickerFontColor': + this.pickerFontColor = attribute.currentValue; + break; + case 'pickerFontSize': + this.pickerFontSize = Number(attribute.currentValue); + break; + case 'pickerFontWeight': + this.pickerFontWeight = fontWeightMapData.get(attribute.currentValue)?.code ?? this.pickerFontWeight; + break; + default: + break; + } + }); + return `@Component +struct CalendarPickerComponent { + build() { + Column() { + CalendarPicker({ hintRadius: 10, selected: new Date('2024-03-05') }) + .width('30%') + .edgeAlign(${this.calendarAlignType}, { dx: ${this.calendarOffsetX}, dy: ${this.calendarOffsetY} }) + .textStyle({ color: '${this.pickerFontColor}', font: { size: ${this.pickerFontSize}, weight: ${this.pickerFontWeight} } }) + } + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .width('100%') + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/picker/calendarPicker/viewmodel/CalendarPickerDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/picker/calendarPicker/viewmodel/CalendarPickerDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..78a19150d870aca3725cdb8a2ae1abb26175ee16 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/picker/calendarPicker/viewmodel/CalendarPickerDescriptor.ets @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../../viewmodel/CommonDescriptor'; +import { fontWeightMapData } from '../../../common/entity/CommonMapData'; +import { + calendarAlignTypeMapData, + EdgeAlign, + edgeAlignDefault, + textStyleDefault, +} from '../entity/CalendarPickerAttributeMapping'; + +@Observed +export class CalendarPickerDescriptor extends CommonDescriptor { + public edgeAlign: EdgeAlign = edgeAlignDefault; + public textStyle: PickerTextStyle = textStyleDefault; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'calendarAlignType': + this.edgeAlign = + { + alignType: calendarAlignTypeMapData.get(attribute.currentValue)?.value ?? CalendarAlign.END, + offset: this.edgeAlign.offset, + }; + break; + case 'calendarOffsetX': + this.edgeAlign = + { + alignType: this.edgeAlign.alignType, + offset: { dx: Number(attribute.currentValue), dy: this.edgeAlign.offset?.dy || 0 }, + }; + break; + case 'calendarOffsetY': + this.edgeAlign = + { + alignType: this.edgeAlign.alignType, + offset: { dx: this.edgeAlign.offset?.dx || 0, dy: Number(attribute.currentValue) }, + }; + break; + case 'pickerFontColor': + this.textStyle = + { color: attribute.currentValue, font: this.textStyle.font }; + break; + case 'pickerFontSize': + this.textStyle = + { + color: this.textStyle.color, + font: { size: Number(attribute.currentValue), weight: this.textStyle.font?.weight }, + }; + break; + case 'pickerFontWeight': + this.textStyle = + { + color: this.textStyle.color, + font: { size: this.textStyle.font?.size, weight: fontWeightMapData.get(attribute.currentValue)?.value }, + }; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/picker/camerapicker/component/CameraPickerBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/picker/camerapicker/component/CameraPickerBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..82baf523613719092ad3126700ab9ab5454fd480 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/picker/camerapicker/component/CameraPickerBuilder.ets @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 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 type { DescriptorWrapper } from '../../../../viewmodel/DescriptorWrapper'; +import type { CameraPickerDescriptor } from '../viewmodel/CameraPickerDescriptor'; +import { CameraPickerComponent } from './CameraPickerComponent'; + +@Builder +export function CameraPickerBuilder(_$$: DescriptorWrapper) { + CameraPickerComponent({ cameraPickerDescriptor: _$$.descriptor as CameraPickerDescriptor }) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/picker/camerapicker/component/CameraPickerComponent.ets b/features/componentlibrary/src/main/ets/componentdetailview/picker/camerapicker/component/CameraPickerComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..4c2a1185fa49520f94a37dc32dd0a93510c6d20c --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/picker/camerapicker/component/CameraPickerComponent.ets @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2024 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 type { common } from '@kit.AbilityKit'; +import { promptAction } from '@kit.ArkUI'; +import type { BusinessError } from '@kit.BasicServicesKit'; +import { camera, cameraPicker } from '@kit.CameraKit'; +import { Logger } from '@ohos/common'; +import type { CameraPickerDescriptor } from '../viewmodel/CameraPickerDescriptor'; + +const TAG: string = '[CameraPickerBuilder]'; +const CODE_SUCC: number = 0; + +@Component +export struct CameraPickerComponent { + @Prop cameraPickerDescriptor: CameraPickerDescriptor; + @State uri?: ResourceStr = undefined; + @State mediaType: cameraPicker.PickerMediaType = cameraPicker.PickerMediaType.PHOTO; + @State isFilled: boolean = false; + @State cameraNum: number = 0; + private controller: VideoController = new VideoController(); + + aboutToAppear() { + const context: common.UIAbilityContext = getContext() as common.UIAbilityContext; + let cameraManager: camera.CameraManager; + try { + cameraManager = camera.getCameraManager(context); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `GetCameraManager error, the code is ${error.code}, the message is ${error.message}`); + return; + } + const cameraArray: camera.CameraDevice[] = this.getSupportedCameras(cameraManager); + this.cameraNum = cameraArray.length; + } + + build() { + Stack({ alignContent: Alignment.Center }) { + if (this.mediaType === cameraPicker.PickerMediaType.VIDEO) { + Video({ src: this.uri, controller: this.controller }) + .width('100%') + .height('100%') + .objectFit(ImageFit.Contain) + .borderRadius($r('sys.float.corner_radius_level8')) + .onPrepared(() => { + this.controller.setCurrentTime(0.5, SeekMode.Accurate) + }) + } else { + Image(this.uri) + .width('100%') + .height('100%') + .objectFit(ImageFit.Contain) + .borderRadius($r('sys.float.corner_radius_level8')) + } + Button($r('app.string.camera_picker_button'), { buttonStyle: ButtonStyleMode.NORMAL }) + .backgroundBlurStyle(this.isFilled ? BlurStyle.COMPONENT_REGULAR : BlurStyle.NONE, + { adaptiveColor: AdaptiveColor.AVERAGE }) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .fontColor(this.isFilled ? $r('sys.color.font_on_primary') : $r('sys.color.font_emphasize')) + .margin({ bottom: $r('sys.float.padding_level10') }) + .onClick(async () => { + // some device did not have any camera. + if (this.cameraNum === 0) { + try { + promptAction.showToast({ message: $r('app.string.device_camera') }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Show toast error, the code is ${error.code}}, the message is ${error.message}`); + } + return; + } + try { + const pickerProfile: cameraPicker.PickerProfile = { + cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK, + }; + const pickerResult: cameraPicker.PickerResult = + await cameraPicker.pick(getContext(), this.cameraPickerDescriptor.mediaTypes, pickerProfile); + if (pickerResult.resultCode === CODE_SUCC) { + this.isFilled = true; + this.uri = pickerResult.resultUri; + this.mediaType = pickerResult.mediaType; + } else { + Logger.error(TAG, 'the pick pickerResult : error'); + } + } catch (error) { + const err = error as BusinessError; + Logger.error(TAG, `${err.code},${err.message}`); + } + }) + } + } + + getSupportedCameras(cameraManager: camera.CameraManager): camera.CameraDevice[] { + let cameras: camera.CameraDevice[] = []; + try { + cameras = cameraManager.getSupportedCameras(); + } catch (error) { + const err = error as BusinessError; + Logger.error(TAG, `The getSupportedCameras call failed. error code: ${err.code}`); + } + return cameras; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/picker/camerapicker/entity/CameraPickerMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/picker/camerapicker/entity/CameraPickerMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..c5f00b8c365ff4b4cda0c67ae7f30af6472ca179 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/picker/camerapicker/entity/CameraPickerMapping.ets @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 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 { cameraPicker } from '@kit.CameraKit'; + +class CameraMediaType { + public code: string; + public value: cameraPicker.PickerMediaType[]; + + constructor(code: string, value: cameraPicker.PickerMediaType[]) { + this.code = code; + this.value = value; + } +} + +export const pickerMediaType: Map = new Map([ + ['Default', new CameraMediaType('[cameraPicker.PickerMediaType.PHOTO, cameraPicker.PickerMediaType.VIDEO]', + [cameraPicker.PickerMediaType.PHOTO, cameraPicker.PickerMediaType.VIDEO])], + ['拍照模式', new CameraMediaType('[cameraPicker.PickerMediaType.PHOTO]', [cameraPicker.PickerMediaType.PHOTO])], + ['录制模式', new CameraMediaType('[cameraPicker.PickerMediaType.VIDEO]', [cameraPicker.PickerMediaType.VIDEO])], + ['混合模式', new CameraMediaType('[cameraPicker.PickerMediaType.PHOTO, cameraPicker.PickerMediaType.VIDEO]', + [cameraPicker.PickerMediaType.PHOTO, cameraPicker.PickerMediaType.VIDEO])], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/picker/camerapicker/viewmodel/CameraPickerCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/picker/camerapicker/viewmodel/CameraPickerCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..e2d3174e0d3475e55ab777a094a0c15faacc5dc3 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/picker/camerapicker/viewmodel/CameraPickerCodeGenerator.ets @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../../viewmodel/CommonCodeGenerator'; +import { pickerMediaType } from '../entity/CameraPickerMapping'; + +export class CameraPickerCodeGenerator implements CommonCodeGenerator { + private mediaTypes: string = pickerMediaType.get('Default')!.code; + + public generate(_attributes: OriginAttribute[]): string { + _attributes.forEach((attribute) => { + switch (attribute.name) { + case 'mediaTypes': + this.mediaTypes = pickerMediaType.get(attribute.currentValue)?.code ?? this.mediaTypes; + break; + default: + break; + } + }); + return `import { camera, cameraPicker } from '@kit.CameraKit'; +import { promptAction } from '@kit.ArkUI'; +import type { BusinessError } from '@kit.BasicServicesKit'; + +@Component +export struct CameraPickerComponent { + @State uri?: ResourceStr = undefined; + @State mediaType: cameraPicker.PickerMediaType = cameraPicker.PickerMediaType.PHOTO; + @State isFilled: boolean = false; + @State cameraNum: number = 0; + private controller: VideoController = new VideoController(); + + aboutToAppear() { + const context: common.UIAbilityContext = getContext() as common.UIAbilityContext; + let cameraManager: camera.CameraManager; + try { + cameraManager = camera.getCameraManager(context); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error('GetCameraManager error'); + return; + } + const cameraArray: camera.CameraDevice[] = this.getSupportedCameras(cameraManager); + this.cameraNum = cameraArray.length; + } + + build() { + Stack({ alignContent: Alignment.Center }) { + if (this.mediaType === cameraPicker.PickerMediaType.VIDEO) { + Video({ src: this.uri, controller: this.controller }) + .width('100%') + .height('100%') + .objectFit(ImageFit.Contain) + .borderRadius($r('sys.float.corner_radius_level8')) + .onPrepared(() => { + this.controller.setCurrentTime(0.5, SeekMode.Accurate) + }) + } else { + Image(this.uri) + .width('100%') + .height('100%') + .objectFit(ImageFit.Contain) + .borderRadius($r('sys.float.corner_radius_level8')) + } + Button('安全使用相机', { buttonStyle: ButtonStyleMode.NORMAL }) + .backgroundBlurStyle(this.isFilled ? BlurStyle.COMPONENT_REGULAR : BlurStyle.NONE, + { adaptiveColor: AdaptiveColor.AVERAGE }) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .fontColor(this.isFilled ? $r('sys.color.font_on_primary') : $r('sys.color.font_emphasize')) + .margin({ bottom: $r('sys.float.padding_level10') }) + .onClick(async () => { + if (this.cameraNum === 0) { + try { + promptAction.showToast({ message: '该设备没有摄像头' }); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`Show toast error, the code is \${error.code}, the message is \${error.message}\`); + } + return; + } + try { + const pickerProfile: cameraPicker.PickerProfile = { + cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK, + }; + const pickerResult: cameraPicker.PickerResult = await cameraPicker.pick(getContext(), + ${this.mediaTypes}, pickerProfile); + console.info(\`the pick pickerResult is: \${JSON.stringify(pickerResult)}\`); + if (pickerResult.resultCode === 0) { + this.isFilled = true; + this.uri = pickerResult.resultUri; + this.mediaType = pickerResult.mediaType; + } else { + console.error("the pick pickerResult:error"); + } + } catch (error) { + const err = error as BusinessError; + console.error(\`\${err.code},\${err.message}\`); + } + }) + } + } + + getSupportedCameras(cameraManager: camera.CameraManager): camera.CameraDevice[] { + let cameras: camera.CameraDevice[] = []; + try { + cameras = cameraManager.getSupportedCameras(); + } catch (error) { + const err = error as BusinessError; + console.error('The getSupportedCameras call failed.'); + } + return cameras; + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/picker/camerapicker/viewmodel/CameraPickerDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/picker/camerapicker/viewmodel/CameraPickerDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..1c06eaf000fbbac1fab6299160f055be5abae4c1 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/picker/camerapicker/viewmodel/CameraPickerDescriptor.ets @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 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 { cameraPicker } from '@kit.CameraKit'; +import { CommonDescriptor } from '../../../../viewmodel/CommonDescriptor'; +import { pickerMediaType } from '../entity/CameraPickerMapping'; +import type { OriginAttribute } from '../../../../viewmodel/Attribute'; + +@Observed +export class CameraPickerDescriptor extends CommonDescriptor { + public mediaTypes: cameraPicker.PickerMediaType[] = pickerMediaType.get('Default')!.value; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'mediaTypes': + this.mediaTypes = + pickerMediaType.get(attribute.currentValue)?.value ?? pickerMediaType.get('Default')!.value; + break; + default: + break; + } + }) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/picker/datepicker/component/DatePickerBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/picker/datepicker/component/DatePickerBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..2c8c7d471c19a1e5aff030bdbb5d3edd8912586e --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/picker/datepicker/component/DatePickerBuilder.ets @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 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 type { DescriptorWrapper } from '../../../../viewmodel/DescriptorWrapper'; +import { DatePickerAttributeModifier } from '../viewmodel/DatePickerAttributeModifier'; +import type { DatePickerDescriptor } from '../viewmodel/DatePickerDescriptor'; + +@Builder +export function DatePickerBuilder($$: DescriptorWrapper) { + Column() { + DatePicker({ + start: new Date('1970-1-1'), + end: new Date('2100-1-1'), + selected: new Date('2021-08-08'), + }) + .margin({ + left: $r('sys.float.padding_level12'), + right: $r('sys.float.padding_level12'), + top: $r('sys.float.padding_level8'), + bottom: $r('sys.float.padding_level8'), + }) + .selectedTextStyle(($$.descriptor as DatePickerDescriptor).selectedTextStyle) + .textStyle(($$.descriptor as DatePickerDescriptor).textStyle) + .attributeModifier(new DatePickerAttributeModifier($$.descriptor as DatePickerDescriptor)) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/picker/datepicker/viewmodel/DatePickerAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/picker/datepicker/viewmodel/DatePickerAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..b95c71675d979e20645e23cdf403d556c73f42c8 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/picker/datepicker/viewmodel/DatePickerAttributeModifier.ets @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 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 { CommonAttributeModifier } from '../../../../viewmodel/CommonAttributeModifier'; +import type { DatePickerDescriptor } from './DatePickerDescriptor'; + +@Observed +export class DatePickerAttributeModifier extends CommonAttributeModifier { + public applyNormalAttribute(instance: DatePickerAttribute): void { + this.assignAttribute((descriptor => descriptor.lunar), (val) => instance.lunar(val)); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/picker/datepicker/viewmodel/DatePickerCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/picker/datepicker/viewmodel/DatePickerCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..ddff2940f0ff044f61d3d6b6357ee15b69d942ba --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/picker/datepicker/viewmodel/DatePickerCodeGenerator.ets @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../../viewmodel/Attribute'; +import { commonFontColorMap, fontWeightMapData } from '../../../common/entity/CommonMapData'; +import { CommonCodeGenerator } from '../../../../viewmodel/CommonCodeGenerator'; + +export class DatePickerCodeGenerator implements CommonCodeGenerator { + private lunar: boolean = false; + private selectedTextColor: ResourceColor = commonFontColorMap.get('Default')!.code; + private selectedFontSize: number = 20; + private selectedFontWeight: string = fontWeightMapData.get('Default')!.code; + private fontSize: number = 16; + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'lunar': + this.lunar = JSON.parse(attribute.currentValue); + break; + case 'selectedTextColor': + this.selectedTextColor = attribute.currentValue; + break; + case 'selectedFontSize': + this.selectedFontSize = Number(attribute.currentValue); + break; + case 'selectedFontWeight': + this.selectedFontWeight = + fontWeightMapData.get(attribute.currentValue)?.code ?? fontWeightMapData.get('Default')!.code; + break; + case 'fontSize': + this.fontSize = Number(attribute.currentValue); + break; + default: + break; + } + }); + return `@Component +struct DatePickerComponent { + build() { + Column() { + DatePicker({ + start: new Date('1970-1-1'), + end: new Date('2100-1-1'), + selected: new Date('2021-08-08') + }) + .margin({ + left: $r('sys.float.padding_level12'), + right: $r('sys.float.padding_level12'), + top: $r('sys.float.padding_level8'), + bottom: $r('sys.float.padding_level8') + }) + .lunar(${this.lunar}) + .selectedTextStyle({ + color: '${this.selectedTextColor}', + font: { + size: ${this.selectedFontSize}, + weight: ${this.selectedFontWeight} + } + }) + .textStyle({ + color: $r('sys.color.font_primary'), + font: { + size: ${this.fontSize}, + weight: FontWeight.Regular + } + }) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/picker/datepicker/viewmodel/DatePickerDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/picker/datepicker/viewmodel/DatePickerDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..b6c32a6c85d2499556e1181cc04c2d70e9c759ef --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/picker/datepicker/viewmodel/DatePickerDescriptor.ets @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../../viewmodel/Attribute'; +import { fontWeightMapData } from '../../../common/entity/CommonMapData'; +import { CommonDescriptor } from '../../../../viewmodel/CommonDescriptor'; + +@Observed +export class DatePickerDescriptor extends CommonDescriptor { + public lunar: boolean = false; + public selectedTextStyle: PickerTextStyle = { + color: $r('app.color.date_picker_default_color'), + font: { + size: 20, + weight: FontWeight.Medium, + }, + }; + public textStyle: PickerTextStyle = { + color: $r('sys.color.font_primary'), + font: { + size: 16, + weight: FontWeight.Regular, + }, + }; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'lunar': + this.lunar = JSON.parse(attribute.currentValue); + break; + case 'selectedTextColor': + this.selectedTextStyle = { + color: attribute.currentValue, + font: { + size: this.selectedTextStyle.font?.size, + weight: this.selectedTextStyle.font?.weight, + }, + }; + break; + case 'selectedFontSize': + this.selectedTextStyle = { + color: this.selectedTextStyle.color, + font: { + size: Number(attribute.currentValue), + weight: this.selectedTextStyle.font?.weight, + }, + }; + break; + case 'selectedFontWeight': + this.selectedTextStyle = { + color: this.selectedTextStyle.color, + font: { + size: this.selectedTextStyle.font?.size, + weight: fontWeightMapData.get(attribute.currentValue)?.value ?? this.selectedTextStyle.font?.weight, + }, + }; + break; + case 'fontSize': + this.textStyle.font = { + size: Number(attribute.currentValue), + weight: FontWeight.Regular, + }; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/popup/component/PopupBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/popup/component/PopupBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..a2b659418890a7c56f0380b01c29b5d5c5959562 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/popup/component/PopupBuilder.ets @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2024 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 { Popup } from '@kit.ArkUI'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import { popupBtnTextCodeMapData, PopupStyle } from '../entity/PopupMapping'; +import type { PopupDescriptor } from '../viewmodel/PopupDescriptor'; + +@Builder +export function PopupBuilder($$: DescriptorWrapper) { + PopupComponent({ popupDescriptor: $$.descriptor as PopupDescriptor }) +} + +@Component +struct PopupComponent { + @Prop popupDescriptor: PopupDescriptor; + @State handlePopup: boolean = false; + + @Builder + popupWithButtonBuilder(type: PopupStyle) { + Row() { + if (type === PopupStyle.STYLE_BUTTON) { + this.popupButton(); + } else if (type === PopupStyle.STYLE_TEXT) { + this.popupText(); + } else if (type === PopupStyle.STYLE_ICON) { + this.popupIcon(); + } + } + .onKeyPreIme((keyEvent: KeyEvent) => { + if ((keyEvent?.keyText === 'KEYCODE_DPAD_RIGHT' || keyEvent?.keyText === 'KEYCODE_DPAD_LEFT') && + keyEvent.type === KeyType.Down) { + return true; + } + return false; + }) + } + + @Builder + popupButton() { + Popup({ + title: { + text: $r('app.string.title'), + }, + message: { + text: $r('app.string.popup_button_message'), + }, + showClose: true, + onClose: () => { + this.handlePopup = false; + }, + buttons: [ + { + text: $r('app.string.confirmTip'), + action: () => { + this.handlePopup = false; + }, + }, + ], + }) + } + + @Builder + popupText() { + Popup({ + message: { + text: $r('app.string.popup_text_message') + }, + showClose: true, + onClose: () => { + this.handlePopup = false; + }, + }) + } + + @Builder + popupIcon() { + Popup({ + icon: { + image: $r('app.media.startIcon') + }, + title: { + text: $r('app.string.title'), + }, + message: { + text: $r('app.string.popup_icon_message') + }, + showClose: true, + onClose: () => { + this.handlePopup = false; + }, + }) + } + + build() { + Column() { + Button(popupBtnTextCodeMapData.get(this.popupDescriptor.type)!) + .backgroundColor($r('sys.color.background_secondary')) + .fontColor($r('sys.color.font_emphasize')) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .onClick(() => { + this.handlePopup = true; + }) + .bindPopup(this.handlePopup, { + builder: this.popupWithButtonBuilder(this.popupDescriptor.type), + placement: this.popupDescriptor.placement, + focusable: true, + width: this.popupDescriptor.type === PopupStyle.STYLE_TEXT ? undefined : $r('app.float.popup_width_large'), + onStateChange: (e) => { + if (!e.isVisible) { + this.handlePopup = false; + } + }, + }) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/popup/entity/PopupMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/popup/entity/PopupMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..e0cfad5383edf2d3107e5e7422ec10ef2f8411af --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/popup/entity/PopupMapping.ets @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2024 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. + */ + +class PlacementPosition { + public code: string; + public value: Placement; + + constructor(code: string, value: Placement) { + this.code = code; + this.value = value; + } +} + +export const placementMapData: Map = new Map([ + ['Default', new PlacementPosition('Placement.Bottom', Placement.Bottom)], + ['Left', new PlacementPosition('Placement.Left', Placement.Left)], + ['Right', new PlacementPosition('Placement.Right', Placement.Right)], + ['Top', new PlacementPosition('Placement.Top', Placement.Top)], + ['Bottom', new PlacementPosition('Placement.Bottom', Placement.Bottom)], + ['TopLeft', new PlacementPosition('Placement.TopLeft', Placement.TopLeft)], + ['TopRight', new PlacementPosition('Placement.TopRight', Placement.TopRight)], + ['BottomLeft', new PlacementPosition('Placement.BottomLeft', Placement.BottomLeft)], + ['BottomRight', new PlacementPosition('Placement.BottomRight', Placement.BottomRight)], + ['LeftTop', new PlacementPosition('Placement.LeftTop', Placement.LeftTop)], + ['LeftBottom', new PlacementPosition('Placement.LeftBottom', Placement.LeftBottom)], + ['RightTop', new PlacementPosition('Placement.RightTop', Placement.RightTop)], + ['RightBottom', new PlacementPosition('Placement.RightBottom', Placement.RightBottom)], +]); + +export enum PopupStyle { + STYLE_BUTTON = 1, + STYLE_TEXT = 2, + STYLE_ICON = 3, +} + +export const popupStyleMapData: Map = new Map([ + ['Default', PopupStyle.STYLE_BUTTON], + ['按钮气泡', PopupStyle.STYLE_BUTTON], + ['文字气泡', PopupStyle.STYLE_TEXT], + ['图文气泡', PopupStyle.STYLE_ICON], +]); + +export const popupStyleCodeMapData: Map = new Map([ + [PopupStyle.STYLE_BUTTON, `Popup({ + title: { + text: '标题', + }, + message: { + text: '这是一个带按钮的气泡' + }, + showClose: true, + onClose: () => { + this.handlePopup = false; + }, + buttons: [ + { + text: '知道了', + action: () => { + this.handlePopup = false; + }, + }, + ] + })`], + [PopupStyle.STYLE_TEXT, `Popup({ + message: { + text: '这是一个文字气泡' + }, + showClose: true, + onClose: () => { + this.handlePopup = false; + } + })`], + [PopupStyle.STYLE_ICON, `Popup({ + icon: { + // Replace the resource images under src/main/resources/base/media in your own project. + image: $r('app.media.startIcon') + }, + title: { + text: '标题', + }, + message: { + text: '这是一个带图标的弹出气泡' + }, + showClose: true, + onClose: () => { + this.handlePopup = false; + } + })`], +]); + +export const popupBtnTextCodeMapData: Map = new Map([ + [PopupStyle.STYLE_BUTTON, $r('app.string.popup_button_button')], + [PopupStyle.STYLE_TEXT, $r('app.string.popup_button_text')], + [PopupStyle.STYLE_ICON, $r('app.string.popup_button_icon')], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/popup/viewmodel/PopupCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/popup/viewmodel/PopupCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..cc55f5fe702ee6c8c14c713f7c973381d13ab85c --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/popup/viewmodel/PopupCodeGenerator.ets @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { placementMapData, PopupStyle, popupStyleCodeMapData, popupStyleMapData } from '../entity/PopupMapping'; + +export class PopupCodeGenerator implements CommonCodeGenerator { + private placement: string = placementMapData.get('Default')!.code; + private type: PopupStyle = popupStyleMapData.get('Default')!; + private styleCode: string = popupStyleCodeMapData.get(this.type)!; + + public generate(_attributes: OriginAttribute[]): string { + let width: string = ''; + _attributes.forEach((attribute) => { + switch (attribute.name) { + case 'placement': + this.placement = placementMapData.get(attribute.currentValue)?.code ?? this.placement; + break; + case 'type': + this.type = popupStyleMapData.get(attribute.currentValue) ?? this.type; + this.styleCode = popupStyleCodeMapData.get(this.type) ?? this.styleCode; + if (this.type !== PopupStyle.STYLE_TEXT) { + width = ` + width: '300vp',`; + } + break; + default: + break; + } + }); + return `import { Popup } from '@kit.ArkUI'; + +@Component +struct PopupComponent { + @State handlePopup: boolean = false; + + @Builder + popupWithButtonBuilder() { + Row() { + ${this.styleCode} + } + } + + build() { + Column() { + Button('点击弹出气泡') + .backgroundColor($r('sys.color.background_secondary')) + .fontColor($r('sys.color.font_emphasize')) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .onClick(() => { + this.handlePopup = true; + }) + .bindPopup(this.handlePopup, { + builder: this.popupWithButtonBuilder(), + placement: ${this.placement},${width} + onStateChange: (e) => { + if (!e.isVisible) { + this.handlePopup = false; + } + } + }) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/popup/viewmodel/PopupDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/popup/viewmodel/PopupDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..8eb5ac2056d93c927aa38b0bc7968a45ab6edeee --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/popup/viewmodel/PopupDescriptor.ets @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { placementMapData, PopupStyle, popupStyleMapData } from '../entity/PopupMapping'; + +@Observed +export class PopupDescriptor extends CommonDescriptor { + public placement: Placement = placementMapData.get('Default')!.value; + public type: PopupStyle = popupStyleMapData.get('Default')!; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'type': + this.type = popupStyleMapData.get(attribute.currentValue) ?? this.type; + break; + case 'placement': + this.placement = placementMapData.get(attribute.currentValue)?.value ?? this.placement; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/progress/component/ProgressBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/progress/component/ProgressBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..b2bbc6e771d0c97b53c195c39d46c427161edb6d --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/progress/component/ProgressBuilder.ets @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 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 { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import { ProgressAttributeModifier } from '../viewmodel/ProgressAttributeModifier'; +import type { ProgressDescriptor } from '../viewmodel/ProgressDescriptor'; + +@Builder +export function ProgressBuilder($$: DescriptorWrapper) { + Column() { + if (($$.descriptor as ProgressDescriptor).kind === 'Progress') { + Progress({ + value: ($$.descriptor as ProgressDescriptor).value, + total: DetailPageConstant.PROGRESS_MAX_VALUE, + type: ($$.descriptor as ProgressDescriptor).type, + }) + .height(($$.descriptor as ProgressDescriptor).type === ProgressType.Capsule ? + DetailPageConstant.PROGRESS_CAPSULE_HEIGHT : DetailPageConstant.PROGRESS_LINE_HEIGHT) + .attributeModifier(new ProgressAttributeModifier($$.descriptor as ProgressDescriptor)) + } else { + LoadingProgress() + .color(($$.descriptor as ProgressDescriptor).color) + .size({ width: DetailPageConstant.PROGRESS_CIRCLE_WIDTH, height: DetailPageConstant.PROGRESS_CIRCLE_WIDTH }) + } + } + .padding($r('sys.float.padding_level16')) + .justifyContent(FlexAlign.Center) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/progress/entity/ProgressAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/progress/entity/ProgressAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..5b28c6d39d0652c989a267a6612a7240daadc202 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/progress/entity/ProgressAttributeMapping.ets @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024 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 { CommonColorMapping, CommonNumberMapping, CommonStringMapping } from '../../common/entity/CommonMapData'; + +class StyleMapping { + public readonly code: string; + public readonly value: CommonProgressStyleOptions; + + constructor(code: string, value: CommonProgressStyleOptions) { + this.code = code; + this.value = value; + } +} + +export const progressStyleMapData: Map = new Map([ + ['LinearStyle', new StyleMapping('{ strokeWidth: 4, enableSmoothEffect: true }', + { strokeWidth: 4, enableSmoothEffect: true } as LinearStyleOptions)], + ['ProgressStyle', new StyleMapping('{ strokeWidth: 4, scaleCount: 20, scaleWidth: 4 }', + { strokeWidth: 4, scaleCount: 20, scaleWidth: 4 } as ProgressStyleOptions)], + ['RingStyle1', new StyleMapping('{ strokeWidth: 4, enableScanEffect: true }', + { strokeWidth: 4, enableScanEffect: true } as ProgressStyleOptions)], + ['RingStyle2', + new StyleMapping('{ strokeWidth: 4, shadow: true }', { strokeWidth: 4, shadow: true } as RingStyleOptions)], + ['EclipseStyle', new StyleMapping('{ strokeWidth: 4, enableSmoothEffect: true }', + { strokeWidth: 4, enableSmoothEffect: true } as EclipseStyleOptions)], + ['ScaleRingStyle', new StyleMapping('{ strokeWidth: 4, scaleCount: 15, scaleWidth: 50 }', + { strokeWidth: 4, scaleCount: 15, scaleWidth: 50 } as ScaleRingStyleOptions)], + ['CapsuleStyle', new StyleMapping(`{ + borderWidth: 1, + enableScanEffect: false, + fontColor: Color.Gray, + showDefaultPercentage: true, + }`, { + borderWidth: 1, + enableScanEffect: false, + fontColor: Color.Gray, + showDefaultPercentage: true, + } as CapsuleStyleOptions)], + ['Default', new StyleMapping('{ strokeWidth: 4, enableSmoothEffect: true }', + { strokeWidth: 4, enableSmoothEffect: true } as LinearStyleOptions)], +]); + +class ProgressTypeMap { + public readonly code: string; + public readonly value: ProgressType; + + constructor(code: string, value: ProgressType) { + this.code = code; + this.value = value; + } +} + +export const progressTypeMapData: Map = new Map([ + ['LinearStyle', new ProgressTypeMap('ProgressType.Linear', ProgressType.Linear)], + ['ProgressStyle', new ProgressTypeMap('ProgressType.Ring', ProgressType.Ring)], + ['RingStyle1', new ProgressTypeMap('ProgressType.Ring', ProgressType.Ring)], + ['RingStyle2', new ProgressTypeMap('ProgressType.Ring', ProgressType.Ring)], + ['EclipseStyle', new ProgressTypeMap('ProgressType.Eclipse', ProgressType.Eclipse)], + ['ScaleRingStyle', new ProgressTypeMap('ProgressType.ScaleRing', ProgressType.ScaleRing)], + ['CapsuleStyle', new ProgressTypeMap('ProgressType.Capsule', ProgressType.Capsule)], + ['Default', new ProgressTypeMap('ProgressType.Linear', ProgressType.Linear)], +]); + +export const progressValueMapData: Map = new Map([ + ['Default', new CommonNumberMapping('10', 10)], +]); + +export const progressColorMapData: Map = new Map([ + ['Default', new CommonColorMapping('rgba(0,85,255,1.00)', 'rgba(0,85,255,1.00)')], +]); + +export const progressKindMapData: Map = new Map([ + ['Progress', new CommonStringMapping('Progress', 'Progress')], + ['LoadingProgress', new CommonStringMapping('LoadingProgress', 'LoadingProgress')], + ['Default', new CommonStringMapping('LoadingProgress', 'LoadingProgress')], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/progress/viewmodel/ProgressAttributeFilter.ets b/features/componentlibrary/src/main/ets/componentdetailview/progress/viewmodel/ProgressAttributeFilter.ets new file mode 100644 index 0000000000000000000000000000000000000000..bcad2b46638b01b367b7beeff4a627a9c7358953 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/progress/viewmodel/ProgressAttributeFilter.ets @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 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 type { ObservedArray } from '@ohos/common'; +import type { Attribute } from '../../../viewmodel/Attribute'; +import { CommonAttributeFilter } from '../../../viewmodel/CommonAttributeFilter'; + +export class ProgressAttributeFilter implements CommonAttributeFilter { + filter(attributes: ObservedArray): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'kind': + const valueIndex = attributes.findIndex((item) => item.name === 'value'); + const styleIndex = attributes.findIndex((item) => item.name === 'style'); + if (valueIndex !== -1 && styleIndex !== -1) { + if (attribute.currentValue === 'Progress') { + attributes[valueIndex].enable = true; + attributes[styleIndex].enable = true; + } else { + attributes[valueIndex].enable = false; + attributes[styleIndex].enable = false; + } + } + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/progress/viewmodel/ProgressAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/progress/viewmodel/ProgressAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..be030ecc4a211f5f177a2013578e1c225bfb43cf --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/progress/viewmodel/ProgressAttributeModifier.ets @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 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 { CommonAttributeModifier } from '../../../viewmodel/CommonAttributeModifier'; +import type { ProgressDescriptor } from './ProgressDescriptor'; + +@Observed +export class ProgressAttributeModifier extends CommonAttributeModifier { + applyNormalAttribute(instance: ProgressAttribute): void { + this.assignAttribute((descriptor => descriptor.color), (val) => instance.color(val)); + this.assignAttribute((descriptor => descriptor.style), (val) => instance.style(val)); + } +} diff --git a/features/componentlibrary/src/main/ets/componentdetailview/progress/viewmodel/ProgressCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/progress/viewmodel/ProgressCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..b4d96509d3810e2e5111cfaa32fd184c67a92604 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/progress/viewmodel/ProgressCodeGenerator.ets @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024 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 { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { + progressColorMapData, + progressKindMapData, + progressStyleMapData, + progressTypeMapData, + progressValueMapData, +} from '../entity/ProgressAttributeMapping'; + +export class ProgressCodeGenerator implements CommonCodeGenerator { + private value: string = progressValueMapData.get('Default')!.code; + private color: string = progressColorMapData.get('Default')!.code; + private type: string = progressTypeMapData.get('Default')!.code; + private style: string = progressStyleMapData.get('Default')!.code; + private kind: string = progressKindMapData.get('Default')!.code; + private height: number = DetailPageConstant.PROGRESS_LINE_HEIGHT; + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'value': + this.value = attribute.currentValue; + break; + case 'color': + this.color = attribute.currentValue; + break; + case 'kind': + this.kind = attribute.currentValue; + break; + case 'style': + this.type = progressTypeMapData.get(attribute.currentValue)?.code ?? this.type; + this.style = progressStyleMapData.get(attribute.currentValue)?.code ?? this.style; + this.height = this.type === 'ProgressType.Capsule' ? DetailPageConstant.PROGRESS_CAPSULE_HEIGHT : + DetailPageConstant.PROGRESS_LINE_HEIGHT; + break; + default: + break; + } + }); + let codeStr = ''; + if (this.kind === 'Progress') { + codeStr = `Progress({ value: ${this.value}, total: 100, type: ${this.type} }) + .color('${this.color}') + .style(${this.style}) + .height(${this.height})`; + } else { + codeStr = `LoadingProgress() + .color('${this.color}') + .size({ width: 64, height: 64 })`; + } + return `@Component +struct ProgressComponent { + build() { + Column() { + ${codeStr} + } + .padding($r('sys.float.padding_level16')) + .justifyContent(FlexAlign.Center) + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/progress/viewmodel/ProgressDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/progress/viewmodel/ProgressDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..6b5e813173df79351c9fa6dc96261d5def6805dc --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/progress/viewmodel/ProgressDescriptor.ets @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { + progressColorMapData, + progressKindMapData, + progressStyleMapData, + progressTypeMapData, + progressValueMapData, +} from '../entity/ProgressAttributeMapping'; + +@Observed +export class ProgressDescriptor extends CommonDescriptor { + public value: number = progressValueMapData.get('Default')!.value; + public color: ResourceColor = progressColorMapData.get('Default')!.value; + public style: CommonProgressStyleOptions = progressStyleMapData.get('Default')!.value; + public type: ProgressType = ProgressType.Linear; + public kind: string = progressKindMapData.get('Default')!.value; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'value': + this.value = Number(attribute.currentValue); + break; + case 'kind': + this.kind = attribute.currentValue; + break; + case 'color': + this.color = attribute.currentValue; + break; + case 'style': + this.style = + progressStyleMapData.get(attribute.currentValue)?.value ?? progressStyleMapData.get('Default')!.value; + this.type = progressTypeMapData.get(attribute.currentValue)?.value ?? ProgressType.Linear; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/rating/component/RatingBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/rating/component/RatingBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..2591c5eec88e2bd8cf7966a45e6cbfe1269da04f --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/rating/component/RatingBuilder.ets @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 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 { ComponentDetailManager } from '../../../viewmodel/ComponentDetailManager'; +import { ChangeAttributeEvent } from '../../../viewmodel/ComponentDetailPageVM'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import { RatingAttributeModifier } from '../viewmodel/RatingAttributeModifier'; +import type { RatingDescriptor } from '../viewmodel/RatingDescriptor'; + +@Builder +export function RatingBuilder($$: DescriptorWrapper) { + Rating({ + rating: ($$.descriptor as RatingDescriptor).rating, + indicator: ($$.descriptor as RatingDescriptor).indicator, + }) + .attributeModifier(new RatingAttributeModifier($$.descriptor as RatingDescriptor)) + .onChange((value: number) => { + ComponentDetailManager.getInstance().getDetailViewModel('Rating')?.sendEvent(new ChangeAttributeEvent('rating', + value.toString())); + }) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/rating/entity/RatingAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/rating/entity/RatingAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..6945239f2ac1f63bba7fb3a91f548c04d12b9f6f --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/rating/entity/RatingAttributeMapping.ets @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 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 { CommonBoolMapping, CommonNumberMapping } from '../../common/entity/CommonMapData'; + +export const ratingValueMapData: Map = new Map([ + ['Default', new CommonNumberMapping('0', 0)], +]); + +export const starsMapData: Map = new Map([ + ['Default', new CommonNumberMapping('5', 5)] +]); + +export const indicatorMapData: Map = new Map([ + ['Default', new CommonBoolMapping('false', false)], +]); + +export const starStyleMapData: Map = new Map([ + ['Default', new CommonBoolMapping('false', false)], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/rating/viewmodel/RatingAttributeFilter.ets b/features/componentlibrary/src/main/ets/componentdetailview/rating/viewmodel/RatingAttributeFilter.ets new file mode 100644 index 0000000000000000000000000000000000000000..e492b5e4940fd0d14b647d03dea47ebdf26a6a61 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/rating/viewmodel/RatingAttributeFilter.ets @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 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 type { ObservedArray } from '@ohos/common'; +import type { Attribute } from '../../../viewmodel/Attribute'; +import { CommonAttributeFilter } from '../../../viewmodel/CommonAttributeFilter'; +import type { SliderComAttribute } from '../../../viewmodel/ComponentDetailState'; + +export class RatingAttributeFilter implements CommonAttributeFilter { + public filter(attributes: ObservedArray): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'stars': + const ratingAttributeIndex = attributes.findIndex((attribute) => { + return attribute.name === 'rating'; + }) + if (ratingAttributeIndex !== -1) { + (attributes[ratingAttributeIndex] as SliderComAttribute).rightRange = Number(attribute.currentValue); + } + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/rating/viewmodel/RatingAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/rating/viewmodel/RatingAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..c6004c99a0697d19b5bc503b382a9636f89fb90f --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/rating/viewmodel/RatingAttributeModifier.ets @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 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 { CommonAttributeModifier } from '../../../viewmodel/CommonAttributeModifier'; +import type { RatingDescriptor } from './RatingDescriptor'; + +@Observed +export class RatingAttributeModifier extends CommonAttributeModifier { + public applyNormalAttribute(instance: RatingAttribute): void { + this.assignAttribute((descriptor => descriptor.stars), (val) => instance.stars(Number(val))); + this.assignAttribute((descriptor => descriptor.starStyle), (val) => instance.starStyle(Boolean(val) ? + { + backgroundUri: '/resources/base/media/rating_background.png', + foregroundUri: '/resources/base/media/rating_foreground.png', + } : { + backgroundUri: undefined, + foregroundUri: undefined, + })); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/rating/viewmodel/RatingCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/rating/viewmodel/RatingCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..fea7e2c2891a0462ec30a184ca7e70a379cada3f --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/rating/viewmodel/RatingCodeGenerator.ets @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { indicatorMapData, ratingValueMapData, starsMapData } from '../entity/RatingAttributeMapping'; + +export class RatingCodeGenerator implements CommonCodeGenerator { + private rating: string = ratingValueMapData.get('Default')!.code; + private indicator: string = indicatorMapData.get('Default')!.code; + private stars: string = starsMapData.get('Default')!.code; + private stepSize: string = '0.5'; + + public generate(attributes: OriginAttribute[]): string { + let codeSegment = ``; + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'rating': + this.rating = attribute.currentValue; + break; + case 'indicator': + this.indicator = attribute.currentValue.toLowerCase(); + break; + case 'stars': + this.stars = attribute.currentValue; + break; + case 'starStyle': + const bool = attribute.currentValue.toLowerCase() === 'true'; + codeSegment = bool ? `{ + // You need to replace the starStyle resource image in the media folder. + backgroundUri: '/resources/base/media/rating_background.png', + foregroundUri: '/resources/base/media/rating_foreground.png', + }` : `{ + backgroundUri: undefined, + foregroundUri: undefined, + }`; + break; + default: + break; + } + }); + return `@Component +struct RatingComponent { + build() { + Rating({ rating: ${this.rating}, indicator: ${this.indicator}}) + .stars(${this.stars}) + .stepSize(${this.stepSize}) + .starStyle(${codeSegment}) + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/rating/viewmodel/RatingDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/rating/viewmodel/RatingDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..925977fe691891ac06068f12cecdca0319b02af9 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/rating/viewmodel/RatingDescriptor.ets @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { + indicatorMapData, + ratingValueMapData, + starsMapData, + starStyleMapData, +} from '../entity/RatingAttributeMapping'; + +@Observed +export class RatingDescriptor extends CommonDescriptor { + public rating: number = ratingValueMapData.get('Default')!.value as number; + public indicator: boolean = indicatorMapData.get('Default')!.value as boolean; + public stars: number = starsMapData.get('Default')!.value as number; + public stepSize: number = 0.5; + public starStyle: boolean = starStyleMapData.get('Default')!.value as boolean; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'rating': + this.rating = Number(attribute.currentValue) ?? ratingValueMapData.get('Default')!.value as number; + break; + case 'indicator': + this.indicator = attribute.currentValue.toLowerCase() === 'true'; + break; + case 'stars': + this.stars = Number(attribute.currentValue) ?? starsMapData.get('Default')!.value as number; + break; + case 'starStyle': + this.starStyle = attribute.currentValue.toLowerCase() === 'true'; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/rowview/component/RowBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/rowview/component/RowBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..8cfa383431d957da5daae5df1f7bbd3edcfd8083 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/rowview/component/RowBuilder.ets @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 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 { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import { RowAttributeModifier } from '../viewmodel/RowAttributeModifier'; +import type { RowDescriptor } from '../viewmodel/RowDescriptor'; + +@Builder +export function RowBuilder($$: DescriptorWrapper) { + Row({ space: ($$.descriptor as RowDescriptor).space }) { + Column() + .size({ width: $r('app.float.container_size_1'), height: $r('app.float.container_size_1') }) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .borderRadius($r('sys.float.corner_radius_level4')) + Column() + .size({ width: $r('app.float.container_size_2'), height: $r('app.float.container_size_2') }) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .borderRadius($r('sys.float.corner_radius_level4')) + Column() + .size({ width: $r('app.float.container_size_3'), height: $r('app.float.container_size_3') }) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .borderRadius($r('sys.float.corner_radius_level4')) + } + .width(($$.descriptor as RowDescriptor).padding === 'None' ? '80%' : 'auto') + .height(($$.descriptor as RowDescriptor).padding === 'None' ? '50%' : 'auto') + .padding(($$.descriptor as RowDescriptor).padding === 'Vertical' ? + { + top: ($$.descriptor as RowDescriptor).paddingNum, + bottom: ($$.descriptor as RowDescriptor).paddingNum + } : ($$.descriptor as RowDescriptor).padding === 'Horizontal' ? { + left: ($$.descriptor as RowDescriptor).paddingNum, + right: ($$.descriptor as RowDescriptor).paddingNum, + } : ($$.descriptor as RowDescriptor).paddingNum) + .attributeModifier(new RowAttributeModifier($$.descriptor as RowDescriptor)) + .borderRadius($r('sys.float.corner_radius_level6')) + .border({ width: DetailPageConstant.CONTAINER_BORDER, color: $r('sys.color.comp_background_emphasize') }) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/rowview/entity/RowAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/rowview/entity/RowAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..6121bd10b933a52905bd59eb207a2237b5edeb41 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/rowview/entity/RowAttributeMapping.ets @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 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 { CommonNumberMapping, CommonStringMapping } from '../../common/entity/CommonMapData'; + +class RowAlignMapping { + public readonly code: string; + public readonly value: VerticalAlign; + + constructor(code: string, value: VerticalAlign) { + this.code = code; + this.value = value; + } +} + +export const rowAlignMapData: Map = new Map([ + ['Top', new RowAlignMapping('VerticalAlign.Top', VerticalAlign.Top)], + ['Center', new RowAlignMapping('VerticalAlign.Center', VerticalAlign.Center)], + ['Bottom', new RowAlignMapping('VerticalAlign.Bottom', VerticalAlign.Bottom)], + ['Default', new RowAlignMapping('VerticalAlign.Center', VerticalAlign.Center)], +]); + +class RowJustifyContentMapping { + public readonly code: string; + public readonly value: FlexAlign; + + constructor(code: string, value: FlexAlign) { + this.code = code; + this.value = value; + } +} + +export const rowJustifyContentMapData: Map = new Map([ + ['Start', new RowJustifyContentMapping('FlexAlign.Start', FlexAlign.Start)], + ['Center', new RowJustifyContentMapping('FlexAlign.Center', FlexAlign.Center)], + ['End', new RowJustifyContentMapping('FlexAlign.End', FlexAlign.End)], + ['SpaceBetween', new RowJustifyContentMapping('FlexAlign.SpaceBetween', FlexAlign.SpaceBetween)], + ['SpaceAround', new RowJustifyContentMapping('FlexAlign.SpaceAround', FlexAlign.SpaceAround)], + ['SpaceEvenly', new RowJustifyContentMapping('FlexAlign.SpaceEvenly', FlexAlign.SpaceEvenly)], + ['Default', new RowJustifyContentMapping('FlexAlign.Start', FlexAlign.Start)], +]); + +export const rowSpaceMapData: Map = new Map([ + ['Default', new CommonNumberMapping('3', 3)], +]); + +export const rowPaddingMapData: Map = new Map([ + ['Vertical', new CommonStringMapping('Vertical', 'Vertical')], + ['Horizontal', new CommonStringMapping('Horizontal', 'Horizontal')], + ['All', new CommonStringMapping('All', 'All')], + ['Default', new CommonStringMapping('All', 'All')], +]); + +export const paddingNumMapData: Map = new Map([ + ['Default', new CommonNumberMapping('3', 3)], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/rowview/viewmodel/RowAttributeFilter.ets b/features/componentlibrary/src/main/ets/componentdetailview/rowview/viewmodel/RowAttributeFilter.ets new file mode 100644 index 0000000000000000000000000000000000000000..3c7a5ba4018661c82e0bd2d79448acc2103b3325 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/rowview/viewmodel/RowAttributeFilter.ets @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 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 type { ObservedArray } from '@ohos/common'; +import type { Attribute } from '../../../viewmodel/Attribute'; +import { CommonAttributeFilter } from '../../../viewmodel/CommonAttributeFilter'; + +export class RowAttributeFilter implements CommonAttributeFilter { + filter(attributes: ObservedArray): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'padding': + const paddingNumIndex = attributes.findIndex((item) => item.name === 'paddingNum'); + const flexAlignIndex = attributes.findIndex((item) => item.name === 'flexAlign'); + if (paddingNumIndex !== -1 && flexAlignIndex !== -1) { + if (attribute.currentValue === 'None') { + attributes[paddingNumIndex].enable = false; + attributes[flexAlignIndex].enable = true; + } else { + attributes[paddingNumIndex].enable = true; + attributes[flexAlignIndex].enable = false; + } + } + break; + case 'flexAlign': + const index = attributes.findIndex((item) => item.name === 'space'); + if (index !== -1) { + attributes[index].enable = (attribute.currentValue !== 'SpaceBetween'); + } + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/rowview/viewmodel/RowAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/rowview/viewmodel/RowAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..f3c2315bd59a9532c03f1c597571098af256856f --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/rowview/viewmodel/RowAttributeModifier.ets @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 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 { CommonAttributeModifier } from '../../../viewmodel/CommonAttributeModifier'; +import type { RowDescriptor } from './RowDescriptor'; + +@Observed +export class RowAttributeModifier extends CommonAttributeModifier { + applyNormalAttribute(instance: RowAttribute): void { + this.assignAttribute((descriptor => descriptor.alignItems), (val) => instance.alignItems(val)); + this.assignAttribute((descriptor => descriptor.flexAlign), (val) => instance.justifyContent(val)); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/rowview/viewmodel/RowCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/rowview/viewmodel/RowCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..72744f42393d99461cfd62e4660a049661631503 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/rowview/viewmodel/RowCodeGenerator.ets @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { + paddingNumMapData, + rowAlignMapData, + rowJustifyContentMapData, + rowPaddingMapData, + rowSpaceMapData, +} from '../entity/RowAttributeMapping'; + +export class RowCodeGenerator implements CommonCodeGenerator { + private alignItems: string = rowAlignMapData.get('Default')!.code; + private flexAlign: string = rowJustifyContentMapData.get('Default')!.code; + private space: string = rowSpaceMapData.get('Default')!.code; + private padding: string = rowPaddingMapData.get('Default')!.code; + private paddingNum: string = paddingNumMapData.get('Default')!.code; + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'alignItems': + this.alignItems = rowAlignMapData.get(attribute.currentValue)?.code ?? this.alignItems; + break; + case 'flexAlign': + this.flexAlign = rowJustifyContentMapData.get(attribute.currentValue)?.code ?? this.flexAlign; + break; + case 'space': + this.space = attribute.currentValue; + break; + case 'padding': + this.padding = attribute.currentValue; + break; + case 'paddingNum': + this.paddingNum = attribute.currentValue; + break; + default: + break; + } + }); + + let codeStr = ''; + if (this.padding === 'Vertical') { + codeStr = `.padding({ + top: ${this.paddingNum}, + bottom: ${this.paddingNum} + })`; + } else if (this.padding === 'Horizontal') { + codeStr = `.padding({ + left: ${this.paddingNum}, + right: ${this.paddingNum} + })`; + } else { + codeStr = `.padding(${this.paddingNum})`; + } + + return `@Component +struct RowComponent { + build() { + Row({ space: ${this.space} }) { + Column() + .size({ width: 40, height: 40 }) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .borderRadius($r('sys.float.corner_radius_level4')) + Column() + .size({ width: 52, height: 52 }) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .borderRadius($r('sys.float.corner_radius_level4')) + Column() + .size({ width: 64, height: 64 }) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .borderRadius($r('sys.float.corner_radius_level4')) + } + .width('${this.padding === 'None' ? '80%' : 'auto'}') + .height('${this.padding === 'None' ? '50%' : 'auto'}') + ${codeStr} + .alignItems(${this.alignItems}) + .justifyContent(${this.flexAlign}) + .borderRadius($r('sys.float.corner_radius_level6')) + .border({ width: 1, color: $r('sys.color.comp_background_emphasize') }) + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/rowview/viewmodel/RowDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/rowview/viewmodel/RowDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..1c130699c5d03facb1ebe34ee6cd8ee0e3094f50 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/rowview/viewmodel/RowDescriptor.ets @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { + paddingNumMapData, + rowAlignMapData, + rowJustifyContentMapData, + rowPaddingMapData, + rowSpaceMapData, +} from '../entity/RowAttributeMapping'; + +@Observed +export class RowDescriptor extends CommonDescriptor { + public alignItems: VerticalAlign = rowAlignMapData.get('Default')!.value; + public flexAlign: FlexAlign = rowJustifyContentMapData.get('Default')!.value; + public space: number = rowSpaceMapData.get('Default')!.value; + public padding: string = rowPaddingMapData.get('Default')!.value; + public paddingNum: number = paddingNumMapData.get('Default')!.value; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'alignItems': + this.alignItems = rowAlignMapData.get(attribute.currentValue)?.value ?? this.alignItems; + break; + case 'flexAlign': + this.flexAlign = rowJustifyContentMapData.get(attribute.currentValue)?.value ?? this.flexAlign; + break; + case 'space': + this.space = Number(attribute.currentValue); + break; + case 'padding': + this.padding = attribute.currentValue; + break; + case 'paddingNum': + this.paddingNum = Number(attribute.currentValue); + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/stackview/component/StackBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/stackview/component/StackBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..80378c19ec196c7f61a31978ccb3aa9022c5c5c4 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/stackview/component/StackBuilder.ets @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 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 { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import { StackAttributeModifier } from '../viewmodel/StackAttributeModifier'; +import type { StackDescriptor } from '../viewmodel/StackDescriptor'; + +@Builder +export function StackBuilder($$: DescriptorWrapper) { + Stack() { + Column() + .size({ width: $r('app.float.container_size_5'), height: $r('app.float.container_size_5') }) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .borderRadius($r('sys.float.corner_radius_level4')) + Column() + .size({ width: $r('app.float.container_size_3'), height: $r('app.float.container_size_3') }) + .backgroundColor($r('sys.color.multi_color_03')) + .borderRadius($r('sys.float.corner_radius_level4')) + } + .padding($r('sys.float.padding_level3')) + .height($r('app.float.container_height')) + .width($r('app.float.container_width')) + .border({ + width: DetailPageConstant.CONTAINER_BORDER, + color: $r('sys.color.comp_background_emphasize'), + radius: $r('sys.float.corner_radius_level6'), + }) + .attributeModifier(new StackAttributeModifier($$.descriptor as StackDescriptor)) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/stackview/entity/StackAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/stackview/entity/StackAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..df71692bccefb47653bc8c26f1313af55a0e50dd --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/stackview/entity/StackAttributeMapping.ets @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 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. + */ + +class StackAlignMapping { + public readonly code: string; + public readonly value: Alignment; + + constructor(code: string, value: Alignment) { + this.code = code; + this.value = value; + } +} + +export const stackAlignMapData: Map = new Map([ + ['Top', new StackAlignMapping('Alignment.Top', Alignment.Top)], + ['TopStart', new StackAlignMapping('Alignment.TopStart', Alignment.TopStart)], + ['TopEnd', new StackAlignMapping('Alignment.TopEnd', Alignment.TopEnd)], + ['Start', new StackAlignMapping('Alignment.Start', Alignment.Start)], + ['Center', new StackAlignMapping('Alignment.Center', Alignment.Center)], + ['End', new StackAlignMapping('Alignment.End', Alignment.End)], + ['BottomStart', new StackAlignMapping('Alignment.BottomStart', Alignment.BottomStart)], + ['BottomEnd', new StackAlignMapping('Alignment.BottomEnd', Alignment.BottomEnd)], + ['Bottom', new StackAlignMapping('Alignment.Bottom', Alignment.Bottom)], + ['Default', new StackAlignMapping('Alignment.Center', Alignment.Center)], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/stackview/viewmodel/StackAttributeFilter.ets b/features/componentlibrary/src/main/ets/componentdetailview/stackview/viewmodel/StackAttributeFilter.ets new file mode 100644 index 0000000000000000000000000000000000000000..7f1c1c581c12b9e2395b3e0d01454c9802e37b8a --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/stackview/viewmodel/StackAttributeFilter.ets @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 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 type { ObservedArray } from '@ohos/common'; +import type { Attribute } from '../../../viewmodel/Attribute'; +import { CommonAttributeFilter } from '../../../viewmodel/CommonAttributeFilter'; + +export class StackAttributeFilter implements CommonAttributeFilter { + filter(attributes: ObservedArray): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'alignDirection': + const topIndex = attributes.findIndex((item) => item.name === 'alignContentTop'); + const centerIndex = attributes.findIndex((item) => item.name === 'alignContentCenter'); + const bottomIndex = attributes.findIndex((item) => item.name === 'alignContentBottom'); + if (topIndex !== -1 && centerIndex !== -1 && bottomIndex !== -1) { + if (attribute.currentValue === 'Top') { + attributes[topIndex].enable = true; + attributes[centerIndex].enable = false; + attributes[bottomIndex].enable = false; + } else if (attribute.currentValue === 'Center') { + attributes[topIndex].enable = false; + attributes[centerIndex].enable = true; + attributes[bottomIndex].enable = false; + } else if (attribute.currentValue === 'Bottom') { + attributes[topIndex].enable = false; + attributes[centerIndex].enable = false; + attributes[bottomIndex].enable = true; + } + } + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/stackview/viewmodel/StackAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/stackview/viewmodel/StackAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..899e7248ea52c939123dfba7df7a52db306368fc --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/stackview/viewmodel/StackAttributeModifier.ets @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 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 { CommonAttributeModifier } from '../../../viewmodel/CommonAttributeModifier'; +import type { StackDescriptor } from './StackDescriptor'; + +@Observed +export class StackAttributeModifier extends CommonAttributeModifier { + applyNormalAttribute(instance: StackAttribute): void { + this.assignAttribute((descriptor => descriptor.alignContent), (val) => instance.alignContent(val)); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/stackview/viewmodel/StackCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/stackview/viewmodel/StackCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..8f8a6eaad1532e1c03419c94f0bc84ae7f2e6527 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/stackview/viewmodel/StackCodeGenerator.ets @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { stackAlignMapData } from '../entity/StackAttributeMapping'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; + +export class StackCodeGenerator implements CommonCodeGenerator { + private alignContent: string = stackAlignMapData.get('Default')!.code; + + public generate(attributes: OriginAttribute[]): string { + const index: number = attributes.findIndex((item) => item.name === 'alignDirection'); + let alignDirection: string = ''; + if (index >= 0) { + alignDirection = attributes[index].currentValue; + } + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'alignContentTop': + if (alignDirection === 'Top') { + this.alignContent = stackAlignMapData.get(attribute.currentValue)?.code ?? this.alignContent; + } + break; + case 'alignContentCenter': + if (alignDirection === 'Center') { + this.alignContent = stackAlignMapData.get(attribute.currentValue)?.code ?? this.alignContent; + } + break; + case 'alignContentBottom': + if (alignDirection === 'Bottom') { + this.alignContent = stackAlignMapData.get(attribute.currentValue)?.code ?? this.alignContent; + } + break; + default: + break; + } + }); + return `@Component +struct StackComponent { + build() { + Stack({ alignContent: ${this.alignContent} }){ + Column() + .size({ width: 92, height: 92 }) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .borderRadius($r('sys.float.corner_radius_level4')) + Column() + .size({ width: 64, height: 64 }) + .backgroundColor($r('sys.color.multi_color_03')) + .borderRadius($r('sys.float.corner_radius_level4')) + } + .padding($r('sys.float.padding_level3')) + .height('180vp') + .width('262vp') + .border({ + width: '1vp', + color: $r('sys.color.comp_background_emphasize'), + radius: $r('sys.float.corner_radius_level6') + }) + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/stackview/viewmodel/StackDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/stackview/viewmodel/StackDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..0deda4e87d3fa086b23b0605922e4069b0f02677 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/stackview/viewmodel/StackDescriptor.ets @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { stackAlignMapData } from '../entity/StackAttributeMapping'; + +@Observed +export class StackDescriptor extends CommonDescriptor { + public alignContent: Alignment = stackAlignMapData.get('Default')!.value; + + public convert(attributes: OriginAttribute[]): void { + const index: number = attributes.findIndex((item) => item.name === 'alignDirection'); + let alignDirection: string = ''; + if (index >= 0) { + alignDirection = attributes[index].currentValue; + } + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'alignContentTop': + if (alignDirection === 'Top') { + this.alignContent = stackAlignMapData.get(attribute.currentValue)?.value ?? this.alignContent; + } + break; + case 'alignContentCenter': + if (alignDirection === 'Center') { + this.alignContent = stackAlignMapData.get(attribute.currentValue)?.value ?? this.alignContent; + } + break; + case 'alignContentBottom': + if (alignDirection === 'Bottom') { + this.alignContent = stackAlignMapData.get(attribute.currentValue)?.value ?? this.alignContent; + } + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/styletext/component/StyleTextBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/styletext/component/StyleTextBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..92c85966c802a4717c01925bd66a2d9f1dd91679 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/styletext/component/StyleTextBuilder.ets @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apach 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 { LengthMetrics } from '@kit.ArkUI'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import type { StyleTextDescriptor } from '../viewmodel/StyleTextDescriptor'; + +@Builder +export function StyleTextBuilder($$: DescriptorWrapper) { + StyleTextComponent({ styleTextDescriptor: $$.descriptor as StyleTextDescriptor }) +} + +@Component +struct StyleTextComponent { + @Prop @Watch('onStyleUpdated') styleTextDescriptor: StyleTextDescriptor; + private text: string = + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; + textController: TextController = new TextController(); + paragraphStyleAttr: ParagraphStyle = new ParagraphStyle({ + textIndent: LengthMetrics.vp(this.styleTextDescriptor.textIndent), + maxLines: this.styleTextDescriptor.maxLines + }); + mutableStyledString: MutableStyledString = + new MutableStyledString(this.text, + [ + { + start: 0, + length: 3, + styledKey: StyledStringKey.PARAGRAPH_STYLE, + styledValue: this.paragraphStyleAttr + }, + { + start: 10, + length: 5, + styledKey: StyledStringKey.FONT, + styledValue: new TextStyle({ + fontColor: this.styleTextDescriptor.highlightColor + }) + } + ]); + + build() { + Column() { + Text(undefined, { controller: this.textController }) + .margin({ left: $r('app.float.common_left_right_margin'), right: $r('app.float.common_left_right_margin') }) + .fontSize($r('sys.float.Body_L')) + .fontWeight(FontWeight.Regular) + } + .width($r('app.float.multiline_text_width')) + .justifyContent(FlexAlign.Center) + .onAttach(() => { + this.textController.setStyledString(this.mutableStyledString); + }) + } + + onStyleUpdated(_changedPropertyName: string) { + this.mutableStyledString.setStyle({ + start: 0, + length: 3, + styledKey: StyledStringKey.PARAGRAPH_STYLE, + styledValue: new ParagraphStyle({ + textIndent: LengthMetrics.vp(this.styleTextDescriptor.textIndent), + maxLines: this.styleTextDescriptor.maxLines, + overflow: this.styleTextDescriptor.overflow, + }), + }); + this.mutableStyledString.setStyle({ + start: 10, + length: 5, + styledKey: StyledStringKey.FONT, + styledValue: new TextStyle({ + fontColor: this.styleTextDescriptor.highlightColor, + }), + }); + this.textController.setStyledString(this.mutableStyledString); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/styletext/viewmodel/StyleTextCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/styletext/viewmodel/StyleTextCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..6f8a09c71cebb7ab01010aed66a2d0f309aacc42 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/styletext/viewmodel/StyleTextCodeGenerator.ets @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { highlightColorMap } from '../../common/entity/CommonMapData'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { textOverflowTypeMapData } from '../../textarea/entity/TextAreaAttributeMapping'; + +export class StyleTextCodeGenerator implements CommonCodeGenerator { + private textIndent: number = 0; + private maxLines: number = 2; + private overflowStr: string = textOverflowTypeMapData.get('Default')!.code; + private highlightColor: string = highlightColorMap.get('Default')!.code; + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'textIndent': + this.textIndent = Number(attribute.currentValue) as number; + break; + case 'maxLines': + this.maxLines = Number(attribute.currentValue) as number; + break; + case 'overflow': + this.overflowStr = + textOverflowTypeMapData.get(attribute.currentValue)?.code ?? textOverflowTypeMapData.get('Default')!.code; + break; + case 'highlightColor': + this.highlightColor = attribute.currentValue; + break; + default: + break; + } + }); + + return `import { LengthMetrics } from '@kit.ArkUI'; + +@Component +struct StyleTextComponent { + private text: string = + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; + textController: TextController = new TextController(); + paragraphStyleAttr: ParagraphStyle = new ParagraphStyle({ + textIndent: LengthMetrics.vp(${this.textIndent}), + maxLines: ${this.maxLines}, + overflow: ${this.overflowStr} + }); + textIndentStyle: SpanStyle = { + start: 0, + length: 3, + styledKey: StyledStringKey.PARAGRAPH_STYLE, + styledValue: this.paragraphStyleAttr + }; + mutableStyledString: MutableStyledString = + new MutableStyledString(this.text, + [ + { + start: 0, + length: 3, + styledKey: StyledStringKey.PARAGRAPH_STYLE, + styledValue: this.paragraphStyleAttr + }, + { + start: 10, + length: 5, + styledKey: StyledStringKey.FONT, + styledValue: new TextStyle({ + fontColor: '${this.highlightColor}' + }) + } + ]); + + build() { + Column() { + Text(undefined, { controller: this.textController }) { + } + .margin({ left: 36, right: 36 }) + .fontSize($r('sys.float.Body_L')) + .fontWeight(FontWeight.Regular) + } + .width(320) + .justifyContent(FlexAlign.Center) + .onAttach(() => { + this.textController.setStyledString(this.mutableStyledString); + }) + } + }`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/styletext/viewmodel/StyleTextDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/styletext/viewmodel/StyleTextDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..ba1cca512ef67b7143d3592a34ffba35fedac1c2 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/styletext/viewmodel/StyleTextDescriptor.ets @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { highlightColorMap } from '../../common/entity/CommonMapData'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { textOverflowTypeMapData } from '../../textarea/entity/TextAreaAttributeMapping'; + +@Observed +export class StyleTextDescriptor extends CommonDescriptor { + public textIndent: number = 0; + public maxLines: number = 2; + public overflow: TextOverflow = textOverflowTypeMapData.get('Default')!.value; + public highlightColor: string = highlightColorMap.get('Default')!.value; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'textIndent': + this.textIndent = Number(attribute.currentValue); + break; + case 'maxLines': + this.maxLines = Number(attribute.currentValue); + break; + case 'overflow': + this.overflow = + textOverflowTypeMapData.get(attribute.currentValue)?.value ?? textOverflowTypeMapData.get('Default')!.value; + break; + case 'highlightColor': + this.highlightColor = attribute.currentValue; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/swiperView/component/SwiperBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/swiperView/component/SwiperBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..633737708b7689a726cd4347629e654e2ce6dc89 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/swiperView/component/SwiperBuilder.ets @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 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 { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import { SwiperAttributeModifier } from '../viewmodel/SwiperAttributeModifier'; +import type { SwiperDescriptor } from '../viewmodel/SwiperDescriptor'; + +function getSwiperData(): string[] { + const list: string[] = ['1', '2', '3']; + return list; +} + +@Builder +export function SwiperBuilder($$: DescriptorWrapper) { + Column() { + Swiper() { + ForEach(getSwiperData(), (item: string, _index: number) => { + Text(item) + .height($r('app.float.swiper_height')) + .backgroundColor(DetailPageConstant.SWIPER_BACKGROUND_COLOR) + .textAlign(TextAlign.Center) + .fontSize($r('sys.float.Body_L')) + }, (item: string) => item) + } + .width('100%') + .height('100%') + .loop(false) + .attributeModifier(new SwiperAttributeModifier($$.descriptor as SwiperDescriptor)) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + .borderRadius($r('sys.float.corner_radius_level8')) + .clip(true) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/swiperView/entity/SwiperAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/swiperView/entity/SwiperAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..962f24b6432b934e9d473921a492a72bb10b805b --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/swiperView/entity/SwiperAttributeMapping.ets @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 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. + */ + +type IndicatorType = DotIndicator | DigitIndicator | boolean; + +class IndicatorMap { + public readonly code: string; + public readonly value: IndicatorType; + + constructor(code: string, value: IndicatorType) { + this.code = code; + this.value = value; + } +} + +const dotIndicatorCode: string = `new DotIndicator() + .itemWidth(6) + .itemHeight(6) + .selectedItemWidth(12) + .selectedItemHeight(6) + .color($r('sys.color.comp_background_secondary')) + .selectedColor($r('sys.color.comp_background_emphasize'))`; + +const digitIndicatorCode: string = `new DigitIndicator() + .fontColor($r('sys.color.font_primary')) + .selectedFontColor($r('sys.color.font_primary')) + .digitFont({ size: 16, weight: FontWeight.Bold })`; + +export const indicatorStyleMapData: Map = new Map([ + ['DotIndicator', new IndicatorMap(dotIndicatorCode, new DotIndicator().itemWidth(6) + .itemHeight(6) + .selectedItemWidth(12) + .selectedItemHeight(6) + .color($r('sys.color.comp_background_secondary')) + .selectedColor($r('sys.color.comp_background_emphasize')))], + ['DigitIndicator', new IndicatorMap(digitIndicatorCode, new DigitIndicator().fontColor($r('sys.color.font_primary')) + .selectedFontColor($r('sys.color.font_primary')) + .digitFont({ size: 16, weight: FontWeight.Bold }))], + ['None', new IndicatorMap('false', false)], + ['Default', new IndicatorMap(dotIndicatorCode, new DotIndicator().itemWidth(6) + .itemHeight(6) + .selectedItemWidth(12) + .selectedItemHeight(6) + .color($r('sys.color.comp_background_secondary')) + .selectedColor($r('sys.color.comp_background_emphasize')))], +]); + +export class IndicatorEffectMap { + public readonly code: string; + public readonly value: EdgeEffect; + + constructor(code: string, value: EdgeEffect) { + this.code = code; + this.value = value; + } +} + +export const indicatorEffectMapping: Map = new Map([ + ['Default', new IndicatorEffectMap('EdgeEffect.Spring', EdgeEffect.Spring)], + ['Spring', new IndicatorEffectMap('EdgeEffect.Spring', EdgeEffect.Spring)], + ['Fade', new IndicatorEffectMap('EdgeEffect.Fade', EdgeEffect.Fade)], + ['None', new IndicatorEffectMap('EdgeEffect.None', EdgeEffect.None)], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/swiperView/viewmodel/SwiperAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/swiperView/viewmodel/SwiperAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..3007e4fe5177c99a9b04329a11d6947166b489cb --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/swiperView/viewmodel/SwiperAttributeModifier.ets @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 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 { CommonAttributeModifier } from '../../../viewmodel/CommonAttributeModifier'; +import type { SwiperDescriptor } from './SwiperDescriptor'; + +@Observed +export class SwiperAttributeModifier extends CommonAttributeModifier { + applyNormalAttribute(instance: SwiperAttribute): void { + this.assignAttribute((descriptor => descriptor.indicator), (val) => instance.indicator(val)); + this.assignAttribute((descriptor => descriptor.vertical), (val) => instance.vertical(val)); + this.assignAttribute((descriptor => descriptor.effectMode), (val) => instance.effectMode(val)); + this.assignAttribute((descriptor => descriptor.displayArrow), (val) => instance.displayArrow(val)); + this.assignAttribute((descriptor => descriptor.loop), (val) => instance.autoPlay(val)); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/swiperView/viewmodel/SwiperCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/swiperView/viewmodel/SwiperCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..6be7f21a066e8e8c11a3bcf15f04c2e15f14bcc9 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/swiperView/viewmodel/SwiperCodeGenerator.ets @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { indicatorEffectMapping, indicatorStyleMapData } from '../entity/SwiperAttributeMapping'; + +export class SwiperCodeGenerator implements CommonCodeGenerator { + private isDisplayArrow: boolean = true; + private displayArrow: string = ''; + private indicatorType: string = 'DotIndicator'; + private indicatorCode: string = indicatorStyleMapData.get('Default')!.code; + private isVertical: boolean = true; + private effectMode: string = indicatorEffectMapping.get('Default')!.code; + private loop: boolean = true; + + public generate(attributes: OriginAttribute[]): string { + let code = `Swiper() { + ForEach(this.list, (item: string,index: number) => { + Text(item.toString()) + .width('90%') + .height("80%") + .height(160) + .backgroundColor(0xAFEEEE) + .textAlign(TextAlign.Center) + .fontSize(30) + }, (item: string) => item) + } + .loop(${this.loop})\n`; + + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'indicator': + this.indicatorType = attribute.currentValue as string; + this.indicatorCode = ` .indicator(${indicatorStyleMapData.get(this.indicatorType)?.code})`; + break; + case 'vertical': + this.isVertical = JSON.parse(attribute.currentValue); + code += ` .vertical(${this.isVertical ? 'true' : 'false'})\n`; + break; + case 'effectMode': + this.effectMode = attribute.currentValue as string; + if (this.effectMode === 'Spring') { + code += ` .effectMode(EdgeEffect.Spring)`; + } else if (this.effectMode === 'Fade') { + code += ` .effectMode(EdgeEffect.Fade)`; + } else if (this.effectMode === 'None') { + code += ` .effectMode(EdgeEffect.None)`; + } + break; + case 'isDisplayArrow': + this.isDisplayArrow = JSON.parse(attribute.currentValue); + if (this.isDisplayArrow) { + this.displayArrow = ` .displayArrow({ + showBackground: true, + isSidebarMiddle: true, + backgroundSize: 24, + backgroundColor: Color.White, + arrowSize: 18, + arrowColor: Color.Blue +})\n`; + } else { + this.displayArrow = ' .displayArrow(false)'; + } + break; + case 'loop': + this.loop = JSON.parse(attribute.currentValue); + break; + default: + break; + } + }); + return `function getSwiperData(): number[] { + const list: number[] = []; + for (let i = 1; i <= 3; i++) { + list.push(i); + } + return list; +} + +@Component +struct SwiperComponent { + @State list: number[] = getSwiperData(); + + build() { + ${code}\n${this.indicatorCode}\n${this.displayArrow} + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/swiperView/viewmodel/SwiperDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/swiperView/viewmodel/SwiperDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..8e26afe322eee1c4c71bf1193e61601c4a1d3ff1 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/swiperView/viewmodel/SwiperDescriptor.ets @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { indicatorEffectMapping, indicatorStyleMapData } from '../entity/SwiperAttributeMapping'; + +@Observed +export class SwiperDescriptor extends CommonDescriptor { + public indicator: DotIndicator | DigitIndicator | boolean = indicatorStyleMapData.get('Default')!.value; + public vertical: boolean = false; + public effectMode: EdgeEffect = indicatorEffectMapping.get('Default')!.value; + public isDisplayArrow: boolean = true; + public displayArrow: ArrowStyle | boolean = false; + public loop: boolean = true; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'indicator': + this.indicator = indicatorStyleMapData.get(attribute.currentValue)?.value ?? this.indicator; + break; + case 'vertical': + this.vertical = JSON.parse(attribute.currentValue); + break; + case 'effectMode': + this.effectMode = indicatorEffectMapping.get(attribute.currentValue)?.value ?? this.effectMode; + break; + case 'isDisplayArrow': + this.isDisplayArrow = JSON.parse(attribute.currentValue); + if (this.isDisplayArrow) { + this.displayArrow = { + showBackground: true, + isSidebarMiddle: true, + backgroundSize: 24, + backgroundColor: Color.White, + arrowSize: 18, + arrowColor: Color.Blue + }; + } else { + this.displayArrow = false; + } + break; + case 'loop': + this.loop = JSON.parse(attribute.currentValue); + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/tabview/component/TabBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/tabview/component/TabBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..45040bbfb3b16b2ad859b6c44bcc0b3545ff9145 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/tabview/component/TabBuilder.ets @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2024 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 type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import { TabAttributeModifier } from '../viewmodel/TabAttributeModifier'; +import type { TabDescriptor } from '../viewmodel/TabDescriptor'; + +@Builder +export function TabBuilder($$: DescriptorWrapper) { + TabComponent({ preview: $$.descriptor as TabDescriptor }) +} + +@Component +struct TabComponent { + @Prop preview: TabDescriptor; + @State currentIndex: number = 0; + + @Builder + tabBuilder(title: ResourceStr, targetIndex: number) { + Column() { + Text(title) + .fontColor(this.currentIndex === targetIndex ? $r('sys.color.font_emphasize') : $r('sys.color.font_primary')) + .fontWeight(FontWeight.Regular) + .fontSize($r('sys.float.Caption_M')) + } + .width($r('app.float.tab_bar_width')) + .height($r('app.float.tab_bar_height')) + .justifyContent(FlexAlign.Center) + } + + @Builder + contentBuilder(text: string) { + Column() { + if (text === 'circle') { + Circle() + .size({ width: $r('app.float.one_hundred_size'), height: $r('app.float.one_hundred_size') }) + .fill($r('sys.color.icon_emphasize')) + } + if (text === 'square') { + Rect() + .size({ width: $r('app.float.one_hundred_size'), height: $r('app.float.one_hundred_size') }) + .fill($r('sys.color.icon_emphasize')) + } + if (text === 'triangle') { + Polygon() + .size({ width: $r('app.float.one_hundred_size'), height: $r('app.float.one_hundred_size') }) + .points([[0, 100], [50, 0], [100, 100]]) + .fill($r('sys.color.icon_emphasize')) + } + if (text === 'rectangle') { + Rect() + .size({ width: $r('app.float.one_hundred_twenty_size'), height: $r('app.float.eighty_size') }) + .fill($r('sys.color.icon_emphasize')) + } + if (text === 'elliptical') { + Ellipse() + .size({ width: $r('app.float.one_hundred_size'), height: $r('app.float.eighty_size') }) + .fill($r('sys.color.icon_emphasize')) + } + if (text === 'trapezium') { + Polygon() + .size({ width: $r('app.float.one_hundred_twenty_size'), height: $r('app.float.eighty_size') }) + .points([[0, 80], [20, 0], [100, 0], [120, 80]]) + .fill($r('sys.color.icon_emphasize')) + } + if (text === 'lozenge') { + Polygon() + .size({ width: $r('app.float.one_hundred_size'), height: $r('app.float.one_hundred_size') }) + .points([[0, 50], [50, 0], [100, 50], [50, 100]]) + .fill($r('sys.color.icon_emphasize')) + } + if (text === 'hexagon') { + Polygon() + .size({ width: $r('app.float.one_hundred_size'), height: $r('app.float.one_hundred_size') }) + .points([[50, 0], [93.3, 25], [93.3, 75], [50, 100], [6.7, 75], [6.7, 25]]) + .fill($r('sys.color.icon_emphasize')) + } + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + .borderRadius($r('sys.float.corner_radius_level4')) + } + + build() { + Column() { + Tabs({ controller: this.preview.controller, barPosition: this.preview.barPosition }) { + TabContent() { + this.contentBuilder('circle') + }.tabBar(this.tabBuilder($r('app.string.circle'), 0)) + + TabContent() { + this.contentBuilder('square') + }.tabBar(this.tabBuilder($r('app.string.square'), 1)) + + TabContent() { + this.contentBuilder('triangle') + }.tabBar(this.tabBuilder($r('app.string.triangle'), 2)) + + TabContent() { + this.contentBuilder('rectangle') + }.tabBar(this.tabBuilder($r('app.string.rectangle'), 3)) + + TabContent() { + this.contentBuilder('elliptical') + }.tabBar(this.tabBuilder($r('app.string.elliptical'), 4)) + + TabContent() { + this.contentBuilder('trapezium') + }.tabBar(this.tabBuilder($r('app.string.trapezium'), 5)) + + TabContent() { + this.contentBuilder('lozenge') + }.tabBar(this.tabBuilder($r('app.string.lozenge'), 6)) + + TabContent() { + this.contentBuilder('hexagon') + }.tabBar(this.tabBuilder($r('app.string.hexagon'), 7)) + } + .attributeModifier(new TabAttributeModifier(this.preview)) + .backgroundColor($r('sys.color.comp_background_secondary')) + .barMode(BarMode.Scrollable) + .divider({ strokeWidth: '1px', color: $r('sys.color.comp_divider') }) + .onChange((index: number) => { + this.currentIndex = index; + }) + } + .padding($r('sys.float.padding_level8')) + .justifyContent(FlexAlign.Center) + .backgroundImage($r('app.media.image_background')) + .backgroundImageSize({ width: '100%', height: '100%' }) + .borderRadius($r('sys.float.corner_radius_level8')) + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/tabview/entity/TabAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/tabview/entity/TabAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..90ae056e7a927dd3e754428239a2284b0b9858ed --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/tabview/entity/TabAttributeMapping.ets @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 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 { CommonBoolMapping, CommonStringMapping } from '../../common/entity/CommonMapData'; + +type TabsValue = BarPosition | BlurStyle; + +class TabsMapping { + public code: string; + public value: TabsValue; + + constructor(code: string, value: TabsValue) { + this.code = code; + this.value = value; + } +} + +export const barPositionMapData: Map = new Map([ + ['Default', new TabsMapping('BarPosition.Start', BarPosition.Start)], + ['Start', new TabsMapping('BarPosition.Start', BarPosition.Start)], + ['End', new TabsMapping('BarPosition.End', BarPosition.End)], +]); + +export const blurStyleMapData: Map = new Map([ + ['Default', new TabsMapping('BlurStyle.NONE', BlurStyle.NONE)], + ['None', new TabsMapping('BlurStyle.NONE', BlurStyle.NONE)], + ['Thin', new TabsMapping('BlurStyle.Thin', BlurStyle.Thin)], + ['Regular', new TabsMapping('BlurStyle.Regular', BlurStyle.Regular)], + ['Thick', new TabsMapping('BlurStyle.Thick', BlurStyle.Thick)], + ['BackgroundThin', new TabsMapping('BlurStyle.BACKGROUND_THIN', BlurStyle.BACKGROUND_THIN)], + ['BackgroundRegular', new TabsMapping('BlurStyle.BACKGROUND_REGULAR', BlurStyle.BACKGROUND_REGULAR)], + ['BackgroundThick', new TabsMapping('BlurStyle.BACKGROUND_THICK', BlurStyle.BACKGROUND_THICK)], + ['BackgroundUltraThick', new TabsMapping('BlurStyle.BACKGROUND_ULTRA_THICK', BlurStyle.BACKGROUND_ULTRA_THICK)], + ['ComponentThin', new TabsMapping('BlurStyle.COMPONENT_THIN', BlurStyle.COMPONENT_THIN)], + ['ComponentUltraThin', new TabsMapping('BlurStyle.COMPONENT_ULTRA_THIN', BlurStyle.COMPONENT_ULTRA_THIN)], + ['ComponentRegular', new TabsMapping('BlurStyle.COMPONENT_REGULAR', BlurStyle.COMPONENT_REGULAR)], + ['ComponentUltraThick', new TabsMapping('BlurStyle.COMPONENT_ULTRA_THICK', BlurStyle.COMPONENT_ULTRA_THICK)], + ['ComponentThick', new TabsMapping('BlurStyle.COMPONENT_THICK', BlurStyle.COMPONENT_THICK)], +]); + +export const fadingEdgeMapData: Map = new Map([ + ['Default', new CommonBoolMapping('true', true)], +]); + +export const verticalMapData: Map = new Map([ + ['Default', new CommonBoolMapping('false', false)], +]); + +export const barWidthMapData: Map = new Map([ + ['Default', new CommonStringMapping('56vp', '56vp')], +]); + +export const barHeightMapData: Map = new Map([ + ['Default', new CommonStringMapping('30vp', '30vp')], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/tabview/viewmodel/TabAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/tabview/viewmodel/TabAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..2f96db0d9f552a11ec08dfd64a696ba6224a87db --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/tabview/viewmodel/TabAttributeModifier.ets @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 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 { CommonAttributeModifier } from '../../../viewmodel/CommonAttributeModifier'; +import type { TabDescriptor } from './TabDescriptor'; + +@Observed +export class TabAttributeModifier extends CommonAttributeModifier { + applyNormalAttribute(instance: TabsAttribute): void { + this.assignAttribute((descriptor => descriptor.vertical), (val) => instance.vertical(val)); + this.assignAttribute((descriptor => descriptor.barWidth), (val) => instance.barWidth(val)); + this.assignAttribute((descriptor => descriptor.barHeight), (val) => instance.barHeight(val)); + this.assignAttribute((descriptor => descriptor.fadingEdge), (val) => instance.fadingEdge(val)); + this.assignAttribute((descriptor => descriptor.backgroundBlurStyle), (val) => instance.backgroundBlurStyle(val)); + this.assignAttribute((descriptor => descriptor.barPosition), (val) => instance.barPosition(val)); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/tabview/viewmodel/TabCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/tabview/viewmodel/TabCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..60ca7659d980ee349bfb68cbc1ea000c3426c5ef --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/tabview/viewmodel/TabCodeGenerator.ets @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { + barHeightMapData, + barPositionMapData, + barWidthMapData, + blurStyleMapData, + fadingEdgeMapData, + verticalMapData, +} from '../entity/TabAttributeMapping'; + +export class TabCodeGenerator implements CommonCodeGenerator { + private barPosition: string = barPositionMapData.get('Default')!.code; + private vertical: string = verticalMapData.get('Default')!.code; + private barWidth: string = barWidthMapData.get('Default')!.code; + private barHeight: string = barHeightMapData.get('Default')!.code; + private backgroundBlurStyle: string = blurStyleMapData.get('Default')!.code; + private fadingEdge: string = fadingEdgeMapData.get('Default')!.code; + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'barPosition': + this.barPosition = + barPositionMapData.get(attribute.currentValue)?.code ?? barPositionMapData.get('Default')!.code; + break; + case 'vertical': + this.vertical = attribute.currentValue; + if (this.vertical === 'true') { + this.barWidth = '64vp'; + this.barHeight = '100%'; + } else { + this.barWidth = '100%'; + this.barHeight = '30vp'; + } + break; + case 'backgroundBlurStyle': + this.backgroundBlurStyle = + blurStyleMapData.get(attribute.currentValue)?.code ?? blurStyleMapData.get('Default')!.code; + break; + case 'fadingEdge': + this.fadingEdge = attribute.currentValue; + break; + default: + break; + } + }); + return `@Component +struct TabComponent { + @State currentIndex: number = 0; + private controller: TabsController = new TabsController(); + + @Builder + tabBuilder(title: string, targetIndex: number) { + Column() { + Text(title) + .fontColor(this.currentIndex === targetIndex ? $r('sys.color.font_emphasize') : $r('sys.color.font_primary')) + .fontWeight(FontWeight.Regular) + .fontSize($r('sys.float.Caption_M')) + } + .width(72) + .height(30) + .justifyContent(FlexAlign.Center) + } + + @Builder + contentBuilder(text: string) { + Column() { + if (text === 'circle') { + Circle().size({ width: 100, height: 100 }).fill($r('sys.color.icon_emphasize')) + } + if (text === 'square') { + Rect().size({ width: 100, height: 100 }).fill($r('sys.color.icon_emphasize')) + } + if (text === 'triangle') { + Polygon({ width: 100, height: 100 }) + .points([[0, 100], [50, 0], [100, 100]]) + .fill($r('sys.color.icon_emphasize')) + } + if (text === 'rectangle') { + Rect().size({ width: 120, height: 80 }).fill($r('sys.color.icon_emphasize')) + } + if (text === 'elliptical') { + Ellipse().size({ width: 100, height: 80 }).fill($r('sys.color.icon_emphasize')) + } + if (text === 'trapezium') { + Polygon({ width: 120, height: 80 }) + .points([[0, 80], [20, 0], [100, 0], [120, 80]]) + .fill($r('sys.color.icon_emphasize')) + } + if (text === 'lozenge') { + Polygon({ width: 100, height: 100 }) + .points([[0, 50], [50, 0], [100, 50], [50, 100]]) + .fill($r('sys.color.icon_emphasize')) + } + if (text === 'hexagon') { + Polygon({ width: 100, height: 100 }) + .points([[50, 0], [93.3, 25], [93.3, 75], [50, 100], [6.7, 75], [6.7, 25]]) + .fill($r('sys.color.icon_emphasize')) + } + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + .borderRadius($r('sys.float.corner_radius_level4')) + } + + build() { + Column() { + Tabs({ controller: this.controller }) { + TabContent() { + this.contentBuilder('circle') + }.tabBar(this.tabBuilder('圆形', 0)) + + TabContent() { + this.contentBuilder('square') + }.tabBar(this.tabBuilder('正方形', 1)) + + TabContent() { + this.contentBuilder('triangle') + }.tabBar(this.tabBuilder('三角形', 2)) + + TabContent() { + this.contentBuilder('rectangle') + }.tabBar(this.tabBuilder('长方形', 3)) + + TabContent() { + this.contentBuilder('elliptical') + }.tabBar(this.tabBuilder('椭圆', 4)) + + TabContent() { + this.contentBuilder('trapezium') + }.tabBar(this.tabBuilder('梯形', 5)) + + TabContent() { + this.contentBuilder('lozenge') + }.tabBar(this.tabBuilder('菱形', 6)) + + TabContent() { + this.contentBuilder('hexagon') + }.tabBar(this.tabBuilder('六边形', 7)) + } + .barWidth('${this.barWidth}') + .barHeight('${this.barHeight}') + .vertical(${this.vertical}) + .barPosition(${this.barPosition}) + .backgroundBlurStyle(${this.backgroundBlurStyle}) + .fadingEdge(${this.fadingEdge}) + .barMode(BarMode.Scrollable) + .backgroundColor($r('sys.color.comp_background_secondary')) + .divider({ strokeWidth: '1px', color: $r('sys.color.comp_divider') }) + .onChange((index: number) => { + this.currentIndex = index; + }) + } + .padding($r('sys.float.padding_level8')) + .justifyContent(FlexAlign.Center) + .backgroundImage($r('app.media.image_background')) // 替换自己项目图片资源文件 + .backgroundImageSize({ width: '100%', height: '100%' }) + .borderRadius($r('sys.float.corner_radius_level8')) + .height('100%') + .width('100%') + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/tabview/viewmodel/TabDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/tabview/viewmodel/TabDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..4565bc81b24fd0be9b8e86236a857d4398f76672 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/tabview/viewmodel/TabDescriptor.ets @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { + barHeightMapData, + barPositionMapData, + barWidthMapData, + blurStyleMapData, + fadingEdgeMapData, + verticalMapData, +} from '../entity/TabAttributeMapping'; + +@Observed +export class TabDescriptor extends CommonDescriptor { + public controller: TabsController = new TabsController(); + public barPosition: BarPosition = barPositionMapData.get('Default')!.value as BarPosition; + public vertical: boolean = verticalMapData.get('Default')!.value as boolean; + public barWidth: string = barWidthMapData.get('Default')!.value as string; + public barHeight: string = barHeightMapData.get('Default')!.value as string; + public backgroundBlurStyle: BlurStyle = blurStyleMapData.get('Default')!.value as BlurStyle; + public fadingEdge: boolean = fadingEdgeMapData.get('Default')!.value as boolean; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'barPosition': + this.barPosition = barPositionMapData.get(attribute.currentValue)?.value as BarPosition ?? + barPositionMapData.get('Default')!.value as BarPosition; + break; + case 'vertical': + this.vertical = JSON.parse(attribute.currentValue) ?? verticalMapData.get('Default')!.value as boolean; + if (this.vertical) { + this.barWidth = '64vp'; + this.barHeight = '100%'; + } else { + this.barWidth = '100%'; + this.barHeight = '30'; + } + break; + case 'backgroundBlurStyle': + this.backgroundBlurStyle = blurStyleMapData.get(attribute.currentValue)?.value as BlurStyle ?? + blurStyleMapData.get('Default')!.value as BlurStyle; + break; + case 'fadingEdge': + this.fadingEdge = JSON.parse(attribute.currentValue) ?? fadingEdgeMapData.get('Default')!.value as boolean; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textarea/component/TextAreaBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/textarea/component/TextAreaBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..612734940e64757dec3298c1264c39007f827667 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textarea/component/TextAreaBuilder.ets @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024 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 { window } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { GlobalInfoModel, Logger } from '@ohos/common'; +import { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import type { TextAreaDescriptor } from '../viewmodel/TextAreaDescriptor'; +import { TextAreaAttributeModifier } from '../viewmodel/TextAreaAttributeModifier'; + +const TAG: string = '[TextAreaBuilder]'; + +@Builder +export function TextAreaBuilder($$: DescriptorWrapper) { + TextAreaComponent({ textAreaDescriptor: $$.descriptor as TextAreaDescriptor }) +} + +@Component +struct TextAreaComponent { + @Prop textAreaDescriptor: TextAreaDescriptor; + // Height of the component preview area. + @State textAreaInputHeight: number = 0; + @State contentOffset: number = 0; + + aboutToAppear(): void { + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel') || new GlobalInfoModel(); + window.getLastWindow(getContext(this)).then(currentWindow => { + // Monitor the appearance and disappearance of the soft keyboard. + try { + currentWindow.on('avoidAreaChange', async data => { + if (data.type !== window.AvoidAreaType.TYPE_KEYBOARD) { + return; + } + const keyboardHeight: number = px2vp(data.area.bottomRect.height); + /* When the size of the component preview area exceeds half the screen, the keyboard + will cover the preview area. At this time, the component needs to move up when the keyboard appears. + */ + if (keyboardHeight > 0 && this.textAreaInputHeight / globalInfoModel.deviceHeight > 0.5) { + this.contentOffset = keyboardHeight / 2; + } else { + this.contentOffset = 0; + } + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `CurrentWindow invoke error, the code is ${error.message}, the message is ${error.message}`); + } + }); + } + + build() { + Column() { + TextArea({ + text: $r('app.string.textarea_text'), + placeholder: $r('app.string.text_placeholder'), + }) + .margin({ + left: $r('app.float.common_left_right_margin'), + right: $r('app.float.common_left_right_margin'), + bottom: this.contentOffset, + }) + .fontWeight(FontWeight.Regular) + .fontSize($r('sys.float.Body_L')) + .enterKeyType(EnterKeyType.Done) + .borderRadius($r('sys.float.corner_radius_level8')) + .backgroundColor($r('sys.color.comp_background_tertiary')) + .maxLines(this.textAreaDescriptor.maxLines) + .attributeModifier(new TextAreaAttributeModifier(this.textAreaDescriptor)) + .animation({ + duration: DetailPageConstant.UP_DURATION, + curve: Curve.Linear, + playMode: PlayMode.Normal, + }) + } + .height('100%') + .width($r('app.float.multiline_text_width')) + .justifyContent(FlexAlign.Center) + .onAreaChange((_: Area, newArea: Area) => { + this.textAreaInputHeight = Number(newArea.height); + }) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textarea/entity/TextAreaAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/textarea/entity/TextAreaAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..1c0836054ee12be55c76812aa04eeac7774dd1d6 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textarea/entity/TextAreaAttributeMapping.ets @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 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. + */ + +class TextOverflowTypeMap { + public readonly code: string; + public readonly value: TextOverflow; + + constructor(code: string, value: TextOverflow) { + this.code = code; + this.value = value; + } +} + +export const textOverflowTypeMapData: Map = new Map([ + ['None', new TextOverflowTypeMap('TextOverflow.None', TextOverflow.None)], + ['Clip', new TextOverflowTypeMap('TextOverflow.Clip', TextOverflow.Clip)], + ['Ellipsis', new TextOverflowTypeMap('TextOverflow.Ellipsis', TextOverflow.Ellipsis)], + ['Marquee', new TextOverflowTypeMap('TextOverflow.MARQUEE', TextOverflow.MARQUEE)], + ['Default', new TextOverflowTypeMap('TextOverflow.Clip', TextOverflow.Clip)], +]); + +class TextAlignTypeMap { + public readonly code: string; + public readonly value: TextAlign; + + constructor(code: string, value: TextAlign) { + this.code = code; + this.value = value; + } +} + +export const textAlignTypeMapData: Map = new Map([ + ['Start', new TextAlignTypeMap('TextAlign.Start', TextAlign.Start)], + ['Center', new TextAlignTypeMap('TextAlign.Center', TextAlign.Center)], + ['End', new TextAlignTypeMap('TextAlign.End', TextAlign.End)], + ['Justify', new TextAlignTypeMap('TextAlign.JUSTIFY', TextAlign.JUSTIFY)], + ['Default', new TextAlignTypeMap('TextAlign.Start', TextAlign.Start)], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textarea/viewmodel/TextAreaAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/textarea/viewmodel/TextAreaAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..833e49b7af5ac8082b30e5d45ec445869e7fa361 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textarea/viewmodel/TextAreaAttributeModifier.ets @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 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 { LengthMetrics } from '@kit.ArkUI'; +import { CommonAttributeModifier } from '../../../viewmodel/CommonAttributeModifier'; +import type { TextAreaDescriptor } from './TextAreaDescriptor'; + +@Observed +export class TextAreaAttributeModifier extends CommonAttributeModifier { + applyNormalAttribute(instance: TextAreaAttribute): void { + this.assignAttribute((descriptor => descriptor.lineSpacing), (val) => instance.lineSpacing(LengthMetrics.vp(val))); + this.assignAttribute((descriptor => descriptor.textOverflowTypeData), (val) => instance.textOverflow(val)); + this.assignAttribute((descriptor => descriptor.textAlign), (val) => instance.textAlign(val)); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textarea/viewmodel/TextAreaCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/textarea/viewmodel/TextAreaCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..db1f940cc58f86e97ea211d5da761c5459cea068 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textarea/viewmodel/TextAreaCodeGenerator.ets @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { textAlignTypeMapData, textOverflowTypeMapData } from '../entity/TextAreaAttributeMapping'; + +export class TextAreaCodeGenerator implements CommonCodeGenerator { + private maxLines: number = 2; + private lineSpacing: number = 5; + private textOverflowTypeStr: string = textOverflowTypeMapData.get('Default')!.code; + private textAlignStr: string = textAlignTypeMapData.get('Default')!.code; + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'maxLines': + this.maxLines = Number(attribute.currentValue); + break; + case 'lineSpacing': + this.lineSpacing = Number(attribute.currentValue); + break; + case 'textOverflowType': + this.textOverflowTypeStr = + textOverflowTypeMapData.get(attribute.currentValue)?.code ?? textOverflowTypeMapData.get('Default')!.code; + break; + case 'textAlign': + this.textAlignStr = + textAlignTypeMapData.get(attribute.currentValue)?.code ?? textAlignTypeMapData.get('Default')!.code; + break; + default: + break; + } + }); + return `import { LengthMetrics } from '@kit.ArkUI'; + +@Component +struct TextAreaComponent { + build() { + Column(){ + TextArea({ + text: '我有一只可爱的玩具熊。它长得很胖,胖得肚子都要爆炸了!它穿的衣服都很漂亮,而且衣服上五颜六色,有紫的、有粉的、还有黄的,非常漂亮!', + placeholder: '请输入文字' + }) + .margin({ left:36, right:36 }) + .fontWeight(FontWeight.Regular) + .fontSize($r('sys.float.Body_L')) + .enterKeyType(EnterKeyType.Done) + .borderRadius($r('sys.float.corner_radius_level8')) + .backgroundColor($r('sys.color.comp_background_tertiary')) + .maxLines(${this.maxLines}) + .lineSpacing(LengthMetrics.px(${this.lineSpacing})) + .textOverflow(${this.textOverflowTypeStr}) + .textAlign(${this.textAlignStr}) + } + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textarea/viewmodel/TextAreaDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/textarea/viewmodel/TextAreaDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..9da10e394efd7105b4c4bcc469b45dee357ac4ef --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textarea/viewmodel/TextAreaDescriptor.ets @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { textAlignTypeMapData, textOverflowTypeMapData } from '../entity/TextAreaAttributeMapping'; + +@Observed +export class TextAreaDescriptor extends CommonDescriptor { + public maxLines: number = 2; + public lineSpacing: number = 5; + public textOverflowTypeData: TextOverflow = textOverflowTypeMapData.get('Default')!.value; + public textAlign: TextAlign = textAlignTypeMapData.get('Default')!.value; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'maxLines': + this.maxLines = Number(attribute.currentValue); + break; + case 'lineSpacing': + this.lineSpacing = Number(attribute.currentValue); + break; + case 'textOverflowType': + this.textOverflowTypeData = + textOverflowTypeMapData.get(attribute.currentValue)?.value ?? textOverflowTypeMapData.get('Default')!.value; + break; + case 'textAlign': + this.textAlign = + textAlignTypeMapData.get(attribute.currentValue)?.value ?? textAlignTypeMapData.get('Default')!.value; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textinput/component/TextInputBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/textinput/component/TextInputBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..0b63431dd3b5590f3aaa3c9ba114bb4d23f844eb --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textinput/component/TextInputBuilder.ets @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2024 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 { window } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { GlobalInfoModel, Logger } from '@ohos/common'; +import { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import { ComponentDetailManager } from '../../../viewmodel/ComponentDetailManager'; +import { AttributeChangeEnable } from '../../../viewmodel/ComponentDetailPageVM'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import { TextInputAttributeModifier } from '../viewmodel/TextInputAttributeModifier'; +import type { TextInputDescriptor } from '../viewmodel/TextInputDescriptor'; + +const TAG: string = '[TextInputBuilder]'; + +@Builder +export function TextInputBuilder($$: DescriptorWrapper) { + TextInputComponent({ textInputComponentDescriptor: $$.descriptor as TextInputDescriptor }) +} + +@Component +struct TextInputComponent { + @Prop textInputComponentDescriptor: TextInputDescriptor; + @State isText: boolean = false; + @State textInputHeight: number = 0; + @State contentOffset: number = 0; + + aboutToAppear(): void { + ComponentDetailManager.getInstance() + .getDetailViewModel('TextInput')?.sendEvent(new AttributeChangeEnable('fontColor', this.isText)); + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel') || new GlobalInfoModel(); + let keyboardHeight: number = 0; + + window.getLastWindow(getContext(this)).then(currentWindow => { + // Monitor the appearance and disappearance of the soft keyboard. + try { + currentWindow.on('avoidAreaChange', async data => { + if (data.type !== window.AvoidAreaType.TYPE_KEYBOARD) { + return; + } + keyboardHeight = px2vp(data.area.bottomRect.height); + /* When the size of the component preview area exceeds half the screen, the keyboard + will cover the preview area. At this time, the component needs to move up when the keyboard appears. + */ + if (keyboardHeight > 0 && this.textInputHeight / globalInfoModel.deviceHeight > 0.5) { + this.contentOffset = keyboardHeight / 2; + } else { + this.contentOffset = 0; + } + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `CurrentWindow invoke error, the code is ${error.message}, the message is ${error.message}`); + } + }); + } + + handleAttributeChange() { + ComponentDetailManager.getInstance() + .getDetailViewModel('TextInput')?.sendEvent(new AttributeChangeEnable('fontColor', !this.isText)); + ComponentDetailManager.getInstance() + .getDetailViewModel('TextInput')?.sendEvent(new AttributeChangeEnable('placeholderFont', this.isText)); + this.isText = !this.isText; + } + + build() { + Column() { + TextInput({ placeholder: $r('app.string.text_placeholder') }) + .margin({ + left: $r('app.float.common_left_right_margin'), + right: $r('app.float.common_left_right_margin'), + bottom: this.contentOffset, + }) + .height($r('app.float.common_component_height')) + .onChange((value: string) => { + if ((value.trim().length === 0 && this.isText) || (value.trim().length !== 0 && !this.isText)) { + this.handleAttributeChange(); + } + }) + .fontColor(this.textInputComponentDescriptor.fontColor) + .fontSize($r('sys.float.Body_L')) + .fontWeight(FontWeight.Regular) + .enterKeyType(EnterKeyType.Done) + .borderRadius($r('sys.float.corner_radius_level12')) + .backgroundColor($r('sys.color.comp_background_tertiary')) + .caretStyle({ color: $r('sys.color.font_emphasize') }) + .placeholderFont(this.textInputComponentDescriptor.placeholderFont) + .attributeModifier(new TextInputAttributeModifier(this.textInputComponentDescriptor)) + .animation({ + duration: DetailPageConstant.UP_DURATION, + curve: Curve.Linear, + playMode: PlayMode.Normal, + }) + } + .height('100%') + .width('100%') + .justifyContent(FlexAlign.Center) + .onAreaChange((_: Area, newArea: Area) => { + this.textInputHeight = Number(newArea.height); + }) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textinput/entity/TextInputAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/textinput/entity/TextInputAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..18202a7c7dccb3a0e7f9c16cc0dffae2c1688d87 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textinput/entity/TextInputAttributeMapping.ets @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 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. + */ + +class TextInputTypeMap { + public readonly code: string; + public readonly value: InputType; + + constructor(code: string, value: InputType) { + this.code = code; + this.value = value; + } +} + +export const textInputTypeMapData: Map = new Map([ + ['Normal', new TextInputTypeMap('InputType.Normal', InputType.Normal)], + ['Number', new TextInputTypeMap('InputType.Number', InputType.Number)], + ['NewPassword', new TextInputTypeMap('InputType.NEW_PASSWORD', InputType.NEW_PASSWORD)], + ['Default', new TextInputTypeMap('InputType.Default', InputType.Normal)], +]); + +class TextInputFontMap { + public readonly code: string; + public readonly value: Font; + + constructor(code: string, value: Font) { + this.code = code; + this.value = value; + } +} + +export const textInputFontMapData: Map = new Map([ + ['较细字体', new TextInputFontMap('{ size: $r(\'sys.float.Body_L\'), weight: FontWeight.Lighter}', + { size: $r('sys.float.Body_L'), weight: FontWeight.Lighter })], + ['正常字体', new TextInputFontMap('{ size: $r(\'sys.float.Body_L\'), weight: FontWeight.Regular }', + { size: $r('sys.float.Body_L'), weight: FontWeight.Regular })], + ['较粗字体', new TextInputFontMap('{ size: $r(\'sys.float.Body_L\'), weight: FontWeight.Bold }', + { size: $r('sys.float.Body_L'), weight: FontWeight.Bold })], + ['Default', new TextInputFontMap('{ size: $r(\'sys.float.Body_L\'), weight: FontWeight.Regular }', + { size: $r('sys.float.Body_L'), weight: FontWeight.Regular })], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textinput/viewmodel/TextInputAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/textinput/viewmodel/TextInputAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..8d150e79b1b63a107f9d89fe989bf4131088b486 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textinput/viewmodel/TextInputAttributeModifier.ets @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 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 { CommonAttributeModifier } from '../../../viewmodel/CommonAttributeModifier'; +import type { TextInputDescriptor } from './TextInputDescriptor'; + +@Observed +export class TextInputAttributeModifier extends CommonAttributeModifier { + applyNormalAttribute(instance: TextInputAttribute): void { + this.assignAttribute((descriptor => descriptor.type), (val) => instance.type(val)); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textinput/viewmodel/TextInputCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/textinput/viewmodel/TextInputCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..a9f7b863e3bce71fb97239023cf54d766ad35dc3 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textinput/viewmodel/TextInputCodeGenerator.ets @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { highlightColorMap } from '../../common/entity/CommonMapData'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { textInputFontMapData, textInputTypeMapData } from '../entity/TextInputAttributeMapping'; + +export class TextInputCodeGenerator implements CommonCodeGenerator { + private typeStr: string = textInputTypeMapData.get('Default')!.code; + private fontColor: string = highlightColorMap.get('Default')!.code; + private placeholderFont: string = textInputFontMapData.get('Default')!.code; + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'type': + this.typeStr = + textInputTypeMapData.get(attribute.currentValue)?.code ?? textInputTypeMapData.get('Default')!.code; + break; + case 'fontColor': + this.fontColor = attribute.currentValue; + break; + case 'placeholderFont': + this.placeholderFont = + textInputFontMapData.get(attribute.currentValue)?.code ?? textInputFontMapData.get('Default')!.code; + break; + default: + break; + } + }); + + return `@Component +struct TextInputComponent { + build() { + Column() { + TextInput({ text: '开发者你好', placeholder: '请输入' }) + .margin({ left: 36, right: 36 }) + .height(48) + .enterKeyType(EnterKeyType.Done) + .borderRadius($r('sys.float.corner_radius_level12')) + .fontSize($r('sys.float.Body_L')) + .fontWeight(FontWeight.Regular) + .backgroundColor($r('sys.color.comp_background_tertiary')) + .caretStyle({ color: $r('sys.color.font_emphasize') }) + .type(${this.typeStr}) + .fontColor('${this.fontColor}') + .placeholderFont(${this.placeholderFont})\n + } + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textinput/viewmodel/TextInputDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/textinput/viewmodel/TextInputDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..2f63a22d70b84a9acf8e59aa879f1644b626409d --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textinput/viewmodel/TextInputDescriptor.ets @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { highlightColorMap } from '../../common/entity/CommonMapData'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { textInputFontMapData, textInputTypeMapData } from '../entity/TextInputAttributeMapping'; + +@Observed +export class TextInputDescriptor extends CommonDescriptor { + public type: InputType = textInputTypeMapData.get('Default')!.value; + public fontColor: string = highlightColorMap.get('Default')!.value; + public placeholderFont: Font = textInputFontMapData.get('Default')!.value; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'type': + this.type = + textInputTypeMapData.get(attribute.currentValue)?.value ?? textInputTypeMapData.get('Default')!.value; + break; + case 'fontColor': + this.fontColor = attribute.currentValue; + break; + case 'placeholderFont': + this.placeholderFont = + textInputFontMapData.get(attribute.currentValue)?.value ?? textInputFontMapData.get('Default')!.value; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textpickerdialogview/component/TextPickerDialogBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/textpickerdialogview/component/TextPickerDialogBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..4f257d2c89c8ab0e6bbe7268add00f612cd7c7e6 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textpickerdialogview/component/TextPickerDialogBuilder.ets @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 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'; +import { Logger } from '@ohos/common'; +import { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import type { TextPickerDialogDescriptor } from '../viewmodel/TextPickerDialogDescriptor'; + +const TAG: string = '[TextPickerDialogBuilder]'; + +@Builder +export function TextPickerDialogBuilder($$: DescriptorWrapper) { + Column() { + Button($r('app.string.text_picker_dialog_tip')) + .margin($r('sys.float.padding_level10')) + .buttonStyle(ButtonStyleMode.NORMAL) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_emphasize')) + .onClick(() => { + TextPickerDialog.show({ + range: $r('app.strarray.text_picker_data'), + defaultPickerItemHeight: ($$.descriptor as TextPickerDialogDescriptor).itemHeight, + canLoop: ($$.descriptor as TextPickerDialogDescriptor).canLoop, + onAccept: (value: TextPickerResult) => { + try { + promptAction.showToast({ + message: `Select ${value.value}`, + duration: DetailPageConstant.LONG_DURATION + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Show toast error, the code is ${error.code}}, the message is ${error.message}`); + } + }, + onCancel: () => { + try { + promptAction.showToast({ + message: 'Canceled', + duration: DetailPageConstant.LONG_DURATION + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `Show toast error, the code is ${error.code}}, the message is ${error.message}`); + } + }, + }); + }) + } + .width('100%') +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textpickerdialogview/entity/TextDialogAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/textpickerdialogview/entity/TextDialogAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..076881753fe88aa919396f3f1252a9281a5b26c1 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textpickerdialogview/entity/TextDialogAttributeMapping.ets @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 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 { CommonBoolMapping, CommonNumberMapping } from '../../common/entity/CommonMapData'; + +export const itemHeightMapData: Map = new Map([ + ['Default', new CommonNumberMapping('56', 56)], +]) + +export const canLoopMapData: Map = new Map([ + ['Default', new CommonBoolMapping('true', true)], +]) + +export const pickerData: string[] = ['apple', 'orange', 'peach', 'grape', 'banana']; + +export const pickerDataCode: string = `['apple', 'orange', 'peach', 'grape', 'banana']`; \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textpickerdialogview/viewmodel/TextPickerDialogCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/textpickerdialogview/viewmodel/TextPickerDialogCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..2fdfd6fe42bff34117dc46cab49673da60dd08a6 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textpickerdialogview/viewmodel/TextPickerDialogCodeGenerator.ets @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { + canLoopMapData, + itemHeightMapData, + pickerDataCode, +} from '../entity/TextDialogAttributeMapping'; + +export class TextPickerDialogCodeGenerator implements CommonCodeGenerator { + private canLoop: string = canLoopMapData.get('Default')!.code; + private itemHeight: string = itemHeightMapData.get('Default')!.code; + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'canLoop': + this.canLoop = attribute.currentValue; + break; + case 'itemHeight': + this.itemHeight = attribute.currentValue; + break; + default: + break; + } + }); + return `import { promptAction } from '@kit.ArkUI'; + +@Component +struct TextPickerComponent { + build() { + Column() { + Button('文本选择器弹窗') + .margin($r('sys.float.padding_level10')) + .buttonStyle(ButtonStyleMode.NORMAL) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_emphasize')) + .onClick(() => { + TextPickerDialog.show({ + range: ${pickerDataCode}, + defaultPickerItemHeight: ${this.itemHeight}, + canLoop: ${this.canLoop}, + onAccept: (value: TextPickerResult) => { + try { + promptAction.showToast({ + message: \`Select \${value.value}\`, + duration: 200 + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`Show toast error, the code is \${error.code}, the message is \${error.message}\`); + } + }, + onCancel: () => { + try { + promptAction.showToast({ + message: 'Canceled', + duration: 200 + }); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`Show toast error, the code is \${error.code}, the message is \${error.message}\`); + } + } + }) + }) + } + .width('100%') + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textpickerdialogview/viewmodel/TextPickerDialogDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/textpickerdialogview/viewmodel/TextPickerDialogDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..3d3030e571cc8dff5021a4584a86eb7d2dcd9593 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textpickerdialogview/viewmodel/TextPickerDialogDescriptor.ets @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { + canLoopMapData, + itemHeightMapData, +} from '../entity/TextDialogAttributeMapping'; + +@Observed +export class TextPickerDialogDescriptor extends CommonDescriptor { + public selected: number | number[] = 0; + public canLoop: boolean = canLoopMapData.get('Default')!.value as boolean; + public itemHeight: number = itemHeightMapData.get('Default')!.value as number; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'canLoop': + this.canLoop = JSON.parse(attribute.currentValue) ?? this.canLoop; + break; + case 'itemHeight': + this.itemHeight = Number(attribute.currentValue) ?? this.itemHeight; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/texttospeech/component/TextToSpeechBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/texttospeech/component/TextToSpeechBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..978a9b619bd2be3558bdbfd3b93fe8d2967f1d59 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/texttospeech/component/TextToSpeechBuilder.ets @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apach 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 type { BusinessError } from '@kit.BasicServicesKit'; +import { textToSpeech } from '@kit.CoreSpeechKit'; +import { Logger } from '@ohos/common'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import type { TextToSpeechDescriptor } from '../viewmodel/TextToSpeechDescriptor'; +import { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import { ComponentDetailManager } from '../../../viewmodel/ComponentDetailManager'; +import { AttributeChangeEnable } from '../../../viewmodel/ComponentDetailPageVM'; + +let ttsEngine: textToSpeech.TextToSpeechEngine; +const TAG: string = '[TextToSpeechComponent]'; + +@Component +struct TextToSpeechComponent { + @Prop textToSpeechDescriptor: TextToSpeechDescriptor; + @State originalText: string = '古人学问无遗力,少壮工夫老始成。纸上得来终觉浅,绝知此事要躬行。'; + @State text: string = ''; + @StorageLink('isPlaying') @Watch('changePlayMode') isPlaying: boolean = false; + + changePlayMode() { + ComponentDetailManager.getInstance() + .getDetailViewModel('TextToSpeech')?.sendEvent(new AttributeChangeEnable('speed', !this.isPlaying)); + } + + aboutToDisappear() { + try { + const isBusy = ttsEngine?.isBusy(); + if (isBusy) { + ttsEngine?.stop(); + AppStorage.setOrCreate('isPlaying', false); + ttsEngine?.shutdown(); + } + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `The ttsEngine invoke error, the code is ${error.code}, the message is ${error.message}`); + } + } + + build() { + Column({ space: DetailPageConstant.COMPONENT_GAP_SIZE }) { + TextArea({ text: `${this.originalText}` }) + .focusable(false) + .backgroundColor(Color.Transparent) + .margin({ + top: $r('sys.float.padding_level16'), + left: $r('sys.float.padding_level16'), + right: $r('sys.float.padding_level16'), + }) + + Row({ space: DetailPageConstant.COMPONENT_GAP_SIZE2 }) { + Button() { + if (this.isPlaying) { + Image($r('app.media.pause')) + .height($r('app.float.button_height_normal')) + .width($r('app.float.button_height_normal')) + } else { + Image($r('app.media.play_circle_fill')) + .height($r('app.float.button_height_normal')) + .width($r('app.float.button_height_normal')) + } + } + .height($r('app.float.button_height_normal')) + .width($r('app.float.button_height_normal')) + .backgroundColor(Color.Transparent) + .onClick(() => { + try { + this.createByCallback(); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `CreateEngine error, the code is ${error.code}, the message is ${error.message}`); + } + }) + + Button() { + Image($r('app.media.stop_circle')) + .height($r('app.float.button_height_normal')) + .width($r('app.float.button_height_normal')) + } + .height($r('app.float.button_height_normal')) + .width($r('app.float.button_height_normal')) + .backgroundColor(Color.Transparent) + .onClick(() => { + try { + ttsEngine?.stop(); + ttsEngine?.shutdown(); + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `The ttsEngine invoke error, the code is ${error.code}, the message is ${error.message}`); + } + AppStorage.setOrCreate('isPlaying', false); + }) + } + .margin({ top: $r('sys.float.padding_level8') }) + .height($r('app.float.button_height_normal')) + .width('100%') + .justifyContent(FlexAlign.Center) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + } + + private createByCallback() { + // The value of 'name' needs to be modified after each playback. + const extraParam: Record = + { 'style': 'interaction-broadcast', 'locate': 'CN' }; + const initParamsInfo: textToSpeech.CreateEngineParams = { + language: 'zh-CN', + person: 0, + online: 1, + extraParams: extraParam, + }; + textToSpeech.createEngine(initParamsInfo, + (err: BusinessError, textToSpeechEngine: textToSpeech.TextToSpeechEngine) => { + if (!err) { + ttsEngine = textToSpeechEngine; + this.speak(); + } else { + Logger.error(TAG, `Fail to createEngine, because ${err.message}`); + } + }); + }; + + private speak() { + const speakListener: textToSpeech.SpeakListener = { + onStart(requestId: string, response: textToSpeech.StartResponse) { + Logger.debug(TAG, `onStart, requestId: ${requestId} response: ${JSON.stringify(response)}`); + AppStorage.setOrCreate('isPlaying', true); + }, + onComplete(requestId: string, response: textToSpeech.CompleteResponse) { + Logger.info(TAG, `onComplete, requestId: ${requestId} response: ${JSON.stringify(response)}`); + // Only playback completion is handled here, with no regard to stream file generation. + // 'type === 1' means broadcast over situation. + if (response.type === 1) { + AppStorage.setOrCreate('isPlaying', false); + ttsEngine?.stop(); + } + }, + onStop(requestId: string, response: textToSpeech.StopResponse) { + Logger.debug(TAG, `onStop, requestId: ${requestId} response: ${JSON.stringify(response)}`); + AppStorage.setOrCreate('isPlaying', false); + ttsEngine?.shutdown(); + }, + onError(requestId: string, errorCode: number, errorMessage: string) { + Logger.error(TAG, `onError, requestId: ${requestId} errorCode: ${errorCode} errorMessage: ${errorMessage}`); + AppStorage.setOrCreate('isPlaying', false); + ttsEngine?.shutdown(); + } + }; + ttsEngine?.setListener(speakListener); + const extraParam: Record = { + 'queueMode': 0, + 'speed': this.textToSpeechDescriptor.speed, + 'volume': 1, + 'pitch': 1, + 'languageContext': 'zh-CN', + 'audioType': 'pcm', + 'soundChannel': 1, + 'playType': 1, + }; + const speakParams: textToSpeech.SpeakParams = { + requestId: '123456-a', + extraParams: extraParam, + }; + ttsEngine?.speak(this.originalText, speakParams); + }; +} + +@Builder +export function TextToSpeechBuilder($$: DescriptorWrapper) { + TextToSpeechComponent({ textToSpeechDescriptor: $$.descriptor as TextToSpeechDescriptor }) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/texttospeech/viewmodel/TextToSpeechCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/texttospeech/viewmodel/TextToSpeechCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..f00faf24351bd5ebc2e7dae6cbf284b403c31600 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/texttospeech/viewmodel/TextToSpeechCodeGenerator.ets @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; + +export class TextToSpeechCodeGenerator implements CommonCodeGenerator { + public generate(_attributes: OriginAttribute[]): string { + return `import { textToSpeech } from '@kit.CoreSpeechKit'; +import type { BusinessError } from '@kit.BasicServicesKit'; +let ttsEngine: textToSpeech.TextToSpeechEngine; + +@Component +struct TextToSpeechComponent { + @State originalText: string = '古人学问无遗力,少壮工夫老始成;纸上得来终觉浅,绝知此事要躬行。'; + @State createCount: number = 0; + @State result: boolean = false; + @State voiceInfo: string = ''; + @State text: string = ''; + @State textContent: string = ''; + @State utteranceId: string = '123456'; + @State illegalText: string = ''; + @StorageLink('isPlaying') isPlaying: boolean = false; + + aboutToDisappear() { + try { + const isBusy = ttsEngine?.isBusy(); + if (isBusy) { + ttsEngine?.stop(); + AppStorage.setOrCreate('isPlaying', false); + ttsEngine?.shutdown(); + } + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`The ttsEngine invoke error, the code is \${error.code}, the message is \${error.message}\`); + } + } + + build() { + Column({ space: 20 }) { + TextArea({ text: this.originalText }) + .focusable(false) + .backgroundColor(Color.Transparent) + .margin({ + top: $r('sys.float.padding_level16'), + left: $r('sys.float.padding_level16'), + right: $r('sys.float.padding_level16') + }) + + Row({ space: 40 }) { + Button('', { type: ButtonType.Normal, stateEffect: true }) + .backgroundColor(Color.Transparent) + // A picture of a play and a picture of a pause button needs to be added here. + .backgroundImage(this.isPlaying?$r('app.media.pause'):$r('app.media.play_circle_fill')) + .backgroundImagePosition(Alignment.Center) + .onClick(() => { + try { + this.createByCallback(); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`CreateEngine error, the code is \${error.code}, the message is \${error.message}\`); + } + }) + .height('40vp') + .width('40vp') + + Button('', { type: ButtonType.Normal, stateEffect: true }) + .backgroundColor(Color.Transparent) + // A picture of a stop button needs to be added here. + .backgroundImage($r('app.media.stop_circle')) + .backgroundImagePosition(Alignment.Center) + .onClick(() => { + try { + ttsEngine?.stop(); + ttsEngine?.shutdown(); + } catch (err) { + const error: BusinessError = err as BusinessError; + console.error(\`The ttsEngine invoke error, the code is \${error.code}, the message is \${error.message}\`); + } + AppStorage.setOrCreate('isPlaying', false); + }) + .height('40vp') + .width('40vp') + } + .margin({ top: $r('sys.float.padding_level8') }) + .height('40vp') + .width('100%') + .justifyContent(FlexAlign.Center) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + } + + createByCallback() { + let extraParam: Record = { 'style': 'interaction-broadcast', 'locate': 'CN', 'name': 'EngineName' }; + let initParamsInfo: textToSpeech.CreateEngineParams = { + language: 'zh-CN', + person: 0, + online: 1, + extraParams: extraParam + }; + textToSpeech.createEngine(initParamsInfo, + (err: BusinessError, textToSpeechEngine: textToSpeech.TextToSpeechEngine) => { + if (!err) { + ttsEngine = textToSpeechEngine; + this.createCount++; + this.speak(); + } else { + console.log('Fail to createEngine.'); + } + }); + }; + + speak() { + let speakListener: textToSpeech.SpeakListener = { + onStart(requestId: string, response: textToSpeech.StartResponse) { + console.log('onStart'); + AppStorage.setOrCreate('isPlaying', true); + }, + onComplete(requestId: string, response: textToSpeech.CompleteResponse) { + console.log('onComplete'); + AppStorage.setOrCreate('isPlaying', true); + }, + onStop(requestId: string, response: textToSpeech.StopResponse) { + console.log('onStop'); + AppStorage.setOrCreate('isPlaying', false); + ttsEngine?.shutdown(); + }, + onData(requestId: string, audio: ArrayBuffer, response: textToSpeech.SynthesisResponse) { + console.log('onData'); + }, + onError(requestId: string, errorCode: number, errorMessage: string) { + console.log('onError'); + AppStorage.setOrCreate('isPlaying', false); + ttsEngine?.shutdown(); + } + }; + ttsEngine?.setListener(speakListener); + let extraParam: Record = { + 'queueMode': 0, + 'speed': 1, + 'volume': 2, + 'pitch': 1, + 'languageContext': 'zh-CN', + 'audioType': 'pcm', + 'soundChannel': 3, + 'playType': 1 + } + let speakParams: textToSpeech.SpeakParams = { + requestId: '123456-a', + extraParams: extraParam + }; + ttsEngine?.speak(this.originalText, speakParams); + }; +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/texttospeech/viewmodel/TextToSpeechDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/texttospeech/viewmodel/TextToSpeechDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..e50f2796b1a0a5964492bca6e373d89691dbd755 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/texttospeech/viewmodel/TextToSpeechDescriptor.ets @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; + +@Observed +export class TextToSpeechDescriptor extends CommonDescriptor { + public speed: number = 1; + public speedType: number = 1; + public speedArray: string[] = ['0.5倍', '1倍', '1.5倍', '2倍']; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'speed': + this.speedType = this.speedArray.indexOf(attribute.currentValue); + if (this.speedType === -1) { + this.speedType = 1; + } + this.speed = (this.speedType + 1) * 0.5; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textview/component/TextBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/textview/component/TextBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..58f70c819cae24dbe5343c74afa2e2e2a94bfeb8 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textview/component/TextBuilder.ets @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 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 type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import type { TextDescriptor } from '../viewmodel/TextDescriptor'; +import { TextAttributeModifier } from '../viewmodel/TextAttributeModifier'; + +@Builder +export function TextBuilder($$: DescriptorWrapper) { + Text($r('app.string.textview_text')) + .attributeModifier(new TextAttributeModifier($$.descriptor as TextDescriptor)) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textview/entity/TextAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/textview/entity/TextAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..3eafed3678001e0c2e71b4ac4c730681d6d7b12b --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textview/entity/TextAttributeMapping.ets @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 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 { CommonColorMapping, CommonNumberMapping } from '../../common/entity/CommonMapData'; + +class TextMapping { + public readonly code: string; + public readonly value: Length; + + constructor(code: string, value: Length) { + this.code = code; + this.value = value; + } +} + +export const fontSizeMapData: Map = new Map([ + ['Default', new TextMapping('16', $r('sys.float.ohos_id_text_size_body1'))], +]); + +export const fontColorMapData: Map = new Map([ + ['Default', new CommonColorMapping('rgba(0,85,255)', 'rgba(0,85,255)')], +]); + +export const opacityMapData: Map = new Map([ + ['Default', new CommonNumberMapping('1', 1)], +]); + +export const letterSpacingMapData: Map = new Map([ + ['Default', new CommonNumberMapping('0', 0)], +]); + +export const textShadowRadiusMapData: Map = new Map([ + ['Default', new CommonNumberMapping('0', 0)], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textview/viewmodel/TextAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/textview/viewmodel/TextAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..6b7aba8994b79cdd49b34e5785535eba4c0f83e4 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textview/viewmodel/TextAttributeModifier.ets @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 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 { CommonAttributeModifier } from '../../../viewmodel/CommonAttributeModifier'; +import type { TextDescriptor } from './TextDescriptor'; + +@Observed +export class TextAttributeModifier extends CommonAttributeModifier { + applyNormalAttribute(instance: TextAttribute): void { + this.assignAttribute((descriptor => descriptor.fontWeight), (val) => instance.fontWeight(val)); + this.assignAttribute((descriptor => descriptor.fontSize), (val) => instance.fontSize(Number(val))); + this.assignAttribute((descriptor => descriptor.fontColor), (val) => instance.fontColor(val)); + this.assignAttribute((descriptor => descriptor.opacity), (val) => instance.opacity(val)); + this.assignAttribute((descriptor => descriptor.letterSpacing), (val) => instance.letterSpacing(val)); + this.assignAttribute((descriptor => descriptor.textShadowRadius), (val) => instance.textShadow({ radius: val, + color: $r('sys.color.ohos_id_color_foreground')})); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textview/viewmodel/TextCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/textview/viewmodel/TextCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..5c840764df9b68ed7cecfd0ffecf46272f291989 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textview/viewmodel/TextCodeGenerator.ets @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { fontWeightMapData } from '../../common/entity/CommonMapData'; +import { + fontColorMapData, + fontSizeMapData, + letterSpacingMapData, + opacityMapData, + textShadowRadiusMapData, +} from '../entity/TextAttributeMapping'; + +export class TextCodeGenerator implements CommonCodeGenerator { + private fontWeight: string = fontWeightMapData.get('Default')!.code; + private fontSize: string = fontSizeMapData.get('Default')!.code; + private fontColor: string = fontColorMapData.get('Default')!.code; + private opacity: string = opacityMapData.get('Default')!.code; + private letterSpacing: string = letterSpacingMapData.get('Default')!.code; + private textShadowRadius: string = textShadowRadiusMapData.get('Default')!.code; + + public generate(attributes: OriginAttribute[]): string { + const text = '开发者你好'; + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'fontWeight': + this.fontWeight = + fontWeightMapData.get(attribute.currentValue)?.code ?? fontWeightMapData.get('Default')!.code; + break; + case 'fontColor': + this.fontColor = attribute.currentValue; + break; + case 'fontSize': + this.fontSize = attribute.currentValue; + break; + case 'opacity': + this.opacity = Number(attribute.currentValue).toString(); + break; + case 'letterSpacing': + this.letterSpacing = attribute.currentValue; + break; + case 'textShadowRadius': + this.textShadowRadius = attribute.currentValue; + break; + default: + break; + } + }); + return `@Component +struct TextComponent { + build() { + Text('${text}') + .fontSize(${this.fontSize}) + .fontColor('${this.fontColor}') + .fontWeight(${this.fontWeight}) + .opacity(${this.opacity}) + .letterSpacing(${this.letterSpacing}) + .textShadow({ radius: ${this.textShadowRadius} }) + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/textview/viewmodel/TextDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/textview/viewmodel/TextDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..770cb82e40fd898f18f56fe531d29dcfc9273af0 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/textview/viewmodel/TextDescriptor.ets @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { fontWeightMapData } from '../../common/entity/CommonMapData'; +import { + fontColorMapData, + fontSizeMapData, + letterSpacingMapData, + opacityMapData, + textShadowRadiusMapData, +} from '../entity/TextAttributeMapping'; + +@Observed +export class TextDescriptor extends CommonDescriptor { + public fontWeight: FontWeight = fontWeightMapData.get('Default')!.value as FontWeight; + public fontSize: number | string = fontSizeMapData.get('Default')!.value as number; + public fontColor: ResourceColor = fontColorMapData.get('Default')!.value as string; + public opacity: number = opacityMapData.get('Default')!.value as number; + public letterSpacing: number = letterSpacingMapData.get('Default')!.value as number; + public textShadowRadius: number = textShadowRadiusMapData.get('Default')!.value as number; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'fontWeight': + this.fontWeight = (fontWeightMapData.get(attribute.currentValue)?.value ?? + fontWeightMapData.get('Default')!.value) as FontWeight; + break; + case 'fontColor': + this.fontColor = attribute.currentValue; + break; + case 'fontSize': + this.fontSize = Number(attribute.currentValue); + break; + case 'opacity': + this.opacity = Number(attribute.currentValue); + break; + case 'letterSpacing': + this.letterSpacing = Number(attribute.currentValue); + break; + case 'textShadowRadius': + this.textShadowRadius = Number(attribute.currentValue); + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/toggleview/component/ToggleBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/toggleview/component/ToggleBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..66a4361421cc387f7a3c8ad2b3dc5da652e6d8a7 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/toggleview/component/ToggleBuilder.ets @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 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 { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import { ComponentDetailManager } from '../../../viewmodel/ComponentDetailManager'; +import { ComPreviewChangeEvent } from '../../../viewmodel/ComponentDetailPageVM'; +import type { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import type { ToggleDescriptor } from '../viewmodel/ToggleDescriptor'; + +@Builder +export function ToggleBuilder($$: DescriptorWrapper) { + Row() { + if (($$.descriptor as ToggleDescriptor).toggleType === ToggleType.Switch) { + Text($r('app.string.toggle_type')) + .fontSize($r('app.float.default_font_16')) + .fontColor($r('sys.color.font_secondary')) + Blank() + } + Toggle({ + type: ($$.descriptor as ToggleDescriptor).toggleType, + isOn: ($$.descriptor as ToggleDescriptor).isOn + }) { + Text($r('app.string.toggle_button_text')) + .fontColor($r('sys.color.font_on_primary')) + .fontSize($r('app.float.default_font_12')) + .visibility(($$.descriptor as ToggleDescriptor).toggleType === ToggleType.Button ? Visibility.Visible : + Visibility.Hidden) + } + .onChange((isOn: boolean) => { + ComponentDetailManager.getInstance().getDetailViewModel('Toggle')?.sendEvent( + new ComPreviewChangeEvent('isOn', String(isOn)) + ) + }) + .width(($$.descriptor as ToggleDescriptor).toggleType === ToggleType.Button ? DetailPageConstant.TOGGLE_WIDTH : + undefined) + .height(($$.descriptor as ToggleDescriptor).toggleType === ToggleType.Button ? DetailPageConstant.TOGGLE_HEIGHT : + undefined) + .selectedColor(($$.descriptor as ToggleDescriptor).backgroundColor) + } + .width('100%') + .justifyContent(FlexAlign.Center) + .padding({ left: $r('sys.float.padding_level16'), right: $r('sys.float.padding_level16') }) +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/toggleview/entity/ToggleAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/toggleview/entity/ToggleAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..ba19882714f6276a6eab4e3039524688669fa652 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/toggleview/entity/ToggleAttributeMapping.ets @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 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 { CommonBoolMapping, CommonColorMapping } from '../../common/entity/CommonMapData'; + +type ToggleValue = ToggleType | ResourceColor ; + +class ToggleTypeMapping { + public readonly code: string; + public readonly value: ToggleValue; + + constructor(code: string, value: ToggleValue) { + this.code = code; + this.value = value; + } +} + +export const toggleTypeMapData: Map = new Map([ + ['Default', new ToggleTypeMapping('ToggleType.Switch', ToggleType.Switch)], + ['Switch', new ToggleTypeMapping('ToggleType.Switch', ToggleType.Switch)], + ['Button', new ToggleTypeMapping('ToggleType.Button', ToggleType.Button)], + ['Checkbox', new ToggleTypeMapping('ToggleType.Checkbox', ToggleType.Checkbox)], +]); + +export const trackBorderRadiusMapData: Map = new Map([ + ['Default', new ToggleTypeMapping('16', $r('sys.float.ohos_id_corner_radius_default_l'))], +]); + +export const isOnMapData: Map = new Map([ + ['Default', new CommonBoolMapping('true', true)], +]); + +export const backgroundColorMapData: Map = new Map([ + ['Default', new CommonColorMapping('rgba(0,85,255)', 'rgba(0,85,255)')], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/toggleview/viewmodel/ToggleAttributeFilter.ets b/features/componentlibrary/src/main/ets/componentdetailview/toggleview/viewmodel/ToggleAttributeFilter.ets new file mode 100644 index 0000000000000000000000000000000000000000..84310191866cbe083a81daf18bb0df05b13317d5 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/toggleview/viewmodel/ToggleAttributeFilter.ets @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 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 type { ObservedArray } from '@ohos/common'; +import type { Attribute } from '../../../viewmodel/Attribute'; +import { CommonAttributeFilter } from '../../../viewmodel/CommonAttributeFilter'; + +export class ToggleAttributeFilter implements CommonAttributeFilter { + filter(attributes: ObservedArray): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'isOn': + const index = attributes.findIndex((item) => item.name === 'backgroundColor'); + if (index !== -1) { + attributes[index].enable = (attribute.currentValue === 'true'); + } + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/toggleview/viewmodel/ToggleCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/toggleview/viewmodel/ToggleCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..ca19292caab41303c3eddfcb5020995c211e6132 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/toggleview/viewmodel/ToggleCodeGenerator.ets @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { backgroundColorMapData, isOnMapData, toggleTypeMapData } from '../entity/ToggleAttributeMapping'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; + +export class ToggleCodeGenerator implements CommonCodeGenerator { + private toggleType: string = toggleTypeMapData.get('Default')!.code; + private isOn: string = isOnMapData.get('Default')!.code; + private backgroundColor: string = backgroundColorMapData.get('Default')!.code; + + public generate(attributes: OriginAttribute[]): string { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'toggleType': + this.toggleType = + toggleTypeMapData.get(attribute.currentValue)?.code ?? toggleTypeMapData.get('Default')!.code; + break; + case 'isOn': + this.isOn = JSON.parse(attribute.currentValue); + break; + case 'backgroundColor': + this.backgroundColor = attribute.currentValue; + break; + default: + break; + } + }); + const codeOne = this.toggleType === 'ToggleType.Switch' ? ` + Text('Switch样式') + .fontSize(16) + .fontColor($r('sys.color.font_secondary')) + Blank()` : ''; + if (this.toggleType === 'ToggleType.Button') { + return `@Component +struct ToggleComponent { + // You can view different styles by changing the toggleType. + build() { + Row() {${codeOne} + Toggle({ + type: ${this.toggleType}, + isOn: ${this.isOn} + }){ + Text('状态按钮') + .fontColor($r('sys.color.font_on_primary')) + .fontSize(12) + } + .selectedColor('${this.backgroundColor}') + } + .width('100%') + .justifyContent(FlexAlign.Center) + .padding({ left: $r('sys.float.padding_level16'), right: $r('sys.float.padding_level16') }) + } +}`; + } else { + return `@Component +struct ToggleComponent { + // You can view different styles by changing the toggleType. + build() { + Row() {${codeOne} + Toggle({ + type: ${this.toggleType}, + isOn: ${this.isOn} + }) + .selectedColor('${this.backgroundColor}') + } + .width('100%') + .justifyContent(FlexAlign.Center) + .padding({ left: $r('sys.float.padding_level16'), right: $r('sys.float.padding_level16') }) + } +}` + } + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/toggleview/viewmodel/ToggleDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/toggleview/viewmodel/ToggleDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..7c5f11c438e508b97f23cf5f1fd561cfb8c8af7a --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/toggleview/viewmodel/ToggleDescriptor.ets @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { + backgroundColorMapData, + isOnMapData, + toggleTypeMapData, + trackBorderRadiusMapData, +} from '../entity/ToggleAttributeMapping'; + +@Observed +export class ToggleDescriptor extends CommonDescriptor { + public toggleType: ToggleType = toggleTypeMapData.get('Default')!.value as ToggleType; + public trackBorderRadius: number = trackBorderRadiusMapData.get('Default')!.value as number; + public isOn: boolean = isOnMapData.get('Default')!.value as boolean; + public backgroundColor: ResourceColor = backgroundColorMapData.get('Default')!.value as ResourceColor; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'toggleType': + this.toggleType = toggleTypeMapData.get(attribute.currentValue)?.value as ToggleType ?? + toggleTypeMapData.get('Default')!.value as ToggleType; + break; + case 'isOn': + this.isOn = JSON.parse(attribute.currentValue) ?? isOnMapData.get('Default')!.value as boolean; + break; + case 'backgroundColor': + this.backgroundColor = attribute.currentValue; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/waterflow/component/WaterFlowBuilder.ets b/features/componentlibrary/src/main/ets/componentdetailview/waterflow/component/WaterFlowBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..e109005e464cc5f9c6c07dce19addbccd934b026 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/waterflow/component/WaterFlowBuilder.ets @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2024 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 { DetailPageConstant } from '../../../constant/DetailPageConstant'; +import { DescriptorWrapper } from '../../../viewmodel/DescriptorWrapper'; +import { WaterFlowAttributeModifier } from '../viewmodel/WaterFlowAttributeModifier'; +import { WaterFlowDescriptor } from '../viewmodel/WaterFlowDescriptor'; + +const ITEMS_COUNT: number = 20; +const FLOW_ITEMS_COUNT: number = 100; +const THRESHOLD_ITEMS_COUNT: number = 20; + +export class WaterFlowDataSource implements IDataSource { + private dataArray: number[] = []; + private listeners: DataChangeListener[] = []; + + constructor() { + for (let i = 0; i < ITEMS_COUNT; i++) { + this.dataArray.push(i); + } + } + + public getData(index: number): number { + return this.dataArray[index]; + } + + public notifyDataReload(): void { + this.listeners.forEach(listener => { + listener.onDataReloaded(); + }) + } + + public notifyDataAdd(index: number): void { + this.listeners.forEach(listener => { + listener.onDataAdd(index); + }) + } + + public notifyDataChange(index: number): void { + this.listeners.forEach(listener => { + listener.onDataChange(index); + }) + } + + public notifyDataDelete(index: number): void { + this.listeners.forEach(listener => { + listener.onDataDelete(index); + }) + } + + public notifyDataMove(from: number, to: number): void { + this.listeners.forEach(listener => { + listener.onDataMove(from, to); + }) + } + + public totalCount(): number { + return this.dataArray.length; + } + + public registerDataChangeListener(listener: DataChangeListener): void { + if (this.listeners.indexOf(listener) < 0) { + this.listeners.push(listener); + } + } + + public unregisterDataChangeListener(listener: DataChangeListener): void { + const pos = this.listeners.indexOf(listener) + if (pos >= 0) { + this.listeners.splice(pos, 1); + } + } + + public add1stItem(): void { + this.dataArray.splice(0, 0, this.dataArray.length); + this.notifyDataAdd(0); + } + + public addLastItem(): void { + this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length); + this.notifyDataAdd(this.dataArray.length - 1); + } + + public addItem(index: number): void { + this.dataArray.splice(index, 0, this.dataArray.length); + this.notifyDataAdd(index); + } + + public delete1stItem(): void { + this.dataArray.splice(0, 1); + this.notifyDataDelete(0); + } + + public delete2ndItem(): void { + this.dataArray.splice(1, 1); + this.notifyDataDelete(1); + } + + public deleteLastItem(): void { + this.dataArray.splice(-1, 1); + this.notifyDataDelete(this.dataArray.length); + } + + public reload(): void { + this.dataArray.splice(1, 1); + this.dataArray.splice(3, 2); + this.notifyDataReload(); + } +} + +@Builder +export function WaterFlowBuilder($$: DescriptorWrapper) { + WaterFlowComponent({ waterFlowDescriptor: $$.descriptor as WaterFlowDescriptor }) +} + +@Component +export struct WaterFlowComponent { + @Prop waterFlowDescriptor: WaterFlowDescriptor; + private list: WaterFlowDataSource = new WaterFlowDataSource(); + private minSize: number = DetailPageConstant.WATER_FLOW_MIN_SIZE; + private maxSize: number = DetailPageConstant.WATER_FLOW_MAX_SIZE; + private itemWidthArray: number[] = []; + private itemHeightArray: number[] = []; + + getSize() { + const ret = Math.floor(Math.random() * this.maxSize); + return (ret > this.minSize ? ret : this.minSize); + } + + setItemSizeArray() { + for (let i = 0; i < FLOW_ITEMS_COUNT; i++) { + this.itemWidthArray.push(this.getSize()); + this.itemHeightArray.push(this.getSize()); + } + } + + aboutToAppear() { + this.setItemSizeArray(); + } + + build() { + WaterFlow() { + LazyForEach(this.list, (item: number) => { + FlowItem() { + ReusableFlowItem({ item: item }) + } + .onAppear(() => { + if (item + THRESHOLD_ITEMS_COUNT === this.list.totalCount()) { + for (let i = 0; i < FLOW_ITEMS_COUNT; i++) { + this.list.addLastItem(); + } + } + }) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .border({ radius: $r('sys.float.corner_radius_level4') }) + .width(this.waterFlowDescriptor.layoutDirection === FlexDirection.Column || + this.waterFlowDescriptor.layoutDirection === FlexDirection.ColumnReverse ? '100%' : + this.itemWidthArray[item % FLOW_ITEMS_COUNT]) + .height(this.waterFlowDescriptor.layoutDirection === FlexDirection.Column || + this.waterFlowDescriptor.layoutDirection === FlexDirection.ColumnReverse ? + this.itemHeightArray[item % FLOW_ITEMS_COUNT] : '100%') + }, (item: string, _index: number) => item.toString()) + } + .cachedCount(2) + .attributeModifier(new WaterFlowAttributeModifier(this.waterFlowDescriptor)) + .width($r('app.float.water_flow_width')) + .height($r('app.float.water_flow_height')) + .rowsGap($r('sys.float.padding_level3')) + .padding($r('sys.float.padding_level3')) + .border({ + width: 1, + color: $r('sys.color.comp_background_emphasize'), + radius: $r('sys.float.corner_radius_level6') + }) + .onScrollFrameBegin((offset: number) => { + return { offsetRemain: offset }; + }) + } +} + +@Reusable +@Component +struct ReusableFlowItem { + @State item: number = 0; + + build() { + Text(`${this.item + 1}`) + .fontWeight(FontWeight.Regular) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_on_primary')) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/waterflow/entity/WaterFlowAttributeMapping.ets b/features/componentlibrary/src/main/ets/componentdetailview/waterflow/entity/WaterFlowAttributeMapping.ets new file mode 100644 index 0000000000000000000000000000000000000000..d3d580a1fafa5862ada169e23cea51074bde846d --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/waterflow/entity/WaterFlowAttributeMapping.ets @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024 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 { CommonNumberMapping, CommonStringMapping } from '../../common/entity/CommonMapData'; + +class FlexDirectionMapping { + public code: string; + public value: FlexDirection; + + constructor(code: string, value: FlexDirection) { + this.code = code; + this.value = value; + } +} + +export const layoutDirectionMapData: Map = new Map([ + ['Default', new FlexDirectionMapping('FlexDirection.Column', FlexDirection.Column)], + ['Column', new FlexDirectionMapping('FlexDirection.Column', FlexDirection.Column)], + ['Row', new FlexDirectionMapping('FlexDirection.Row', FlexDirection.Row)], + ['RowReverse', new FlexDirectionMapping('FlexDirection.RowReverse', FlexDirection.RowReverse)], + ['ColumnReverse', new FlexDirectionMapping('FlexDirection.ColumnReverse', FlexDirection.ColumnReverse)], +]); + +export const frictionMapData: Map = new Map([ + ['Default', new CommonNumberMapping('0.75', 0.75)], + ['0.1', new CommonNumberMapping('0.1', 0.1)], + ['0.6', new CommonNumberMapping('0.6', 0.6)], + ['0.75', new CommonNumberMapping('0.75', 0.75)], + ['0.9', new CommonNumberMapping('0.9', 0.9)], +]); + +export const columnsTemplateMapData: Map = new Map([ + ['Default', new CommonStringMapping('1fr', '1fr')], +]); + +export const rowsTemplateMapData: Map = new Map([ + ['Default', new CommonStringMapping('1fr', '1fr')], +]); + +export const columnsGapMapData: Map = new Map([ + ['Default', new CommonNumberMapping('0', 0)], +]); \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/waterflow/viewmodel/WaterFlowAttributeModifier.ets b/features/componentlibrary/src/main/ets/componentdetailview/waterflow/viewmodel/WaterFlowAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..59d087b927a62b7b712798a38f10c5fd3bba969d --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/waterflow/viewmodel/WaterFlowAttributeModifier.ets @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 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 { CommonAttributeModifier } from '../../../viewmodel/CommonAttributeModifier'; +import type { WaterFlowDescriptor } from './WaterFlowDescriptor'; + +@Observed +export class WaterFlowAttributeModifier extends CommonAttributeModifier { + applyNormalAttribute(instance: WaterFlowAttribute): void { + this.assignAttribute((descriptor => descriptor.layoutDirection), (val) => instance.layoutDirection(val)); + this.assignAttribute((descriptor => descriptor.friction), (val) => instance.friction(Number(val))); + this.assignAttribute((descriptor => descriptor.columnsTemplate), (val) => instance.columnsTemplate(val)); + this.assignAttribute((descriptor => descriptor.rowsTemplate), (val) => instance.rowsTemplate(val)); + this.assignAttribute((descriptor => descriptor.columnsGap), (val) => instance.columnsGap(val)); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/waterflow/viewmodel/WaterFlowCodeGenerator.ets b/features/componentlibrary/src/main/ets/componentdetailview/waterflow/viewmodel/WaterFlowCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..f0af7033b13fb419e66c81a7f23bd435771d7e94 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/waterflow/viewmodel/WaterFlowCodeGenerator.ets @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2024 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 { StringUtil } from '../../../util/StringUtil'; +import type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonCodeGenerator } from '../../../viewmodel/CommonCodeGenerator'; +import { + columnsGapMapData, + columnsTemplateMapData, + frictionMapData, + layoutDirectionMapData, + rowsTemplateMapData, +} from '../entity/WaterFlowAttributeMapping'; + +export class WaterFlowCodeGenerator implements CommonCodeGenerator { + private layoutDirection: string = layoutDirectionMapData.get('Default')!.code; + private friction: string = frictionMapData.get('Default')!.code; + private columnsTemplate: string = columnsTemplateMapData.get('Default')!.code; + private rowsTemplate: string = rowsTemplateMapData.get('Default')!.code; + private columnsGap: string = columnsGapMapData.get('Default')!.code; + + public generate(attributes: OriginAttribute[]): string { + let itemWidth = ''; + let itemHeight = ''; + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'layoutDirection': + this.layoutDirection = + layoutDirectionMapData.get(attribute.currentValue)?.code ?? layoutDirectionMapData.get('Default')!.code; + if (this.layoutDirection === `FlexDirection.Column` || + this.layoutDirection === `FlexDirection.ColumnReverse`) { + itemWidth = `'100%'`; + itemHeight = 'this.itemHeightArray[item % 100]'; + } else { + itemWidth = 'this.itemWidthArray[item % 100]'; + itemHeight = `'100%'`; + } + break; + case 'friction': + this.friction = frictionMapData.get(attribute.currentValue)?.code ?? frictionMapData.get('Default')!.code; + break; + case 'columnsTemplate': + this.columnsTemplate = StringUtil.getTemplateString(Number(attribute.currentValue)); + break; + case 'rowsTemplate': + this.rowsTemplate = StringUtil.getTemplateString(Number(attribute.currentValue)); + break; + case 'columnsGap': + this.columnsGap = attribute.currentValue; + break; + default: + break; + } + }); + return `export class WaterFlowDataSource implements IDataSource { + private dataArray: number[] = [] + private listeners: DataChangeListener[] = [] + + constructor() { + for (let i = 0; i < 100; i++) { + this.dataArray.push(i) + } + } + + public getData(index: number): number { + return this.dataArray[index] + } + + notifyDataReload(): void { + this.listeners.forEach(listener => { + listener.onDataReloaded() + }) + } + + notifyDataAdd(index: number): void { + this.listeners.forEach(listener => { + listener.onDataAdd(index) + }) + } + + notifyDataChange(index: number): void { + this.listeners.forEach(listener => { + listener.onDataChange(index) + }) + } + + notifyDataDelete(index: number): void { + this.listeners.forEach(listener => { + listener.onDataDelete(index) + }) + } + + notifyDataMove(from: number, to: number): void { + this.listeners.forEach(listener => { + listener.onDataMove(from, to) + }) + } + + public totalCount(): number { + return this.dataArray.length + } + + registerDataChangeListener(listener: DataChangeListener): void { + if (this.listeners.indexOf(listener) < 0) { + this.listeners.push(listener) + } + } + + unregisterDataChangeListener(listener: DataChangeListener): void { + const pos = this.listeners.indexOf(listener) + if (pos >= 0) { + this.listeners.splice(pos, 1) + } + } + + public add1stItem(): void { + this.dataArray.splice(0, 0, this.dataArray.length) + this.notifyDataAdd(0) + } + + public addLastItem(): void { + this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length) + this.notifyDataAdd(this.dataArray.length - 1) + } + + public addItem(index: number): void { + this.dataArray.splice(index, 0, this.dataArray.length) + this.notifyDataAdd(index) + } + + public delete1stItem(): void { + this.dataArray.splice(0, 1) + this.notifyDataDelete(0) + } + + public delete2ndItem(): void { + this.dataArray.splice(1, 1) + this.notifyDataDelete(1) + } + + public deleteLastItem(): void { + this.dataArray.splice(-1, 1) + this.notifyDataDelete(this.dataArray.length) + } + + public reload(): void { + this.dataArray.splice(1, 1) + this.dataArray.splice(3, 2) + this.notifyDataReload() + } +} + +@Reusable +@Component +struct ReusableFlowItem { + @State item: number = 0; + + build() { + Text(this.item + 1 + '') + .fontWeight(FontWeight.Regular) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_on_primary')) + } +} + +@Component +export struct WaterFlowComponent { + private list: WaterFlowDataSource = new WaterFlowDataSource(); + private minSize: number = 92; + private maxSize: number = 180; + private itemWidthArray: number[] = []; + private itemHeightArray: number[] = []; + + getSize() { + let ret = Math.floor(Math.random() * this.maxSize); + return (ret > this.minSize ? ret : this.minSize); + } + + setItemSizeArray() { + for (let i = 0; i < 100; i++) { + this.itemWidthArray.push(this.getSize()); + this.itemHeightArray.push(this.getSize()); + } + } + + aboutToAppear() { + this.setItemSizeArray(); + } + + build() { + Column() { + WaterFlow() { + LazyForEach(this.list, (item: number) => { + FlowItem() { + ReusableFlowItem({ item: item }) + } + .onAppear(() => { + if (item + 20 === this.list.totalCount()) { + for (let i = 0; i < 100; i++) { + this.list.addLastItem(); + } + } + }) + .backgroundColor($r('sys.color.comp_background_emphasize')) + .border({ radius: $r('sys.float.corner_radius_level4') }) + .width(${itemWidth}) + .height(${itemHeight}) + + }, (item: string, _index: number) => item.toString()) + } + .cachedCount(2) + .width('300vp') + .height('200vp') + .layoutDirection(${this.layoutDirection}) + .friction(${this.friction}) + .columnsTemplate('${this.columnsTemplate}') + .rowsTemplate('${this.rowsTemplate}') + .columnsGap(${this.columnsGap}) + .rowsGap($r('sys.float.padding_level3')) + .padding($r('sys.float.padding_level3')) + .border({ + width: 1, + color: $r('sys.color.comp_background_emphasize'), + radius: $r('sys.float.corner_radius_level6') + }) + .onScrollFrameBegin((offset: number) => { + return { offsetRemain: offset }; + }) + } + .width('100%') + } +}`; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/waterflow/viewmodel/WaterFlowDescriptor.ets b/features/componentlibrary/src/main/ets/componentdetailview/waterflow/viewmodel/WaterFlowDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..548cf74a7dcbb472c5401bb8f9e928f124c538ba --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/waterflow/viewmodel/WaterFlowDescriptor.ets @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 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 { StringUtil } from '../../../util/StringUtil'; +import type { OriginAttribute } from '../../../viewmodel/Attribute'; +import { CommonDescriptor } from '../../../viewmodel/CommonDescriptor'; +import { + columnsGapMapData, + columnsTemplateMapData, + frictionMapData, + layoutDirectionMapData, + rowsTemplateMapData, +} from '../entity/WaterFlowAttributeMapping'; + +@Observed +export class WaterFlowDescriptor extends CommonDescriptor { + public layoutDirection: FlexDirection = layoutDirectionMapData.get('Default')!.value as FlexDirection; + public friction: number = frictionMapData.get('Default')!.value as number; + public columnsTemplate: string = columnsTemplateMapData.get('Default')!.value as string; + public rowsTemplate: string = rowsTemplateMapData.get('Default')!.value as string; + public columnsGap: number = columnsGapMapData.get('Default')!.value as number; + + public convert(attributes: OriginAttribute[]): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'layoutDirection': + this.layoutDirection = layoutDirectionMapData.get(attribute.currentValue)?.value as FlexDirection ?? + layoutDirectionMapData.get('Default')!.value as FlexDirection; + break; + case 'friction': + this.friction = + frictionMapData.get(attribute.currentValue)?.value as number ?? + frictionMapData.get('Default')!.value as number; + break; + case 'columnsTemplate': + this.columnsTemplate = + StringUtil.getTemplateString(Number(attribute.currentValue)) ?? + columnsTemplateMapData.get('Default')!.value as string; + break; + case 'rowsTemplate': + this.rowsTemplate = + StringUtil.getTemplateString(Number(attribute.currentValue)) ?? rowsTemplateMapData.get('Default')!.value as string; + break; + case 'columnsGap': + this.columnsGap = Number(attribute.currentValue) ?? columnsGapMapData.get('Default')!.value as number; + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/componentdetailview/waterflow/viewmodel/WaterflowAttributeFilter.ets b/features/componentlibrary/src/main/ets/componentdetailview/waterflow/viewmodel/WaterflowAttributeFilter.ets new file mode 100644 index 0000000000000000000000000000000000000000..7440ccaab4a38dbc03b71e624f6754ea163e53d1 --- /dev/null +++ b/features/componentlibrary/src/main/ets/componentdetailview/waterflow/viewmodel/WaterflowAttributeFilter.ets @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 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 type { ObservedArray } from '@ohos/common'; +import type { Attribute } from '../../../viewmodel/Attribute'; +import { CommonAttributeFilter } from '../../../viewmodel/CommonAttributeFilter'; + +export class WaterFlowAttributeFilter implements CommonAttributeFilter { + filter(attributes: ObservedArray): void { + attributes.forEach((attribute) => { + switch (attribute.name) { + case 'layoutDirection': + const rowsTemplateIndex = attributes.findIndex((item) => item.name === 'rowsTemplate'); + const columnsTemplateIndex = attributes.findIndex((item) => item.name === 'columnsTemplate'); + if (rowsTemplateIndex !== -1 && columnsTemplateIndex !== -1) { + if (attribute.currentValue === 'Column' || attribute.currentValue === 'ColumnReverse') { + attributes[rowsTemplateIndex].enable = false; + attributes[columnsTemplateIndex].enable = true; + } else if (attribute.currentValue === 'Row' || attribute.currentValue === 'RowReverse') { + attributes[rowsTemplateIndex].enable = true; + attributes[columnsTemplateIndex].enable = false; + } + } + break; + default: + break; + } + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/constant/DetailPageConstant.ets b/features/componentlibrary/src/main/ets/constant/DetailPageConstant.ets new file mode 100644 index 0000000000000000000000000000000000000000..2d123606e2bda369fbcfecaecd3c3934f46e40cb --- /dev/null +++ b/features/componentlibrary/src/main/ets/constant/DetailPageConstant.ets @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 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 DetailPageConstant { + public static GRID_POPUP_OFFSET_Y: number = -10; + public static IMAGE_POPUP_OFFSET_Y: number = -50; + public static LONG_DURATION: number = 2000; + public static ANIMATION_DURATION: number = 500; + public static ANIMATION_DURATION_SHORT: number = 300; + public static CUSTOM_DIALOG_CONTENT_HEIGHT: number = 100; + public static DETAIL_SELECT_COMPONENT_HEIGHT: number = 48; + public static WATER_FLOW_MIN_SIZE: number = 92; + public static WATER_FLOW_MAX_SIZE: number = 180; + public static SWIPER_BACKGROUND_COLOR: string = 'rgba(0, 85, 255, 0.1)'; + public static IMAGE_OPACITY: number = 0.4; + public static IMAGE_OPACITY_BG: number = 0.2; + public static PROGRESS_MAX_VALUE: number = 100; + public static PROGRESS_CIRCLE_WIDTH: number = 64; + public static PROGRESS_LINE_HEIGHT: number = 48; + public static PROGRESS_CAPSULE_HEIGHT: number = 24; + public static SCALE_LEVEL1: number = 1.0; + public static ASPECT_RATIO_SQUARE: number = 1; + public static CODEPREVIEW_IMMERSIVE_HEIGHT = 16; + // AspectRatio invalid value + public static ASPECT_RATIO_INVALID: number = -1; + // Threshold for the number of columns + public static LIST_LANES_THRESHOLD: number = 4; + public static ALERT_DIALOG_OFFSET_Y: number = -20; + public static ACTION_SHEET_OFFSET_Y: number = -10; + public static SELECT_RESULT_DIALOG_SIZE: number = 300; + public static MARGIN_NEGATIVE_LARGER: number = -95; + // Space definition. + public static SPACE_NORMAL: number = 8; + public static SPACE_SMALL: number = 5; + public static SPACE_LARGE: number = 20; + // Opacity level. + public static OPACITY_SMALL: number = 0.2; + // toggle. + public static TOGGLE_WIDTH: number = 120; + public static TOGGLE_HEIGHT: number = 28; + // column. + public static CONTAINER_BORDER: number = 1; + // flex. + public static FLEX_SPACE: number = 6; + // Page Area Size. + public static PREVIEW_HEIGHT_SM: number = 284; + public static PREVIEW_SUB_HEIGHT_SM: number = 230; + public static ATTRIBUTE_ITEM_HEIGHT: number = 56; + public static TEXT_TIP_HEIGHT: number = 50; + // Font line height. + public static LINE_HEIGHT_LARGE: number = 16; + // Offset level. + public static SCROLL_OFFSET_Y: number = 20; + public static MENU_ITEM_HEIGHT: number = 46; + public static TEXT_MAX_LINES: number = 3; + // CalendarPicker hintRadius number + public static DATE_HINT_RADIUS: number = 10; + // Animation duration for component displacement when the keyboard appears + public static UP_DURATION: number = 300; + public static CALENDAR_DEFAULT_FONT_SIZE: number = 20; + public static CALENDAR_DEFAULT_FONT_SIZE2: number = 16; + public static COMPONENT_GAP_SIZE: number = 20; + public static COMPONENT_GAP_SIZE2: number = 40; + // The offset of the popup component relative to the display position defined by the placement setting. + public static HOVER_POPUP_LEFT: number = 10; + public static HOVER_POPUP_TOP: number = 8; + public static PEN_CLOSE_TOP: number = 56; +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/model/ComponentData.ets b/features/componentlibrary/src/main/ets/model/ComponentData.ets new file mode 100644 index 0000000000000000000000000000000000000000..a50e8566a0ae4ef83ac0265166a2e5b66595cb87 --- /dev/null +++ b/features/componentlibrary/src/main/ets/model/ComponentData.ets @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 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 type { BannerData } from '@ohos/commonbusiness'; +import { CardStyleTypeEnum, CardTypeEnum, MediaTypeEnum } from '@ohos/commonbusiness'; + +@Observed +export class ComponentContent { + public id: number = 0; + public type: CardTypeEnum = CardTypeEnum.UNKNOWN; + public mediaType: MediaTypeEnum = MediaTypeEnum.IMAGE; + public mediaUrl: string = ''; + public title: string = ''; + public subTitle: string = ''; + public desc: string = ''; +} + +@Observed +export class ComponentCardData { + public id: number = 0; + public cardTitle: string = ''; + public cardSubTitle: string = ''; + public cardType: CardTypeEnum = CardTypeEnum.UNKNOWN; + public cardStyleType: CardStyleTypeEnum = CardStyleTypeEnum.LIST; + public cardImage: string = ''; + public version: string = ''; + public cardContents: ComponentContent[] = []; +} + +export class ComponentData { + public bannerInfos?: BannerData[]; + public cardData: ComponentCardData[] = []; +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/model/ComponentDetailData.ets b/features/componentlibrary/src/main/ets/model/ComponentDetailData.ets new file mode 100644 index 0000000000000000000000000000000000000000..2d79d9cad19757d6c409a28eb18886452624a1df --- /dev/null +++ b/features/componentlibrary/src/main/ets/model/ComponentDetailData.ets @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 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 ComponentResult { + code: number; + message: string; + data: ComponentDetailData; +} + +export interface ComponentDetailData { + id: number; + componentName: string; + type: number; + isFavorite: boolean; + props: AttributeData[]; + recommendList?: RecommendData[]; +} + +export interface AttributeData { + propertyName: string; + propertyDesc: string; + displayType: string; + defaultProperty: string; + propertyValues: string; +} + +export interface RecommendData { + id?: number; + title: ResourceStr; + subTitle?: string; + articleType: number; + url: string; +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/model/ComponentDetailModel.ets b/features/componentlibrary/src/main/ets/model/ComponentDetailModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..25930c4b7f569884db15eca87c912a1f2da6f04a --- /dev/null +++ b/features/componentlibrary/src/main/ets/model/ComponentDetailModel.ets @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 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 { Logger } from '@ohos/common'; +import { ComponentLibraryService } from '../service/ComponentLibraryService'; +import type { ComponentDetailData } from './ComponentDetailData'; + +const TAG = '[ComponentDetailModel]'; + +export class ComponentDetailModel { + private static instance: ComponentDetailModel; + private service: ComponentLibraryService = new ComponentLibraryService(); + + private constructor() { + } + + public static getInstance(): ComponentDetailModel { + if (!ComponentDetailModel.instance) { + ComponentDetailModel.instance = new ComponentDetailModel(); + } + return ComponentDetailModel.instance; + } + + public init(id: number): Promise { + return this.service.getComponentDetailByPreference(id).then((data: ComponentDetailData) => { + return data; + }).catch((err: string) => { + Logger.error(TAG, `get data from network failed! try to get data form preference. ${err}`); + return this.service.getComponentDetail(id) + .then((data: ComponentDetailData) => { + this.service.setComponentDetailToPreference(id, data); + return data; + }); + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/model/ComponentListModel.ets b/features/componentlibrary/src/main/ets/model/ComponentListModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..809b2c1e2de9675a4df034ea9db6c5cff073dd29 --- /dev/null +++ b/features/componentlibrary/src/main/ets/model/ComponentListModel.ets @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 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 type { BusinessError } from '@kit.BasicServicesKit'; +import type { ResponseData } from '@ohos/common'; +import { Logger } from '@ohos/common'; +import { ComponentLibraryService } from '../service/ComponentLibraryService'; +import type { ComponentData } from './ComponentData'; +import { ComponentDetailData } from './ComponentDetailData'; +import { componentDetailConfig } from '../componentdetailview/ComponentDetailConfig'; + +const TAG = '[ComponentListModel]'; + +export class ComponentListModel { + private service: ComponentLibraryService = new ComponentLibraryService(); + private static instance: ComponentListModel; + + private constructor() { + } + + public static getInstance(): ComponentListModel { + if (!ComponentListModel.instance) { + ComponentListModel.instance = new ComponentListModel(); + } + return ComponentListModel.instance; + } + + public getComponentPage(currentPage: number, pageSize: number): Promise> { + return this.service.getComponentListByPreference(currentPage, pageSize) + .then((data: ResponseData) => { + return data; + }).catch((err: string) => { + Logger.error(TAG, + `getComponentPage data from network failed! try to get data form preference. ${err}`); + return this.service.getComponentList(currentPage, pageSize) + .then((data: ResponseData) => { + this.service.setComponentListToPreference(data); + return data; + }); + }); + } + + public preloadComponentData(): Promise { + AppStorage.setOrCreate('componentDetailConfig', componentDetailConfig); + this.service.getComponentDetailList().then((detailList: ComponentDetailData[]) => { + this.service.setDetailsToPreference(detailList); + }); + return this.service.getComponentList() + .then((result: ResponseData) => { + this.service.setComponentListToPreference(result); + }).catch((err: BusinessError) => { + Logger.error(TAG, + `preloadComponentPage data from network failed. ${err.code}, ${err.message}`); + }); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/service/ComponentLibraryService.ets b/features/componentlibrary/src/main/ets/service/ComponentLibraryService.ets new file mode 100644 index 0000000000000000000000000000000000000000..57897bd81027b72ea4a83b38f772e9b2bd1b63a0 --- /dev/null +++ b/features/componentlibrary/src/main/ets/service/ComponentLibraryService.ets @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2024 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 type { BusinessError } from '@kit.BasicServicesKit'; +import type { ResponseData } from '@ohos/common'; +import { Logger, MockRequest, PreferenceManager } from '@ohos/common'; +import type { ComponentData } from '../model/ComponentData'; +import type { ComponentDetailData } from '../model/ComponentDetailData'; + +const TAG = '[ComponentLibraryService]'; + +export class ComponentLibraryService { + private static readonly MAIN_PAGE_DATA = 'COMPONENT_LIBRARY_MAIN_PAGE_DATA'; + private static readonly DETAIL_PAGE_DATA = 'COMPONENT_LIBRARY_DETAIL_PAGE_DATA'; + + constructor() { + } + + public getComponentListByMock(): Promise> { + return new Promise((resolve: (value: ResponseData) => void, + reject: (reason?: Object) => void) => { + MockRequest.call>(ComponentLibraryTrigger.COMPONENT_PAGE) + .then((result: Object) => { + resolve(result as ResponseData); + }) + .catch((error: BusinessError) => { + reject(error); + }); + }); + } + + public getComponentList(currentPage?: number, pageSize?: number): Promise> { + Logger.info(TAG, `getComponentList param: currentPage ${currentPage}, pageSize: ${pageSize} `); + return this.getComponentListByMock(); + } + + public getComponentListByPreference(currentPage: number, pageSize: number): Promise> { + return new Promise((resolve: (value: ResponseData) => void, + reject: (reason?: string) => void) => { + PreferenceManager.getInstance() + .getValue>>(ComponentLibraryService.MAIN_PAGE_DATA) + .then((resp) => { + if (!resp) { + reject('There is no data in the Preference'); + } + resp = (resp as Record>); + const ret = resp[`${currentPage}_${pageSize}`]; + if (!ret) { + reject('There is no data in the Preference'); + } + resolve(ret); + }) + .catch((error: BusinessError) => { + Logger.error(TAG, `get getComponentListByPreference failed, error: ${error.code}, ${error.message}`); + reject(error.message); + }); + }); + } + + public setComponentListToPreference(data: ResponseData): Promise { + return new Promise((resolve: () => void) => { + PreferenceManager.getInstance().hasValue(ComponentLibraryService.MAIN_PAGE_DATA) + .then((result) => { + if (result) { + PreferenceManager.getInstance() + .getValue>>(ComponentLibraryService.MAIN_PAGE_DATA) + .then((resp) => { + resp = (resp as Record>); + resp[`${data.currentPage}_${data.pageSize}`] = data; + PreferenceManager.getInstance().setValue(ComponentLibraryService.MAIN_PAGE_DATA, resp); + resolve(); + }); + } else { + const record: Record> = {}; + record[`${data.currentPage}_${data.pageSize}`] = data; + PreferenceManager.getInstance().setValue(ComponentLibraryService.MAIN_PAGE_DATA, record); + } + }); + }); + } + + public getComponentDetail(componentId: number): Promise { + return new Promise((resolve: (value: ComponentDetailData) => void, reject: (reason?: Object) => void) => { + this.getComponentDetailListByMock().then((result: ComponentDetailData[]) => { + const item = result.find((item: ComponentDetailData) => item.id === componentId); + if (item !== undefined) { + resolve(item); + } else { + Logger.error(TAG, `getComponentDetailListByMock failed, error: can't find detail data by id ${componentId}`); + reject(); + } + }); + }); + } + + public getComponentDetailByPreference(componentId: number): Promise { + return new Promise((resolve: (value: ComponentDetailData) => void, + reject: (reason?: Object) => void) => { + PreferenceManager.getInstance() + .getValue>(ComponentLibraryService.DETAIL_PAGE_DATA) + .then((resp) => { + if (!resp) { + reject('There is no data in the Preference'); + } + resp = (resp as Record) + const ret = resp[String(componentId)]; + if (!ret) { + reject('There is no data in the Preference'); + } + resolve(ret); + }); + }); + } + + public setComponentDetailToPreference(componentId: number, data: ComponentDetailData): Promise { + return new Promise((resolve: () => void) => { + PreferenceManager.getInstance().hasValue(ComponentLibraryService.DETAIL_PAGE_DATA) + .then((result) => { + if (result) { + PreferenceManager.getInstance() + .getValue>(ComponentLibraryService.DETAIL_PAGE_DATA) + .then((resp) => { + resp = (resp as Record); + resp[String(componentId)] = data; + PreferenceManager.getInstance().setValue(ComponentLibraryService.DETAIL_PAGE_DATA, resp); + resolve(); + }) + } else { + const record: Record = {}; + record[String(componentId)] = data; + PreferenceManager.getInstance().setValue(ComponentLibraryService.DETAIL_PAGE_DATA, record); + } + }); + }); + } + + public setDetailsToPreference(details: ComponentDetailData[]): Promise { + return new Promise((resolve: () => void) => { + PreferenceManager.getInstance().hasValue(ComponentLibraryService.DETAIL_PAGE_DATA) + .then((result) => { + if (result) { + PreferenceManager.getInstance() + .getValue>(ComponentLibraryService.DETAIL_PAGE_DATA) + .then((resp: Record | null) => { + const record: Record = resp || {}; + details.forEach((detail: ComponentDetailData) => { + record[String(detail.id)] = detail; + }) + PreferenceManager.getInstance().setValue(ComponentLibraryService.DETAIL_PAGE_DATA, record); + resolve(); + }) + } else { + const record: Record = {}; + details.forEach((detail: ComponentDetailData) => { + record[String(detail.id)] = detail; + }); + PreferenceManager.getInstance().setValue(ComponentLibraryService.DETAIL_PAGE_DATA, record); + } + }); + }); + } + + public getComponentDetailListByMock(): Promise { + return new Promise((resolve: (value: ComponentDetailData[]) => void, + reject: (reason?: Object) => void) => { + MockRequest.call(ComponentLibraryTrigger.COMPONENT_DETAIL_LIST) + .then((result: ComponentDetailData[]) => { + const details: ComponentDetailData[] = result as ComponentDetailData[]; + this.setDetailsToPreference(details); + resolve(result as ComponentDetailData[]); + }) + .catch((error: BusinessError) => { + reject(error); + }); + }); + } + + public getComponentDetailList(): Promise { + return this.getComponentDetailListByMock(); + } +} + +enum ComponentLibraryTrigger { + COMPONENT_PAGE = 'component-page', + COMPONENT_DETAIL = 'component-details', + COMPONENT_DETAIL_LIST = 'file-data', + CODELAB_DETAIL = 'codelab-details', +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/util/CodePreviewJSUtil.ets b/features/componentlibrary/src/main/ets/util/CodePreviewJSUtil.ets new file mode 100644 index 0000000000000000000000000000000000000000..fe946aa3116de4f353b34d2ff0a610ea79fa4dce --- /dev/null +++ b/features/componentlibrary/src/main/ets/util/CodePreviewJSUtil.ets @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 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 type { BusinessError } from '@kit.BasicServicesKit'; +import { Logger, WebUtil } from '@ohos/common'; + +const TAG: string = '[CodePreviewJSUtil]'; + +export class CodePreviewJSUtil { + public static codeViewRunJS(jsMethod: string, params?: string, callback?: Function): void { + if (WHITE_JS_METHODS.indexOf(jsMethod) >= 0) { + let runMethod = jsMethod; + if (params && params.trim() !== '') { + runMethod = runMethod.replace('%param', params); + } + try { + const promise = WebUtil.getWebController(WebUtil.getComponentCodeUrl())?.runJavaScript(runMethod); + callback && promise?.then(() => { + callback(); + }) + } catch (err) { + const error: BusinessError = err as BusinessError; + Logger.error(TAG, `RunJavaScript error, the code is ${error.code}, the message is ${error.message}`); + } + } else { + Logger.error(TAG, `Input method ${jsMethod} not in whitelist`); + } + } +} + +const WHITE_JS_METHODS = [ + 'changeColorMode(%param)', + 'showPortraitView()', + 'codeToHtml(%param)', + 'toFullScreen()', + 'toSmallScreen()', + 'showLandscapeView(%param)', +]; \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/util/StringUtil.ets b/features/componentlibrary/src/main/ets/util/StringUtil.ets new file mode 100644 index 0000000000000000000000000000000000000000..15e4dbcb88fc67da82c514198eabd8eeaabf207f --- /dev/null +++ b/features/componentlibrary/src/main/ets/util/StringUtil.ets @@ -0,0 +1,32 @@ +/* + * 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 StringUtil { + public static getTemplateString(num: number): string { + let str = ''; + for (let i = 0; i < num; i++) { + str += '1fr '; + } + return str.trim(); + } + + // Translate the input numeric string into an array of individual character numbers. + public static stringToArray(inputStr: string): number[] { + if (inputStr.trim() === '') { + return []; + } + return inputStr.split(',').map(Number); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/view/CodePreview.ets b/features/componentlibrary/src/main/ets/view/CodePreview.ets new file mode 100644 index 0000000000000000000000000000000000000000..47c3a8fd4e7f7fe67ae54f662ec7523266153a0b --- /dev/null +++ b/features/componentlibrary/src/main/ets/view/CodePreview.ets @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2024 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 { ConfigurationConstant } from '@kit.AbilityKit'; +import { curves, window } from '@kit.ArkUI'; +import { BusinessError, settings } from '@kit.BasicServicesKit'; +import { type GlobalInfoModel, Logger, CommonConstants } from '@ohos/common'; +import { BreakpointTypeEnum, ScreenOrientation, WebNodeController, WebUtil, WindowUtil } from '@ohos/common'; +import { CodePreviewComponent } from '../component/CodePreviewComponent'; +import { CodePreviewJSUtil } from '../util/CodePreviewJSUtil'; +import { CodePreviewEvent, CodePreviewPageVM } from '../viewmodel/CodePreviewPageVM'; +import type { CodePreviewState } from '../viewmodel/CodePreviewState'; +import { CodeViewParams } from '../viewmodel/ComponentDetailPageVM'; + +const TAG: string = '[CodePreview]'; +const ORIENTATION_LOCK_ON: string = '0'; + +@Component +export struct CodePreview { + @StorageLink('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @StorageProp('systemColorMode') systemColorMode: ConfigurationConstant.ColorMode = AppStorage.get('systemColorMode')!; + @State webNodeController?: WebNodeController = undefined; + @State isFocus: boolean = false; + @State isBack: boolean = false; + @State isOrientationLockChange: boolean = false; + private viewModel?: CodePreviewPageVM; + @State codePreviewState?: CodePreviewState = this.viewModel?.getState(); + private beginScreenOrientation: string = ScreenOrientation.PORTRAIT; + private beginBreakpoint: BreakpointTypeEnum = this.globalInfoModel.currentBreakpoint; + private beginOrientationLockState?: string; + private code: string = ''; + private componentName: string = ''; + + onBackPage(): void { + this.isBack = true; + WindowUtil.updateStatusBarColor(getContext(this), + this.systemColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK); + this.javascriptRun(); + this.webNodeController?.remove(); + this.webNodeController = undefined; + this.resetScreenOrientation(); + this.preFinishAnimation(); + animateTo({ curve: curves.interpolatingSpring(0, 1, 342, 38) }, () => { + this.viewModel?.pop(false); + }); + } + + javascriptRun(): void { + const toSmallScreenMethod: string = 'toSmallScreen()'; + const changeColorModeMethod: string = 'changeColorMode(%param)'; + const changeColorModeParams: string = JSON.stringify(this.systemColorMode); + CodePreviewJSUtil.codeViewRunJS(toSmallScreenMethod); + CodePreviewJSUtil.codeViewRunJS(changeColorModeMethod, changeColorModeParams); + } + + aboutToAppear(): void { + this.viewModel = CodePreviewPageVM.getInstance(); + this.initOrientationState(); + this.viewModel.sendEvent(CodePreviewEvent.INIT); + } + + aboutToDisappear(): void { + WindowUtil.setMainWindowOrientation(getContext(), window.Orientation.UNSPECIFIED); + if (canIUse('SystemCapability.Applications.Settings.Core')) { + settings.unregisterKeyObserver(getContext(), settings.general.ACCELEROMETER_ROTATION_STATUS, + settings.domainName.DEVICE_SHARED); + } + } + + private initOrientationState(): void { + if (this.globalInfoModel.deviceWidth > this.globalInfoModel.deviceHeight) { + this.beginScreenOrientation = ScreenOrientation.LANDSCAPE; + } else { + this.beginScreenOrientation = ScreenOrientation.PORTRAIT; + } + if (canIUse('SystemCapability.Applications.Settings.Core')) { + this.beginOrientationLockState = + settings.getValueSync(getContext(), settings.general.ACCELEROMETER_ROTATION_STATUS, + settings.domainName.DEVICE_SHARED); + this.registerKeyObserver(); + } + } + + private registerKeyObserver(): boolean { + if (canIUse('SystemCapability.Applications.Settings.Core')) { + try { + return settings.registerKeyObserver(getContext(), settings.general.ACCELEROMETER_ROTATION_STATUS, + settings.domainName.DEVICE_SHARED, () => { + this.isOrientationLockChange = true; + }); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, + `RegisterKeyObserver ${settings.general.ACCELEROMETER_ROTATION_STATUS} error: ${err.code}, ${err.message}`); + return false; + } + } + return false; + } + + private resetScreenOrientation(): void { + if (!this.isOrientationLockChange && this.beginOrientationLockState === ORIENTATION_LOCK_ON) { + if (this.beginBreakpoint === BreakpointTypeEnum.LG || this.beginBreakpoint === BreakpointTypeEnum.MD) { + if (this.beginScreenOrientation === ScreenOrientation.LANDSCAPE) { + WindowUtil.setMainWindowOrientation(getContext(), window.Orientation.LANDSCAPE); + } else { + WindowUtil.setMainWindowOrientation(getContext(), window.Orientation.PORTRAIT); + } + } + } + } + + build() { + NavDestination() { + Column() { + CodePreviewComponent({ + onBackPage: () => { + this.onBackPage(); + }, + webNodeController: this.webNodeController, + code: this.code, + componentName: this.componentName, + pageContainer: true, + topTranslateY: this.codePreviewState?.topTranslateY, + bottomTranslateY: this.codePreviewState?.bottomTranslateY, + navigationOpacity: this.codePreviewState?.navigationOpacity, + isFocus: this.isFocus, + isBack: this.isBack, + }) + } + .geometryTransition(CommonConstants.CODE_PREVIEW_GEOMETRY_ID) + .width('100%') + .height('100%') + } + .width('100%') + .height('100%') + .hideTitleBar(true) + .onReady((ctx: NavDestinationContext) => { + const params: CodeViewParams = ctx.pathInfo.param as CodeViewParams; + this.code = params.code as string; + this.componentName = params.componentName as string; + this.webNodeController = WebUtil.getWebNode(WebUtil.getComponentCodeUrl()) as WebNodeController; + this.preFinishAnimation = params.preFinishAnimation as () => void; + this.codePreviewState = this.viewModel!.getState(); + }) + .onFocus(() => { + this.isFocus = true; + }) + .onBlur(() => { + this.isFocus = false; + }) + .onShown(() => { + this.isBack = false; + }) + .onBackPressed(() => { + this.onBackPage(); + return true; + }) + } + + private preFinishAnimation: () => void = () => { + }; +} + +@Builder +export function CodePreviewBuilder() { + CodePreview() +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/view/ComponentDetailView.ets b/features/componentlibrary/src/main/ets/view/ComponentDetailView.ets new file mode 100644 index 0000000000000000000000000000000000000000..c060540b144ee1da772a6f11943185cd689b1dd6 --- /dev/null +++ b/features/componentlibrary/src/main/ets/view/ComponentDetailView.ets @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2024 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 { ConfigurationConstant } from '@kit.AbilityKit'; +import { displaySync } from '@kit.ArkGraphics2D'; +import { + CommonConstants, + LoadingStatus, + TopNavigationView, + WebUtil, + WindowUtil, +} from '@ohos/common'; +import { BaseDetailComponent, ComponentDetailParams } from '@ohos/commonbusiness'; +import { + ComponentDetailEvent, + ComponentDetailEventType, + ComponentDetailPageVM, + InitComponentEvent, +} from '../viewmodel/ComponentDetailPageVM'; +import { DetailContentView } from '../component/DetailContentView'; +import type { ComponentDetailState } from '../viewmodel/ComponentDetailState'; +import { ComponentDetailManager } from '../viewmodel/ComponentDetailManager'; +import { CodePreviewJSUtil } from '../util/CodePreviewJSUtil'; +import { RecommendData } from '../model/ComponentDetailData'; + +@Component({ freezeWhenInactive: true }) +export struct ComponentDetailView { + @StorageProp('systemColorMode') systemColorMode: ConfigurationConstant.ColorMode = AppStorage.get('systemColorMode')!; + @StorageLink('webIsLoading') @Watch('sendCodeToWeb') webIsLoading: boolean = false; + @Prop componentName: string = ''; + @Prop componentId: number = 0; + private displaySync = displaySync.create(); + private viewModel?: ComponentDetailPageVM; + @State componentDetailState?: ComponentDetailState = this.viewModel?.getState(); + @State loadingStatus: LoadingStatus = LoadingStatus.IDLE; + + aboutToAppear(): void { + const isSystemDark: boolean = (this.systemColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK); + WindowUtil.updateStatusBarColor(getContext(this), isSystemDark); + } + + aboutToDisappear(): void { + CommonConstants.PROMISE_WAIT(CommonConstants.REMOVE_DURATION).then(() => { + this.componentDetailState?.recommends.forEach((item: RecommendData) => { + WebUtil.removeNode(item.url); + }); + }); + } + + @Builder + DetailContentBuilder() { + DetailContentView({ + componentDetailState: this.componentDetailState, + componentName: this.componentName, + }) + .layoutWeight(1) + } + + @Builder + TopTitleViewBuilder() { + TopNavigationView({ + topNavigationData: this.componentDetailState?.topNavigationData, + }) + } + + build() { + NavDestination() { + BaseDetailComponent({ + detailContentView: () => { + this.DetailContentBuilder() + }, + topTitleView: () => { + this.TopTitleViewBuilder() + }, + loadingStatus: this.loadingStatus, + }) + .expandSafeArea([SafeAreaType.KEYBOARD, SafeAreaType.SYSTEM]) + } + .hideTitleBar(true) + .height('100%') + .backgroundColor($r('sys.color.background_secondary')) + .onDisAppear(() => { + this.displaySync.stop(); + }) + .onReady((ctx: NavDestinationContext) => { + const params: ComponentDetailParams = ctx.pathInfo.param as ComponentDetailParams; + this.componentName = params.componentName as string; + this.componentId = Number(params.componentId); + CommonConstants.PROMISE_WAIT(500).then(() => { + this.assignViewData(); + }); + }) + } + + assignViewData() { + this.viewModel = ComponentDetailManager.getInstance().getDetailViewModel(this.componentName); + if (!this.viewModel) { + this.viewModel = new ComponentDetailPageVM(this.componentName); + } + const range: ExpectedFrameRateRange = { expected: 120, min: 60, max: 120 }; + this.displaySync.setExpectedFrameRateRange(range); + let frameCount = 0; + const eventList: ComponentDetailEventType[] = [ + ComponentDetailEventType.INIT_DESCRIPTOR, + ComponentDetailEventType.INIT_RECOMMEND, + ComponentDetailEventType.WEB_CODE_EVENT, + ]; + const eventCount = eventList.length; + this.viewModel?.sendEvent(new InitComponentEvent(this.componentId))?.then(() => { + this.componentDetailState = this.viewModel?.getState(); + this.loadingStatus = LoadingStatus.LOADING; + this.displaySync.on('frame', () => { + const promise = this.viewModel?.sendEvent(new ComponentDetailEvent(eventList[frameCount])); + this.componentDetailState = this.viewModel?.getState(); + frameCount++; + if (frameCount >= eventCount) { + promise?.then(() => { + this.displaySync.stop(); + this.refreshCodePreviewWeb(); + }); + } + }); + this.displaySync.start(); + }); + } + + refreshCodePreviewWeb() { + this.webIsLoading = true; + WebUtil.getWebController(WebUtil.getComponentCodeUrl())?.refresh(); + } + + sendCodeToWeb() { + if (this.loadingStatus === LoadingStatus.LOADING && !this.webIsLoading) { + const codeToHtmlMethod: string = 'codeToHtml(%param)'; + const params: string = `${JSON.stringify(this.componentDetailState?.code)}, ${this.systemColorMode}`; + CodePreviewJSUtil.codeViewRunJS(codeToHtmlMethod, params, () => { + this.loadingStatus = LoadingStatus.SUCCESS; + }); + } + } +} + +@Builder +export function ComponentDetailBuilder() { + ComponentDetailView() +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/view/ComponentListView.ets b/features/componentlibrary/src/main/ets/view/ComponentListView.ets new file mode 100644 index 0000000000000000000000000000000000000000..8eeed6fadefcb25320136ca468bccb2e9b179ad8 --- /dev/null +++ b/features/componentlibrary/src/main/ets/view/ComponentListView.ets @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2024 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 { ConfigurationConstant } from '@kit.AbilityKit'; +import type { GlobalInfoModel, PageContext } from '@ohos/common'; +import { BreakpointType, CommonConstants } from '@ohos/common'; +import type { BannerData } from '@ohos/commonbusiness'; +import { + BannerCard, + BaseHomeEventType, + BaseHomeView, + CalculateHeightParam, + CardStyleTypeEnum, + CardTypeEnum, + FullScreenNavigation, + LoadingMoreItemBuilder, + OffsetParam, + TabBarType, +} from '@ohos/commonbusiness'; +import { CodeLabCard } from '../component/CodeLabCard'; +import { ListCard } from '../component/ListCard'; +import { PictureListCard } from '../component/PictureListCard'; +import type { ComponentCardData, ComponentContent } from '../model/ComponentData'; +import type { ComponentListState } from '../viewmodel/ComponentListState'; +import { ComponentListEventType, ComponentListViewModel } from '../viewmodel/ComponentListViewModel'; + +@Component({ freezeWhenInactive: true }) +export struct ComponentListView { + @StorageProp('GlobalInfoModel') @Watch('handleBreakPointChange') globalInfoModel: GlobalInfoModel = + AppStorage.get('GlobalInfoModel')!; + @StorageProp('systemColorMode') @Watch('handleColorModeChange') systemColorMode: ConfigurationConstant.ColorMode = + AppStorage.get('systemColorMode')!; + viewModel: ComponentListViewModel = ComponentListViewModel.getInstance(); + @State componentListState: ComponentListState = this.viewModel.getState(); + private componentListPageContext: PageContext = AppStorage.get('componentListPageContext')!; + private scroller: Scroller = new Scroller(); + + aboutToAppear(): void { + this.viewModel.sendEvent({ type: ComponentListEventType.LOAD_COMPONENT_PAGE, param: null }); + } + + handleBreakPointChange() { + this.viewModel.sendEvent({ type: ComponentListEventType.UPDATE_FLOW_SECTION, param: null }); + this.viewModel.sendEvent({ + type: BaseHomeEventType.HANDLE_BREAKPOINT_CHANGE, + param: { yOffset: (this.scroller?.currentOffset()?.yOffset || 0), tabIndex: TabBarType.HOME }, + }); + } + + handleColorModeChange() { + this.viewModel.sendEvent({ + type: BaseHomeEventType.HANDLE_COLOR_CHANGE, + param: { yOffset: (this.scroller?.currentOffset()?.yOffset || 0), tabIndex: TabBarType.HOME }, + }); + } + + jumpComponentDetailView(componentContent: ComponentContent) { + this.viewModel.sendEvent({ + type: ComponentListEventType.JUMP_DETAIL_DETAIL, + param: componentContent, + }); + } + + jumpCodelabDetailView(componentCard: ComponentCardData) { + if (componentCard.cardType === CardTypeEnum.COMPONENT) { + this.jumpComponentDetailView(componentCard.cardContents?.[0]); + } + } + + jumpBannerDetail(banner: BannerData) { + this.viewModel.sendEvent({ type: BaseHomeEventType.JUMP_BANNER_DETAIL, param: banner }); + } + + @Builder + ContentViewBuilder() { + WaterFlow({ + scroller: this.scroller, + sections: this.componentListState.sections, + }) { + FlowItem() { + BannerCard({ + tabViewType: TabBarType.HOME, + bannerState: this.componentListState.bannerState, + handleItemClick: (banner: BannerData) => { + this.jumpBannerDetail(banner); + }, + }) + } + .width('100%') + + LazyForEach(this.componentListState.cardSource, (item: ComponentCardData) => { + FlowItem() { + if (item.cardStyleType === CardStyleTypeEnum.PICTURE_ABOVE_LIST) { + PictureListCard({ + componentCardData: item, + handleItemClick: (componentContent: ComponentContent) => { + this.jumpComponentDetailView(componentContent); + }, + }) + .reuseId(CardStyleTypeEnum.PICTURE_ABOVE_LIST.toString()) + } else if (item.cardStyleType === CardStyleTypeEnum.LIST) { + ListCard({ + componentCardData: item, + handleItemClick: (componentContent: ComponentContent) => { + this.jumpComponentDetailView(componentContent); + }, + }) + .reuseId(CardStyleTypeEnum.LIST.toString()) + } else { + CodeLabCard({ componentCardData: item }) + .reuseId(CardStyleTypeEnum.PICTURE.toString()) + .onClick(() => { + this.jumpCodelabDetailView(item); + }) + } + } + .width('100%') + }, (item: ComponentCardData) => item.id.toString()) + FlowItem() { + LoadingMoreItemBuilder(this.componentListState.loadingModel) + } + .width('100%') + .padding({ + bottom: (this.globalInfoModel.naviIndicatorHeight + + (new BreakpointType({ + sm: CommonConstants.TAB_BAR_HEIGHT, + md: CommonConstants.TAB_BAR_HEIGHT, + lg: 0, + }).getValue(this.globalInfoModel.currentBreakpoint))), + }) + } + .columnsGap(new BreakpointType({ + sm: $r('sys.float.padding_level6'), + md: $r('sys.float.padding_level6'), + lg: $r('sys.float.padding_level8'), + xl: $r('sys.float.padding_level8'), + }).getValue(this.globalInfoModel.currentBreakpoint)) + .rowsGap(new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level6'), + lg: $r('sys.float.padding_level8'), + xl: $r('sys.float.padding_level12') + }).getValue(this.globalInfoModel.currentBreakpoint)) + .edgeEffect(this.componentListState.hasEdgeEffect ? EdgeEffect.Spring : EdgeEffect.None) + .onScrollFrameBegin((offset: number, state: ScrollState) => { + const param: CalculateHeightParam = { offset, state, yOffset: this.scroller.currentOffset().yOffset }; + const bannerHeightChange: boolean | void = this.viewModel.sendEvent({ + type: BaseHomeEventType.CALCULATE_BANNER_HEIGHT, + param, + }) as boolean; + if (bannerHeightChange) { + return { offsetRemain: 0 }; + } + return { offsetRemain: offset }; + }) + .width('100%') + .height('100%') + .cachedCount(9) + .onDidScroll(() => { + this.viewModel.sendEvent({ + type: BaseHomeEventType.HANDLE_SCROLL_OFFSET, + param: { yOffset: this.scroller.currentOffset().yOffset, tabIndex: TabBarType.HOME }, + }); + }) + } + + @Builder + TopTitleViewBuilder() { + FullScreenNavigation({ + topNavigationData: this.componentListState.topNavigationData, + }) + } + + build() { + Navigation(this.componentListPageContext.navPathStack) { + BaseHomeView({ + loadingModel: this.componentListState.loadingModel, + contentView: () => { + this.ContentViewBuilder() + }, + topTitleView: () => { + this.TopTitleViewBuilder() + }, + reloadData: () => { + this.viewModel.sendEvent({ type: ComponentListEventType.LOAD_COMPONENT_PAGE, param: null }); + }, + }) + } + .defaultFocus(true) + .mode(NavigationMode.Stack) + .hideTitleBar(true) + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/viewmodel/Attribute.ets b/features/componentlibrary/src/main/ets/viewmodel/Attribute.ets new file mode 100644 index 0000000000000000000000000000000000000000..b04b4053fbd412760cc17df8afff0442cf715112 --- /dev/null +++ b/features/componentlibrary/src/main/ets/viewmodel/Attribute.ets @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 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 { AttributeTypeEnum } from './AttributeTypeEnum'; + +@Observed +export abstract class Attribute { + public name: string; + public disPlayName: ResourceStr; + public currentValue: string; + public type: AttributeTypeEnum; + public enable: boolean; + + constructor(name: string, disPlayName: ResourceStr, currentValue: string, type: AttributeTypeEnum) { + this.name = name; + this.disPlayName = disPlayName; + this.currentValue = currentValue; + this.type = type; + this.enable = true; + } +} + +export class OriginAttribute { + public name: string = ''; + public displayName: ResourceStr = ''; + public type: AttributeTypeEnum = AttributeTypeEnum.SELECT; + public values: string [] = []; + public leftRange: number = 0; + public rightRange: number = 0; + public step: number = 1; + public currentValue: string = ''; +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/viewmodel/AttributeTypeEnum.ets b/features/componentlibrary/src/main/ets/viewmodel/AttributeTypeEnum.ets new file mode 100644 index 0000000000000000000000000000000000000000..29c7f905a49239261f150860098a65f316a0150e --- /dev/null +++ b/features/componentlibrary/src/main/ets/viewmodel/AttributeTypeEnum.ets @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 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 enum AttributeTypeEnum { + SELECT = 0, + TOGGLE_BUTTON = 1, + TOGGLE = 2, + SLIDER = 3, + COLOR = 4, + OPACITY = 5, +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/viewmodel/CodePreviewPageVM.ets b/features/componentlibrary/src/main/ets/viewmodel/CodePreviewPageVM.ets new file mode 100644 index 0000000000000000000000000000000000000000..6412b74eba3b76da70bb6c15bf90e1710595e277 --- /dev/null +++ b/features/componentlibrary/src/main/ets/viewmodel/CodePreviewPageVM.ets @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024 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 { + BaseVM, + BreakpointTypeEnum, + CommonConstants, + GlobalInfoModel, + ModuleNameEnum, + PageContext, + ScrollDirectionEnum, + WebUtil +} from '@ohos/common'; +import { CodePreviewState } from './CodePreviewState'; +import { DetailPageConstant } from '../constant/DetailPageConstant'; + +export class CodePreviewPageVM extends BaseVM { + private static instance: CodePreviewPageVM; + private globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + + constructor() { + super(new CodePreviewState(0, 0, 1)); + } + + public static getInstance(): CodePreviewPageVM { + if (!CodePreviewPageVM.instance) { + CodePreviewPageVM.instance = new CodePreviewPageVM(); + } + return CodePreviewPageVM.instance; + } + + sendEvent(event: CodePreviewEvent): void { + if (event === CodePreviewEvent.INIT) { + this.init(); + this.resetNavigationViewState(); + } + } + + public pop(animated?: boolean): void { + const pageContext: PageContext = + this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? AppStorage.get('componentListPageContext')! : + AppStorage.get('pageContext')!; + pageContext.popPage(animated); + } + + private init(): void { + WebUtil.registerEmitter(ModuleNameEnum.CODE_PREVIEW, this.changeScrollDirection); + } + + private resetNavigationViewState() { + this.state.navigationOpacity = 1; + this.state.topTranslateY = 0; + this.state.bottomTranslateY = 0; + } + + private changeScrollDirection = (direction: string, offset: number): void => { + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + if (globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL) { + return; + } + animateTo({ + duration: 300, + curve: Curve.EaseOut, + }, () => { + if (direction === ScrollDirectionEnum.DOWN) { + this.state.navigationOpacity = 1 - Math.min(offset, DetailPageConstant.CODEPREVIEW_IMMERSIVE_HEIGHT) / + DetailPageConstant.CODEPREVIEW_IMMERSIVE_HEIGHT; + const topTranslateY = offset > CommonConstants.NAVIGATION_HEIGHT ? -CommonConstants.NAVIGATION_HEIGHT : -offset; + const bottomTranslateY = offset > (CommonConstants.TAB_BAR_HEIGHT + globalInfoModel.naviIndicatorHeight) ? + (CommonConstants.TAB_BAR_HEIGHT + globalInfoModel.naviIndicatorHeight) : offset; + CommonConstants.TAB_BAR_HEIGHT + globalInfoModel.naviIndicatorHeight; + this.state.topTranslateY = topTranslateY; + this.state.bottomTranslateY = bottomTranslateY; + } else if (direction === ScrollDirectionEnum.UP) { + this.state.navigationOpacity = 1; + this.state.topTranslateY = 0; + this.state.bottomTranslateY = 0; + } + }) + } +} + +export enum CodePreviewEvent { + INIT = 'INIT_CODE_PREVIEW_PAGE', +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/viewmodel/CodePreviewState.ets b/features/componentlibrary/src/main/ets/viewmodel/CodePreviewState.ets new file mode 100644 index 0000000000000000000000000000000000000000..8fd6d2b922b46a3049bbd823261252d4744c0f48 --- /dev/null +++ b/features/componentlibrary/src/main/ets/viewmodel/CodePreviewState.ets @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 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 { BaseState } from '@ohos/common'; + +@Observed +export class CodePreviewState extends BaseState { + public topTranslateY: number; + public bottomTranslateY: number; + public navigationOpacity: number; + + constructor(topTranslateY: number, bottomTranslateY: number, navigationOpacity: number) { + super(); + this.topTranslateY = topTranslateY; + this.bottomTranslateY = bottomTranslateY; + this.navigationOpacity = navigationOpacity; + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/viewmodel/CommonAttributeFilter.ets b/features/componentlibrary/src/main/ets/viewmodel/CommonAttributeFilter.ets new file mode 100644 index 0000000000000000000000000000000000000000..f7c0f51723b2a283dcccd44536c15ce44de473b3 --- /dev/null +++ b/features/componentlibrary/src/main/ets/viewmodel/CommonAttributeFilter.ets @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 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 type { ObservedArray } from '@ohos/common'; +import type { Attribute } from './Attribute'; + +export interface CommonAttributeFilter { + filter(attributes: ObservedArray): void; +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/viewmodel/CommonAttributeModifier.ets b/features/componentlibrary/src/main/ets/viewmodel/CommonAttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..1dcc39af13d604a4c3d6f390a903ac8abb2a47f7 --- /dev/null +++ b/features/componentlibrary/src/main/ets/viewmodel/CommonAttributeModifier.ets @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 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 abstract class CommonAttributeModifier implements AttributeModifier { + public attributeHolder: TDescriptor; + + constructor(attributeHolder: TDescriptor) { + this.attributeHolder = attributeHolder; + } + + abstract applyNormalAttribute(instance: TAttribute): void; + + assignAttribute( + extractAttributeValue: (attributeHolder: TDescriptor) => TAttributeValue, + assign: (propertyValue: TAttributeValue) => void, + ): void { + const attributeValue = extractAttributeValue(this.attributeHolder); + assign(attributeValue); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/viewmodel/CommonCodeGenerator.ets b/features/componentlibrary/src/main/ets/viewmodel/CommonCodeGenerator.ets new file mode 100644 index 0000000000000000000000000000000000000000..6746faaf4cfb5d49ff8a02731ebbbd79a9edf68b --- /dev/null +++ b/features/componentlibrary/src/main/ets/viewmodel/CommonCodeGenerator.ets @@ -0,0 +1,5 @@ +import type { OriginAttribute } from './Attribute'; + +export interface CommonCodeGenerator { + generate(attributes: OriginAttribute[]): string; +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/viewmodel/CommonDescriptor.ets b/features/componentlibrary/src/main/ets/viewmodel/CommonDescriptor.ets new file mode 100644 index 0000000000000000000000000000000000000000..93aa3d3f31fd1be5cb58138681353a2523670380 --- /dev/null +++ b/features/componentlibrary/src/main/ets/viewmodel/CommonDescriptor.ets @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 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 type { OriginAttribute } from './Attribute'; + +@Observed +export class CommonDescriptor { + public convert(attributes: OriginAttribute[]): void { + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/viewmodel/ComponentCardSource.ets b/features/componentlibrary/src/main/ets/viewmodel/ComponentCardSource.ets new file mode 100644 index 0000000000000000000000000000000000000000..f73fefba2d75550c9c08519e9bf7f18e0a7da62d --- /dev/null +++ b/features/componentlibrary/src/main/ets/viewmodel/ComponentCardSource.ets @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 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 { ComponentCardData } from '../model/ComponentData'; + +export class ComponentCardSource implements IDataSource { + private cardList: ComponentCardData[] = []; + private listeners: DataChangeListener[] = []; + + public setDataArray(dataArray: ComponentCardData[]): void { + this.cardList = dataArray; + } + + public totalCount(): number { + return this.cardList.length; + } + + public getData(index: number): ComponentCardData { + return this.cardList[index]; + } + + public registerDataChangeListener(listener: DataChangeListener): void { + if (this.listeners.indexOf(listener) < 0) { + this.listeners.push(listener); + } + } + + public unregisterDataChangeListener(listener: DataChangeListener): void { + const pos: number = this.listeners.indexOf(listener); + if (pos >= 0) { + this.listeners.splice(pos, 1); + } + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/viewmodel/ComponentDetailManager.ets b/features/componentlibrary/src/main/ets/viewmodel/ComponentDetailManager.ets new file mode 100644 index 0000000000000000000000000000000000000000..19e7c71d75db2a5e39e874312b64072dda2e08c8 --- /dev/null +++ b/features/componentlibrary/src/main/ets/viewmodel/ComponentDetailManager.ets @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 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 type { ComponentDetailPageVM } from './ComponentDetailPageVM'; + +export class ComponentDetailManager { + private static instance: ComponentDetailManager; + private detailViewModelMap: Map = new Map(); + + private constructor() { + } + + public static getInstance(): ComponentDetailManager { + if (!ComponentDetailManager.instance) { + ComponentDetailManager.instance = new ComponentDetailManager(); + } + return ComponentDetailManager.instance; + } + + public getDetailViewModel(key: string): ComponentDetailPageVM | undefined { + return this.detailViewModelMap.get(key); + } + + public updateDetailViewModelMap(key: string, value: ComponentDetailPageVM) { + this.detailViewModelMap.set(key, value); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/viewmodel/ComponentDetailPageVM.ets b/features/componentlibrary/src/main/ets/viewmodel/ComponentDetailPageVM.ets new file mode 100644 index 0000000000000000000000000000000000000000..9ef73ea7e9b40e895f097c4673675386d63b2fbd --- /dev/null +++ b/features/componentlibrary/src/main/ets/viewmodel/ComponentDetailPageVM.ets @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2024 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 { SegmentButtonTextItem } from '@kit.ArkUI'; +import { ConfigurationConstant } from '@kit.AbilityKit'; +import { + BaseVM, + BaseVMEvent, + BreakpointTypeEnum, + GlobalInfoModel, + LoadingStatus, + ObservedArray, + PageContext, + TopNavigationData, +} from '@ohos/common'; +import { + ColorPickerAttribute, + ComponentDetailState, + getEnumType, + OpacityPickerAttribute, + SelectComAttribute, + SliderComAttribute, + ToggleButtonAttribute, + ToggleComAttribute, +} from './ComponentDetailState'; +import type { AttributeData, ComponentDetailData, RecommendData } from '../model/ComponentDetailData'; +import { ComponentDetailModel } from '../model/ComponentDetailModel'; +import { Attribute, OriginAttribute } from './Attribute'; +import { AttributeTypeEnum } from './AttributeTypeEnum'; +import { CommonCodeGenerator } from './CommonCodeGenerator'; +import { CommonAttributeFilter } from './CommonAttributeFilter'; +import { CommonDescriptor } from './CommonDescriptor'; +import type { ConfigInterface } from '../componentdetailview/ComponentDetailConfig'; +import { ComponentDetailManager } from './ComponentDetailManager'; + +export class ComponentDetailPageVM extends BaseVM { + public componentName: string; + public codeGenerator: CommonCodeGenerator; + public attributeFilter?: CommonAttributeFilter; + public descriptor: CommonDescriptor; + public originAttributes: OriginAttribute[] = []; + public fullAttributes: ObservedArray = []; + private detailPageModel = ComponentDetailModel.getInstance(); + private globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + private detailData?: ComponentDetailData; + + constructor(componentName: string) { + const componentDetailConfig: Record = AppStorage.get('componentDetailConfig')!; + super(new ComponentDetailState(componentDetailConfig[componentName].descriptor(), [], '', [], LoadingStatus.OFF, + new TopNavigationData())); + this.componentName = componentName; + this.codeGenerator = componentDetailConfig[componentName].codeGenerate; + this.attributeFilter = componentDetailConfig[componentName].attributeFilter; + this.descriptor = componentDetailConfig[componentName].descriptor(); + } + + init(event: InitComponentEvent): Promise { + if (this.state.loadingStatus !== LoadingStatus.LOADING) { + this.state.loadingStatus = LoadingStatus.LOADING; + this.initTopNavigationData(); + this.state.code = ''; + this.state.recommends = []; + this.state.attributes = []; + if (this.detailData) { + this.processAttribute(this.detailData.props); + this.state.loadingStatus = LoadingStatus.SUCCESS; + return Promise.resolve(); + } + return this.detailPageModel.init(event.id).then((data: ComponentDetailData) => { + this.detailData = data; + this.processAttribute(data.props); + this.state.loadingStatus = LoadingStatus.SUCCESS; + ComponentDetailManager.getInstance().updateDetailViewModelMap(this.componentName, this); + }).catch((_err: string) => { + this.state.loadingStatus = LoadingStatus.FAILED; + }); + } + return Promise.resolve(); + } + + sendEvent(event: DetailPageEvent): Promise | null { + if (event instanceof InitComponentEvent) { + return this.init(event); + } else if ((event instanceof ChangeAttributeEvent) || (event instanceof ComPreviewChangeEvent)) { + this.changeAttribute(event); + } else if (event instanceof ComponentDetailEvent) { + if (event.type === ComponentDetailEventType.INIT_RECOMMEND) { + this.initRecommendList(this.detailData?.recommendList || []); + } else if (event.type === ComponentDetailEventType.INIT_DESCRIPTOR) { + this.initDescriptor(); + } else { + return this.initWebCode(); + } + } else if (event instanceof TopNavigationChangeEvent) { + this.changeTopNavigationState(event); + } else { + this.changeAttributeEnable(event); + } + return null; + } + + public pop(animated?: boolean) { + const pageContext: PageContext = + this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? AppStorage.get('componentListPageContext')! : + AppStorage.get('pageContext')!; + pageContext.popPage(animated); + } + + public jumpToCodePreview(code: string, preRebuild: () => void) { + const pageContext: PageContext = this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? + AppStorage.get('componentListPageContext')! : AppStorage.get('pageContext')!; + pageContext.openPage({ + routerName: 'CodePreviewView', + param: { + code: code, + componentName: this.componentName, + preFinishAnimation: preRebuild, + } as CodeViewParams, + }, false); + } + + public jumpToPenKitView() { + const pageContext: PageContext = this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? + AppStorage.get('componentListPageContext')! : AppStorage.get('pageContext')!; + pageContext.openPage({ + routerName: 'PenKitView', + }); + } + + private changeAttribute(event: ChangeAttributeEvent | ComPreviewChangeEvent) { + const index: number = this.originAttributes.findIndex((item) => item.name === event.attributeName); + this.originAttributes[index].currentValue = event.attributeValue; + this.fullAttributes[index].currentValue = event.attributeValue; + this.state.code = this.codeGenerator.generate(this.originAttributes); + if (this.attributeFilter) { + this.attributeFilter.filter(this.fullAttributes); + } + this.state.attributes = this.fullAttributes.filter((attribute) => attribute.enable); + // New instance. + this.descriptor.convert(this.originAttributes); + this.state.descriptor = this.descriptor; + } + + private initWebCode(): Promise { + this.state.code = this.codeGenerator.generate(this.originAttributes); + if (this.state.recommends.length === 0) { + this.initRecommendList(this.detailData?.recommendList || []); + } + return Promise.resolve(); + } + + private initDescriptor(): void { + this.state.descriptor.convert(this.originAttributes); + if (this.attributeFilter) { + this.attributeFilter.filter(this.fullAttributes); + } + this.state.attributes = this.fullAttributes.filter((attribute) => attribute.enable); + } + + private changeAttributeEnable(event: AttributeChangeEnable): void { + const name: string = event.attributeName; + const enable: boolean = event.enable; + const index = this.fullAttributes.findIndex(item => item.name === name); + if (index !== -1) { + this.fullAttributes[index].enable = enable; + this.state.attributes = this.fullAttributes.filter((attribute) => attribute.enable); + } + } + + private initRecommendList(recommendList: RecommendData[]): void { + const recommends: RecommendData[] = []; + recommendList?.forEach((item) => { + recommends.push({ + title: item.articleType === 1 ? $r('app.string.develop_practice') : $r('app.string.design_practice'), + articleType: item.articleType, + url: item.url, + }); + }); + this.state.recommends = recommends; + } + + private constructAttributeCom(originAttribute: OriginAttribute): Attribute { + switch (originAttribute.type) { + case AttributeTypeEnum.TOGGLE_BUTTON: { + const attribute = + new ToggleButtonAttribute(originAttribute.name, originAttribute.displayName, originAttribute.currentValue, + originAttribute.type); + originAttribute.values.map((item, index) => { + attribute.selectOption[index] = { text: item } as SegmentButtonTextItem; + }); + return attribute; + } + case AttributeTypeEnum.TOGGLE: { + const attribute = + new ToggleComAttribute(originAttribute.name, originAttribute.displayName, originAttribute.currentValue, + originAttribute.type); + return attribute; + } + case AttributeTypeEnum.SELECT: { + const attribute = + new SelectComAttribute(originAttribute.name, originAttribute.displayName, originAttribute.currentValue, + originAttribute.type); + attribute.selectOption = originAttribute.values.map((item) => { + return { value: item } as SelectOption; + }); + return attribute; + } + case AttributeTypeEnum.SLIDER: { + const attribute = + new SliderComAttribute(originAttribute.name, originAttribute.displayName, originAttribute.currentValue, + originAttribute.type); + attribute.leftRange = originAttribute.leftRange; + attribute.rightRange = originAttribute.rightRange; + attribute.step = originAttribute.step; + return attribute; + } + case AttributeTypeEnum.COLOR: { + const attribute = + new ColorPickerAttribute(originAttribute.name, originAttribute.displayName, originAttribute.currentValue, + originAttribute.type); + return attribute; + } + default: { + const attribute = + new OpacityPickerAttribute(originAttribute.name, originAttribute.displayName, originAttribute.currentValue, + originAttribute.type); + attribute.leftRange = originAttribute.leftRange; + attribute.rightRange = originAttribute.rightRange; + attribute.step = originAttribute.step; + return attribute; + } + } + } + + private processAttribute(attributes: AttributeData[]) { + let curValueArray: string[] = []; + const systemColorMode: ConfigurationConstant.ColorMode = AppStorage.get('systemColorMode')!; + const originAttributeList: OriginAttribute[] = []; + const attributeList: ObservedArray = []; + attributes.forEach((attribute: AttributeData) => { + const originAttribute: OriginAttribute = new OriginAttribute(); + originAttribute.name = attribute.propertyName; + originAttribute.displayName = attribute.propertyDesc; + originAttribute.type = getEnumType(attribute); + if (attribute.displayType === 'color') { + curValueArray = attribute.defaultProperty.split(';'); + if (curValueArray.length === 1) { + originAttribute.currentValue = curValueArray[0]; + } else { + originAttribute.currentValue = + systemColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT ? curValueArray[0] : curValueArray[1]; + } + } else { + originAttribute.currentValue = attribute.defaultProperty; + } + + if (attribute.displayType === 'enum') { + originAttribute.values = JSON.parse(attribute.propertyValues); + } else if (attribute.displayType === 'number' || attribute.displayType === 'opacity') { + const propertyValues: Record = JSON.parse(attribute.propertyValues); + originAttribute.leftRange = propertyValues.left; + originAttribute.rightRange = propertyValues.right; + originAttribute.step = propertyValues.step; + } + originAttributeList.push(originAttribute); + attributeList.push(this.constructAttributeCom(originAttribute)); + }); + this.fullAttributes = attributeList; + this.state.attributes = attributeList; + this.originAttributes = originAttributeList; + } + + private initTopNavigationData() { + this.state.topNavigationData = { + title: `${this.componentName}`, + titleColor: $r('sys.color.font_primary'), + isFullScreen: true, + isBlur: false, + onBackClick: () => { + this.pop(); + }, + }; + } + + private changeTopNavigationState(event: TopNavigationChangeEvent): void { + this.state.topNavigationData = { + title: this.state.topNavigationData.title, + titleColor: this.state.topNavigationData.titleColor, + isFullScreen: this.state.topNavigationData.isFullScreen, + isBlur: event.isBlur, + onBackClick: this.state.topNavigationData.onBackClick, + }; + } +} + + +export enum ComponentDetailEventType { + INIT_RECOMMEND = 'initRecommendEvent', + INIT_DESCRIPTOR = 'initDescriptor', + WEB_CODE_EVENT = 'webCodeEvent', +} + +export class ComponentDetailEvent implements BaseVMEvent { + public readonly type: ComponentDetailEventType; + + constructor(type: ComponentDetailEventType) { + this.type = type; + } +} + +/** + * Initial the data. + */ +export class InitComponentEvent implements BaseVMEvent { + public readonly id: number; + + constructor(id: number) { + this.id = id; + } +} + +/** + * Update the attribute value in attribute-adjustment area. + */ +export class ChangeAttributeEvent implements BaseVMEvent { + public readonly attributeName: string; + public readonly attributeValue: string; + + constructor(attributeName: string, attributeValue: string) { + this.attributeName = attributeName; + this.attributeValue = attributeValue; + } +} + +/** + * Update the attribute value in component-preview area. + */ +export class ComPreviewChangeEvent implements BaseVMEvent { + public readonly attributeName: string; + public readonly attributeValue: string; + + constructor(attributeName: string, attributeValue: string) { + this.attributeName = attributeName; + this.attributeValue = attributeValue; + } +} + +/** + * Update the state value in Top Navigation area. + */ +export class TopNavigationChangeEvent implements BaseVMEvent { + public readonly isBlur: boolean; + + constructor(isBlur: boolean) { + this.isBlur = isBlur; + } +} + +/** + * Change the active mode of the attribute. + */ +export class AttributeChangeEnable { + public readonly attributeName: string; + public readonly enable: boolean; + + constructor(attributeName: string, enable: boolean) { + this.attributeName = attributeName; + this.enable = enable; + } +} + +export interface CodeViewParams { + code: string; + componentName: string; + preFinishAnimation: () => void; +} + +export type DetailPageEvent = ComponentDetailEvent | InitComponentEvent | ChangeAttributeEvent | ComPreviewChangeEvent + | AttributeChangeEnable | TopNavigationChangeEvent; \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/viewmodel/ComponentDetailState.ets b/features/componentlibrary/src/main/ets/viewmodel/ComponentDetailState.ets new file mode 100644 index 0000000000000000000000000000000000000000..6635838345b7cdcf1f96878e8d872fb4e18060c3 --- /dev/null +++ b/features/componentlibrary/src/main/ets/viewmodel/ComponentDetailState.ets @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2024 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 { ItemRestriction, SegmentButtonTextItem } from '@kit.ArkUI'; +import type { TopNavigationData } from '@ohos/common'; +import { BaseState, LoadingStatus, ObservedArray } from '@ohos/common'; +import type { CommonDescriptor } from './CommonDescriptor'; +import type { AttributeData, RecommendData } from '../model/ComponentDetailData'; +import { Attribute } from './Attribute'; +import { AttributeTypeEnum } from './AttributeTypeEnum'; + +@Observed +export class ComponentDetailState extends BaseState { + public descriptor: CommonDescriptor; + public attributes: ObservedArray; + public code: string; + public recommends: RecommendData[]; + public loadingStatus: LoadingStatus; + public topNavigationData: TopNavigationData; + + constructor( + descriptor: CommonDescriptor, + attributes: ObservedArray, + code: string, + recommends: RecommendData[], + loadingStatus: LoadingStatus, + topNavigationData: TopNavigationData, + ) { + super(); + this.descriptor = descriptor; + this.attributes = attributes; + this.code = code; + this.recommends = recommends; + this.loadingStatus = loadingStatus; + this.topNavigationData = topNavigationData; + } +} + +/** + * Six component-shown. + */ + +@Observed +export class SelectComAttribute extends Attribute { + public selectOption: SelectOption[] = []; +} + +@Observed +export class ToggleButtonAttribute extends Attribute { + public selectOption: ItemRestriction = + [{} as SegmentButtonTextItem, {} as SegmentButtonTextItem]; +} + +@Observed +export class SliderComAttribute extends Attribute { + public leftRange: number = 0; + public rightRange: number = 0; + public step: number = 1; +} + +@Observed +export class ColorPickerAttribute extends Attribute { +} + +@Observed +export class OpacityPickerAttribute extends Attribute { + public leftRange: number = 0; + public rightRange: number = 0; + public step: number = 1; +} + +@Observed +export class ToggleComAttribute extends Attribute { +} + +/** + * Get the component-shown by attributeData displayType. + * + * @param attributeData + * @returns + */ +export function getEnumType(attributeData: AttributeData): AttributeTypeEnum { + switch (attributeData.displayType) { + case 'enum': + return JSON.parse(attributeData.propertyValues).length > 2 ? AttributeTypeEnum.SELECT : + AttributeTypeEnum.TOGGLE_BUTTON; + case 'boolean': + return AttributeTypeEnum.TOGGLE; + case 'number': + return AttributeTypeEnum.SLIDER; + case 'color': + return AttributeTypeEnum.COLOR; + case 'opacity': + return AttributeTypeEnum.OPACITY; + } + return AttributeTypeEnum.SELECT; +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/viewmodel/ComponentListState.ets b/features/componentlibrary/src/main/ets/viewmodel/ComponentListState.ets new file mode 100644 index 0000000000000000000000000000000000000000..2a9f2038ca82aa7fdf5a167b1a04c6bac958a70d --- /dev/null +++ b/features/componentlibrary/src/main/ets/viewmodel/ComponentListState.ets @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 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 { BaseHomeState } from '@ohos/commonbusiness'; +import { ComponentCardSource } from './ComponentCardSource'; + +export class ComponentListState extends BaseHomeState { + public cardSource: ComponentCardSource = new ComponentCardSource() + public sections: WaterFlowSections = new WaterFlowSections(); + + public constructor() { + super(); + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/viewmodel/ComponentListViewModel.ets b/features/componentlibrary/src/main/ets/viewmodel/ComponentListViewModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..f12c1063e3e5ef96f01d34fc16c3eaeb263278de --- /dev/null +++ b/features/componentlibrary/src/main/ets/viewmodel/ComponentListViewModel.ets @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2024 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 { ConfigurationConstant } from '@kit.AbilityKit'; +import type { BusinessError } from '@kit.BasicServicesKit'; +import type { GlobalInfoModel, ResponseData, } from '@ohos/common'; +import { + BreakpointType, + BreakpointTypeEnum, + CommonConstants, + LoadingStatus, + Logger, + PageContext, + RequestErrorCode, + StatusBarColorType, + WindowUtil, +} from '@ohos/common'; +import type { BannerData } from '@ohos/commonbusiness'; +import { + BaseHomeEventParam, + BaseHomeEventType, + BaseHomeViewModel, + ComponentDetailParams, + TabBarType, + TAB_CONTENT_STATUSES, +} from '@ohos/commonbusiness'; +import type { ComponentContent, ComponentData } from '../model/ComponentData'; +import { ComponentListModel } from '../model/ComponentListModel'; +import { ComponentListState } from './ComponentListState'; + +const TAG = '[ComponentListViewModel]'; + +export class ComponentListViewModel extends BaseHomeViewModel { + private static instance: ComponentListViewModel; + private globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + private componentListModel: ComponentListModel = ComponentListModel.getInstance(); + private bannerColumnSection: SectionOptions = { + itemsCount: 1, + crossCount: 1, + }; + private componentColumnSection: SectionOptions = { + itemsCount: 0, + crossCount: new BreakpointType({ + sm: 1, + md: 2, + lg: 3, + }).getValue(this.globalInfoModel.currentBreakpoint), + margin: $r('sys.float.padding_level8'), + }; + private footerSection: SectionOptions = { + itemsCount: 1, + crossCount: 1, + }; + + private constructor() { + super(new ComponentListState()); + this.state.topNavigationData.title = $r('app.string.component_name'); + } + + public static getInstance(): ComponentListViewModel { + if (!ComponentListViewModel.instance) { + ComponentListViewModel.instance = new ComponentListViewModel(); + } + return ComponentListViewModel.instance; + } + + public sendEvent(eventParam: ComponentListEventParam): void | boolean { + const eventType: ComponentListEventType | BaseHomeEventType = eventParam.type; + if (eventType === ComponentListEventType.LOAD_COMPONENT_PAGE) { + return this.loadComponentPage(); + } else if (eventType === ComponentListEventType.JUMP_DETAIL_DETAIL) { + return this.jumpComponentDetail(eventParam.param as ComponentContent); + } else if (eventType === ComponentListEventType.UPDATE_FLOW_SECTION) { + return this.updateFlowSection(); + } else { + return super.sendEvent(eventParam as BaseHomeEventParam); + } + } + + protected loadComponentPage(): void { + const isDark: boolean = AppStorage.get('systemColorMode') === ConfigurationConstant.ColorMode.COLOR_MODE_DARK; + this.state.loadingModel.loadingStatus = LoadingStatus.LOADING; + this.state.topNavigationData.titleColor = isDark ? StatusBarColorType.WHITE : StatusBarColorType.BLACK; + this.state.topNavigationData.isBlur = false; + WindowUtil.updateStatusBarColor(getContext(), isDark); + this.componentListModel.getComponentPage(this.state.currentPage, this.pageSize) + .then((result: ResponseData) => { + if (result.data.bannerInfos) { + result.data.bannerInfos.forEach((item: BannerData) => { + item.tabViewType = TabBarType.HOME; + }); + this.state.bannerState.bannerResource.setDataArray([...result.data.bannerInfos]); + } + this.state.cardSource.setDataArray(result.data.cardData); + this.updateFlowSection(); + this.state.loadingModel.hasNextPage = + result.data.cardData.length === this.pageSize && (result.totalSize > this.state.currentPage * this.pageSize); + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + if (globalInfoModel.currentBreakpoint !== BreakpointTypeEnum.LG && + globalInfoModel.currentBreakpoint !== BreakpointTypeEnum.XL) { + this.state.topNavigationData.titleColor = StatusBarColorType.WHITE; + WindowUtil.updateStatusBarColor(getContext(), true); + TAB_CONTENT_STATUSES[TabBarType.HOME] = true; + } + this.state.loadingModel.loadingStatus = LoadingStatus.SUCCESS; + }) + .catch((error: BusinessError) => { + WindowUtil.updateStatusBarColor(getContext(), isDark); + TAB_CONTENT_STATUSES[TabBarType.HOME] = isDark; + if (error.code === RequestErrorCode.ERROR_NETWORK_CONNECT_FAILED) { + this.state.loadingModel.loadingStatus = LoadingStatus.NO_NETWORK; + } else { + this.state.loadingModel.loadingStatus = LoadingStatus.FAILED; + } + }); + } + + protected updateFlowSection(): void { + this.componentColumnSection.itemsCount = this.state.cardSource.totalCount(); + const crossCount: number = new BreakpointType({ + sm: 1, + md: 2, + lg: 3, + }).getValue(this.globalInfoModel.currentBreakpoint); + const leftMargin: Length = + new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level12'), + lg: CommonConstants.SPACE_32 + CommonConstants.TAB_BAR_WIDTH, + xl: $r('sys.float.padding_level16'), + }).getValue(this.globalInfoModel.currentBreakpoint); + const rightMargin: Resource = new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level12'), + lg: $r('sys.float.padding_level16'), + }).getValue(this.globalInfoModel.currentBreakpoint); + const columnMargin: Resource = new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level10'), + lg: $r('sys.float.padding_level12'), + }).getValue(this.globalInfoModel.currentBreakpoint); + const margin: Margin = { + left: leftMargin, + right: rightMargin, + top: columnMargin, + bottom: $r('sys.float.padding_level6'), + }; + + this.componentColumnSection.crossCount = crossCount; + this.componentColumnSection.margin = margin; + this.footerSection.margin = { left: leftMargin, right: rightMargin }; + this.state.sections.splice(0, this.state.sections.length(), + [this.bannerColumnSection, this.componentColumnSection, this.footerSection]); + Logger.debug(TAG, `updateFlowSection ${JSON.stringify(this.state.cardSource)}`); + } + + protected jumpComponentDetail(componentContent: ComponentContent) { + const pageContext: PageContext = + this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? AppStorage.get('componentListPageContext')! : + AppStorage.get('pageContext')!; + const params: ComponentDetailParams = { + componentName: componentContent.title, + componentId: componentContent.id, + }; + pageContext.openPage({ + routerName: 'ComponentDetailView', + param: params, + }, true); + } +} + +export enum ComponentListEventType { + JUMP_DETAIL_DETAIL = 'jumpDetailView', + LOAD_COMPONENT_PAGE = 'loadComponentPage', + UPDATE_FLOW_SECTION = 'updateFlowSection', +} + +export interface ComponentListEventParam { + type: ComponentListEventType | BaseHomeEventType; + param: T; +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/ets/viewmodel/DescriptorWrapper.ets b/features/componentlibrary/src/main/ets/viewmodel/DescriptorWrapper.ets new file mode 100644 index 0000000000000000000000000000000000000000..f4fa3bbc68d12c7da69f1065e43c75ce1707730b --- /dev/null +++ b/features/componentlibrary/src/main/ets/viewmodel/DescriptorWrapper.ets @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 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 type { CommonDescriptor } from './CommonDescriptor'; + +export interface DescriptorWrapper { + descriptor: CommonDescriptor; +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/module.json5 b/features/componentlibrary/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..0026348e0ec5d455849ad062078864f5b8d5ed1d --- /dev/null +++ b/features/componentlibrary/src/main/module.json5 @@ -0,0 +1,12 @@ +{ + "module": { + "name": "componentlibrary", + "type": "har", + "routerMap": "$profile:router_map", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ] + } +} diff --git a/features/componentlibrary/src/main/resources/2in1/element/color.json b/features/componentlibrary/src/main/resources/2in1/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..1b78436888c3349b33b14d5854dd05c62e558560 --- /dev/null +++ b/features/componentlibrary/src/main/resources/2in1/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "code_preview_bg_color", + "value": "#303336" + } + ] +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/resources/base/element/color.json b/features/componentlibrary/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..8e8db1072413194077b31cd8512fabf54f80801a --- /dev/null +++ b/features/componentlibrary/src/main/resources/base/element/color.json @@ -0,0 +1,52 @@ +{ + "color": [ + { + "name": "icon_primary_light", + "value": "#E5000000" + }, + { + "name": "icon_primary_dark", + "value": "#E5FFFFFF" + }, + { + "name": "font_primary_light", + "value": "#E5000000" + }, + { + "name": "font_primary_dark", + "value": "#E5FFFFFF" + }, + { + "name": "icon_secondary_light", + "value": "#99000000" + }, + { + "name": "icon_secondary_dark", + "value": "#99FFFFFF" + }, + { + "name": "font_secondary_light", + "value": "#99000000" + }, + { + "name": "font_secondary_dark", + "value": "#99FFFFFF" + }, + { + "name": "calender_default_text_color", + "value": "#ff182431" + }, + { + "name": "date_picker_default_color", + "value": "#ff007dff" + }, + { + "name": "code_preview_bg_color", + "value": "#202224" + }, + { + "name": "web_overlay_color_start", + "value": "#00ffffff" + } + ] +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/resources/base/element/float.json b/features/componentlibrary/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..8f7a9b9888674aec673f4e5cfb3e9cc0403bcc9e --- /dev/null +++ b/features/componentlibrary/src/main/resources/base/element/float.json @@ -0,0 +1,248 @@ +{ + "float": [ + { + "name": "tip_image_height", + "value": "48.0vp" + }, + { + "name": "tip_button_width", + "value": "72.0vp" + }, + { + "name": "codelab_content_height", + "value": "72.0vp" + }, + { + "name": "card_content_height", + "value": "144vp" + }, + { + "name": "codelab_card_height", + "value": "400vp" + }, + { + "name": "codelab_card_height_verde", + "value": "456vp" + }, + { + "name": "card_button_height", + "value": "40.0vp" + }, + { + "name": "component_item_height", + "value": "64.0vp" + }, + { + "name": "component_item_height_max", + "value": "96.0vp" + }, + { + "name": "card_top_height", + "value": "136.0vp" + }, + { + "name": "card_top_height_verde", + "value": "192.0vp" + }, + { + "name": "border_width_large", + "value": "1.5vp" + }, + { + "name": "water_flow_height", + "value": "200vp" + }, + { + "name": "water_flow_width", + "value": "300vp" + }, + { + "name": "swiper_height", + "value": "160vp" + }, + { + "name": "tab_bar_height", + "value": "30vp" + }, + { + "name": "tab_bar_width", + "value": "64vp" + }, + { + "name": "one_hundred_size", + "value": "100vp" + }, + { + "name": "one_hundred_twenty_size", + "value": "120vp" + }, + { + "name": "eighty_size", + "value": "80vp" + }, + { + "name": "common_left_right_margin", + "value": "36vp" + }, + { + "name": "common_margin", + "value": "36vp" + }, + { + "name": "multiline_text_width", + "value": "320vp" + }, + { + "name": "common_component_height", + "value": "48vp" + }, + { + "name": "common_component_track_thickness", + "value": "4vp" + }, + { + "name": "common_component_block_size", + "value": "16vp" + }, + { + "name": "detail_common_component_option_width", + "value": "224vp" + }, + { + "name": "image_component_width", + "value": "200vp" + }, + { + "name": "image_component_height", + "value": "140vp" + }, + { + "name": "document_select_button_width", + "value": "160vp" + }, + { + "name": "ai_caption_width", + "value": "240vp" + }, + { + "name": "ai_caption_width_xl", + "value": "400vp" + }, + { + "name": "ai_caption_height", + "value": "100vp" + }, + { + "name": "column_size_middle_one", + "value": "200vp" + }, + { + "name": "button_height_normal", + "value": "40vp" + }, + { + "name": "text_height_large", + "value": "32vp" + }, + { + "name": "popup_width_large", + "value": "300vp" + }, + { + "name": "border_width_normal", + "value": "1vp" + }, + { + "name": "default_font_16", + "value": "16fp" + }, + { + "name": "default_font_12", + "value": "12fp" + }, + { + "name": "container_size_1", + "value": "40vp" + }, + { + "name": "container_size_2", + "value": "52vp" + }, + { + "name": "container_size_3", + "value": "64vp" + }, + { + "name": "container_size_4", + "value": "76vp" + }, + { + "name": "container_size_5", + "value": "92vp" + }, + { + "name": "container_width", + "value": "262vp" + }, + { + "name": "container_height", + "value": "180vp" + }, + { + "name": "menu_item_height", + "value": "46vp" + }, + { + "name": "symbol_size_normal", + "value": "20vp" + }, + { + "name": "symbol_size_large", + "value": "24vp" + }, + { + "name": "slider_track_thick_large", + "value": "24vp" + }, + { + "name": "slider_block_border_size", + "value": "2vp" + }, + { + "name": "slider_block_size_large", + "value": "20vp" + }, + { + "name": "dialog_text_height", + "value": "20vp" + }, + { + "name": "dialog_progress_height", + "value": "24vp" + }, + { + "name": "dialog_contain_image_height", + "value": "180vp" + }, + { + "name": "code_preview_top_navigation_height", + "value": "40vp" + }, + { + "name": "code_preview_icon_margin", + "value": "120vp" + }, + { + "name": "toolbar_focus_border_width", + "value": "2vp" + }, + { + "name": "web_overlay_height", + "value": "20vp" + }, + { + "name": "code_preview_height", + "value": "235vp" + } + ] +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/resources/base/element/strarray.json b/features/componentlibrary/src/main/resources/base/element/strarray.json new file mode 100644 index 0000000000000000000000000000000000000000..86d564055e4059fc98bf9118841149f7a6ffa4e3 --- /dev/null +++ b/features/componentlibrary/src/main/resources/base/element/strarray.json @@ -0,0 +1,24 @@ +{ + "strarray": [ + { + "name": "text_picker_data", + "value": [ + { + "value": "apple" + }, + { + "value": "orange" + }, + { + "value": "peach" + }, + { + "value": "grape" + }, + { + "value": "banana" + } + ] + } + ] +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/resources/base/element/string.json b/features/componentlibrary/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..0988d2b372d38bce32bb88085c49820964d2ec50 --- /dev/null +++ b/features/componentlibrary/src/main/resources/base/element/string.json @@ -0,0 +1,340 @@ +{ + "string": [ + { + "name": "component_name", + "value": "Component" + }, + { + "name": "preview", + "value": "Preview area" + }, + { + "name": "changeAttributes", + "value": "Adjustment area" + }, + { + "name": "code", + "value": "Example code" + }, + { + "name": "recommend", + "value": "Related recommends" + }, + { + "name": "open", + "value": "Open" + }, + { + "name": "text_placeholder", + "value": "Please enter" + }, + { + "name": "textview_text", + "value": "Hello, Developer" + }, + { + "name": "textarea_text", + "value": "I have a cute teddy bear. It's quite chubby, so fat that its belly looks like it's about to burst! The clothes it wears are beautiful, and the clothes are colorful, with purple, pink, and yellow, very beautiful!" + }, + { + "name": "camera_picker_button", + "value": "Safe Use of Cameras" + }, + { + "name": "button_text", + "value": "Button Example" + }, + { + "name": "button_text2", + "value": "The button has been long-pressed." + }, + { + "name": "photoViewPicker_text", + "value": "Choose photo" + }, + { + "name": "penKit", + "value": "PenKit service" + }, + { + "name": "edit", + "value": "Edit Mode" + }, + { + "name": "editTip", + "value": "In edit mode, you can drag and drop the 'item' block to swap positions, come and experience it!" + }, + { + "name": "confirmTip", + "value": "Known" + }, + { + "name": "cancelTip", + "value": "Not notice" + }, + { + "name": "btn_click", + "value": "The button has been clicked." + }, + { + "name": "custom_graphic_dialog", + "value": "Custom graphic dialog" + }, + { + "name": "custom_progress_dialog", + "value": "Custom progress dialog" + }, + { + "name": "dialog_cancel", + "value": "Cancel" + }, + { + "name": "dialog_confirm", + "value": "Confirm" + }, + { + "name": "circle", + "value": "circle" + }, + { + "name": "square", + "value": "square" + }, + { + "name": "triangle", + "value": "triangle" + }, + { + "name": "rectangle", + "value": "rectangle" + }, + { + "name": "elliptical", + "value": "elliptical" + }, + { + "name": "trapezium", + "value": "trapezium" + }, + { + "name": "lozenge", + "value": "lozenge" + }, + { + "name": "hexagon", + "value": "hexagon" + }, + { + "name": "aiMatting", + "value": "AIMatting" + }, + { + "name": "aiMatting_tip", + "value": "Long press the object in the picture to use the AI function to automatically cut out the picture, come and experience it!" + }, + { + "name": "alert_dialog_tip", + "value": "Click Alert Dialog" + }, + { + "name": "button_one", + "value": "button_one" + }, + { + "name": "button_two", + "value": "button_two" + }, + { + "name": "text_picker_dialog_tip", + "value": "TextPickerDialog" + }, + { + "name": "action_sheet_tip", + "value": "ActionSheet Dialog" + }, + { + "name": "select_document", + "value": "Select document" + }, + { + "name": "read_pcm_audio", + "value": "Read PCM" + }, + { + "name": "dark_mode", + "value": "dark mode" + }, + { + "name": "light_mode", + "value": "light mode" + }, + { + "name": "portrait_screen", + "value": "portrait screen" + }, + { + "name": "landscape_screen", + "value": "landscape screen" + }, + { + "name": "copy_success", + "value": "copy success" + }, + { + "name": "copy_fail", + "value": "copy fail" + }, + { + "name": "copy", + "value": "copy" + }, + { + "name": "copy_code", + "value": "Copy Code" + }, + { + "name": "popup_button_button", + "value": "Show button popup" + }, + { + "name": "popup_button_text", + "value": "Show text popup" + }, + { + "name": "popup_button_icon", + "value": "Show icon popup" + }, + { + "name": "toggle_button_text", + "value": "Status Button" + }, + { + "name": "title", + "value": "Title" + }, + { + "name": "subtitle", + "value": "Subtitle" + }, + { + "name": "content", + "value": "Content" + }, + { + "name": "confirm_click", + "value": "Confirm is clicked" + }, + { + "name": "item_click", + "value": "%1$s is clicked" + }, + { + "name": "item1", + "value": "item1 (clickable)" + }, + { + "name": "item2", + "value": "item2 (clickable)" + }, + { + "name": "item3", + "value": "item3 (clickable)" + }, + { + "name": "apples", + "value": "apples (clickable)" + }, + { + "name": "bananas", + "value": "bananas (clickable)" + }, + { + "name": "pears", + "value": "pears (clickable)" + }, + { + "name": "popup_button_message", + "value": "This is a popup with two buttons." + }, + { + "name": "popup_text_message", + "value": "This is a text popup." + }, + { + "name": "popup_icon_message", + "value": "This is a popup with icon and title." + }, + { + "name": "pull_up_page", + "value": "Pull up %s page" + }, + { + "name": "sure", + "value": "Sure" + }, + { + "name": "not_support_dial", + "value": "The device does not support dial-up." + }, + { + "name": "pull_up_gallery", + "value": "Tips gallery" + }, + { + "name": "pull_up_map", + "value": "map" + }, + { + "name": "pull_up_settings", + "value": "settings" + }, + { + "name": "pull_up_dail", + "value": "dail" + }, + { + "name": "dialog_graphic_title", + "value": "Graphic confirmation box" + }, + { + "name": "dialog_graphic_content", + "value": "When necessary, a graphic confirmation box can be presented to help users better understand or agree to the content being confirmed." + }, + { + "name": "dialog_graphic_tip", + "value": "I have already acknowledged the above content. Do not remind me again." + }, + { + "name": "dialog_progress", + "value": "%d%" + }, + { + "name": "File_path", + "value": "File path" + }, + { + "name": "toggle_type", + "value": "Switch Mode" + }, + { + "name": "device_camera", + "value": "The device has no capture hardware" + }, + { + "name": "function_handwrite_not_support", + "value": "This device does not support SystemCapability.Stylus.Handwrite" + }, + { + "name": "develop_practice", + "value": "Development Practice" + }, + { + "name": "design_practice", + "value": "Design Practice" + }, + { + "name": "share_description", + "value": "Text" + } + ] +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/resources/base/media/16k.pcm b/features/componentlibrary/src/main/resources/base/media/16k.pcm new file mode 100644 index 0000000000000000000000000000000000000000..e5194ef726f42545b8adbfc6ad2b1c9836c90ecc Binary files /dev/null and b/features/componentlibrary/src/main/resources/base/media/16k.pcm differ diff --git a/features/componentlibrary/src/main/resources/base/media/ic_placeholder.png b/features/componentlibrary/src/main/resources/base/media/ic_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..eba4138d934c2b7e4532ef747136a132ba48319e Binary files /dev/null and b/features/componentlibrary/src/main/resources/base/media/ic_placeholder.png differ diff --git a/features/componentlibrary/src/main/resources/base/media/image_ai.png b/features/componentlibrary/src/main/resources/base/media/image_ai.png new file mode 100644 index 0000000000000000000000000000000000000000..d6d5dcca3fa1194632b480882b9e0c2b64378a94 Binary files /dev/null and b/features/componentlibrary/src/main/resources/base/media/image_ai.png differ diff --git a/features/componentlibrary/src/main/resources/base/media/image_background.png b/features/componentlibrary/src/main/resources/base/media/image_background.png new file mode 100644 index 0000000000000000000000000000000000000000..db49813fbf0801f76117adb1d18c1c3910761f82 Binary files /dev/null and b/features/componentlibrary/src/main/resources/base/media/image_background.png differ diff --git a/features/componentlibrary/src/main/resources/base/media/image_dialog.png b/features/componentlibrary/src/main/resources/base/media/image_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..e947532243c455bafcf7e4f55418fc343505137a Binary files /dev/null and b/features/componentlibrary/src/main/resources/base/media/image_dialog.png differ diff --git a/features/componentlibrary/src/main/resources/base/media/image_src340.png b/features/componentlibrary/src/main/resources/base/media/image_src340.png new file mode 100644 index 0000000000000000000000000000000000000000..84b7d4a85afb4688d1b0b49d65265290339184f9 Binary files /dev/null and b/features/componentlibrary/src/main/resources/base/media/image_src340.png differ diff --git a/features/componentlibrary/src/main/resources/base/media/pause.png b/features/componentlibrary/src/main/resources/base/media/pause.png new file mode 100644 index 0000000000000000000000000000000000000000..5d364df1ff40dc494484219ecdace961607bbc73 Binary files /dev/null and b/features/componentlibrary/src/main/resources/base/media/pause.png differ diff --git a/features/componentlibrary/src/main/resources/base/media/play_circle_fill.png b/features/componentlibrary/src/main/resources/base/media/play_circle_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..0291ed2cd04be45194f8dc057ad80b965a680e64 Binary files /dev/null and b/features/componentlibrary/src/main/resources/base/media/play_circle_fill.png differ diff --git a/features/componentlibrary/src/main/resources/base/media/rating_background.png b/features/componentlibrary/src/main/resources/base/media/rating_background.png new file mode 100644 index 0000000000000000000000000000000000000000..563091036b82e9365e44f6687926ce18ebffa1ea Binary files /dev/null and b/features/componentlibrary/src/main/resources/base/media/rating_background.png differ diff --git a/features/componentlibrary/src/main/resources/base/media/rating_foreground.png b/features/componentlibrary/src/main/resources/base/media/rating_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..81380f7a90225322299a7e04516cddca878960ba Binary files /dev/null and b/features/componentlibrary/src/main/resources/base/media/rating_foreground.png differ diff --git a/features/componentlibrary/src/main/resources/base/media/startIcon.png b/features/componentlibrary/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/features/componentlibrary/src/main/resources/base/media/startIcon.png differ diff --git a/features/componentlibrary/src/main/resources/base/media/stop_circle.png b/features/componentlibrary/src/main/resources/base/media/stop_circle.png new file mode 100644 index 0000000000000000000000000000000000000000..7ca463ba20648bf36bd4a4b631d60d687b4b7fbb Binary files /dev/null and b/features/componentlibrary/src/main/resources/base/media/stop_circle.png differ diff --git a/features/componentlibrary/src/main/resources/base/profile/router_map.json b/features/componentlibrary/src/main/resources/base/profile/router_map.json new file mode 100644 index 0000000000000000000000000000000000000000..19fc15a72b92e991dd84adc39b3c52b5adddf0c1 --- /dev/null +++ b/features/componentlibrary/src/main/resources/base/profile/router_map.json @@ -0,0 +1,19 @@ +{ + "routerMap": [ + { + "name": "ComponentDetailView", + "pageSourceFile": "src/main/ets/view/ComponentDetailView.ets", + "buildFunction": "ComponentDetailBuilder" + }, + { + "name": "CodePreviewView", + "pageSourceFile": "src/main/ets/view/CodePreview.ets", + "buildFunction": "CodePreviewBuilder" + }, + { + "name": "PenKitView", + "pageSourceFile": "src/main/ets/componentdetailview/penkit/component/HandWritingComponent.ets", + "buildFunction": "PenKitViewBuilder" + } + ] +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/resources/dark/element/color.json b/features/componentlibrary/src/main/resources/dark/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..b4be4d8e74052df3e50599ce9f028a3515b94a9e --- /dev/null +++ b/features/componentlibrary/src/main/resources/dark/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "web_overlay_color_start", + "value": "#00202224" + } + ] +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/resources/dark/media/ic_placeholder.png b/features/componentlibrary/src/main/resources/dark/media/ic_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..d48cc01efc41207ba919a592343464c39fcceda7 Binary files /dev/null and b/features/componentlibrary/src/main/resources/dark/media/ic_placeholder.png differ diff --git a/features/componentlibrary/src/main/resources/en_US/element/strarray.json b/features/componentlibrary/src/main/resources/en_US/element/strarray.json new file mode 100644 index 0000000000000000000000000000000000000000..86d564055e4059fc98bf9118841149f7a6ffa4e3 --- /dev/null +++ b/features/componentlibrary/src/main/resources/en_US/element/strarray.json @@ -0,0 +1,24 @@ +{ + "strarray": [ + { + "name": "text_picker_data", + "value": [ + { + "value": "apple" + }, + { + "value": "orange" + }, + { + "value": "peach" + }, + { + "value": "grape" + }, + { + "value": "banana" + } + ] + } + ] +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/resources/en_US/element/string.json b/features/componentlibrary/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..4a11410f83136366348a98c1d2fd21e7341f4550 --- /dev/null +++ b/features/componentlibrary/src/main/resources/en_US/element/string.json @@ -0,0 +1,340 @@ +{ + "string": [ + { + "name": "component_name", + "value": "Component" + }, + { + "name": "preview", + "value": "Preview area" + }, + { + "name": "changeAttributes", + "value": "Adjustment area" + }, + { + "name": "code", + "value": "Example code" + }, + { + "name": "recommend", + "value": "Related recommends" + }, + { + "name": "open", + "value": "Open" + }, + { + "name": "text_placeholder", + "value": "Please enter" + }, + { + "name": "textview_text", + "value": "Hello, Developer" + }, + { + "name": "camera_picker_button", + "value": "Safe Use of Cameras" + }, + { + "name": "button_text", + "value": "Button Example" + }, + { + "name": "button_text2", + "value": "The button has been long-pressed." + }, + { + "name": "photoViewPicker_text", + "value": "Choose photo" + }, + { + "name": "penKit", + "value": "PenKit service" + }, + { + "name": "edit", + "value": "Edit Mode" + }, + { + "name": "editTip", + "value": "In edit mode, you can drag and drop the 'item' block to swap positions, come and experience it!" + }, + { + "name": "confirmTip", + "value": "Known" + }, + { + "name": "cancelTip", + "value": "Not notice" + }, + { + + "name": "btn_click", + "value": "The button has been clicked." + }, + { + "name": "custom_graphic_dialog", + "value": "Custom graphic dialog" + }, + { + "name": "custom_progress_dialog", + "value": "Custom progress dialog" + }, + { + "name": "dialog_cancel", + "value": "Cancel" + }, + { + "name": "dialog_confirm", + "value": "Confirm" + }, + { + "name": "circle", + "value": "circle" + }, + { + "name": "square", + "value": "square" + }, + { + "name": "triangle", + "value": "triangle" + }, + { + "name": "rectangle", + "value": "rectangle" + }, + { + "name": "elliptical", + "value": "elliptical" + }, + { + "name": "trapezium", + "value": "trapezium" + }, + { + "name": "lozenge", + "value": "lozenge" + }, + { + "name": "hexagon", + "value": "hexagon" + },{ + "name": "aiMatting", + "value": "AIMatting" + }, + { + "name": "aiMatting_tip", + "value": "Long press the object in the picture to use the AI function to automatically cut out the picture, come and experience it!" + }, + { + "name": "alert_dialog_tip", + "value": "Click Alert Dialog" + }, + { + "name": "button_one", + "value": "button_one" + }, + { + "name": "button_two", + "value": "button_two" + }, + { + "name": "action_sheet_tip", + "value": "ActionSheet Dialog" + }, + { + "name": "select_document", + "value": "Select document" + }, + { + "name": "textarea_text", + "value": "I have a cute teddy bear. It's quite chubby, so fat that its belly looks like it's about to burst! The clothes it wears are beautiful, and the clothes are colorful, with purple, pink, and yellow, very beautiful!" + }, + { + "name": "text_picker_dialog_tip", + "value": "TextPickerDialog" + }, + { + "name": "read_pcm_audio", + "value": "Read PCM" + }, + { + "name": "dark_mode", + "value": "dark mode" + }, + { + "name": "light_mode", + "value": "light mode" + }, + { + "name": "popup_button_button", + "value": "Show button popup" + }, + { + "name": "popup_button_text", + "value": "Show text popup" + }, + { + "name": "popup_button_icon", + "value": "Show icon popup" + }, + { + "name": "toggle_button_text", + "value": "Status Button" + }, + { + "name": "title", + "value": "Title" + }, + { + "name": "subtitle", + "value": "Subtitle" + }, + { + "name": "content", + "value": "Content" + }, + { + "name": "copy_success", + "value": "Copy Success" + }, + { + "name": "copy_fail", + "value": "Copy Fail" + }, + { + "name": "copy_code", + "value": "Copy Code" + }, + { + "name": "landscape_screen", + "value": "Landscape" + }, + { + "name": "portrait_screen", + "value": "Portrait" + }, + { + "name": "confirm_click", + "value": "Confirm is clicked" + }, + { + "name": "item_click", + "value": "%1$s is clicked" + }, + { + "name": "item1", + "value": "item1 (clickable)" + }, + { + "name": "item2", + "value": "item2 (clickable)" + }, + { + "name": "item3", + "value": "item3 (clickable)" + }, + { + "name": "apples", + "value": "apples (clickable)" + }, + { + "name": "bananas", + "value": "bananas (clickable)" + }, + { + "name": "pears", + "value": "pears (clickable)" + }, + { + "name": "popup_button_message", + "value": "This is a popup with two buttons." + }, + { + "name": "popup_text_message", + "value": "This is a text popup." + }, + { + "name": "popup_icon_message", + "value": "This is a popup with icon and title." + }, + { + "name": "copy", + "value": "copy" + }, + { + "name": "pull_up_page", + "value": "Pull up %s page" + }, + { + "name": "sure", + "value": "Sure" + }, + { + "name": "not_support_dial", + "value": "The device does not support dial-up." + }, + { + "name": "pull_up_gallery", + "value": "Tips gallery" + }, + { + "name": "pull_up_map", + "value": "map" + }, + { + "name": "pull_up_settings", + "value": "settings" + }, + { + "name": "pull_up_dail", + "value": "dail" + }, + { + "name": "dialog_graphic_title", + "value": "Graphic confirmation box" + }, + { + "name": "dialog_graphic_content", + "value": "When necessary, a graphic confirmation box can be presented to help users better understand or agree to the content being confirmed." + }, + { + "name": "dialog_graphic_tip", + "value": "I have already acknowledged the above content. Do not remind me again." + }, + { + "name": "dialog_progress", + "value": "%d%" + }, + { + "name": "File_path", + "value": "File path" + }, + { + "name": "toggle_type", + "value": "Switch Mode" + }, + { + "name": "device_camera", + "value": "The device has no capture hardware" + }, + { + "name": "function_handwrite_not_support", + "value": "This device does not support SystemCapability.Stylus.Handwrite" + }, + { + "name": "develop_practice", + "value": "Development Practice" + }, + { + "name": "design_practice", + "value": "Design Practice" + }, + { + "name": "share_description", + "value": "Text" + } + ] +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/resources/zh_CN/element/strarray.json b/features/componentlibrary/src/main/resources/zh_CN/element/strarray.json new file mode 100644 index 0000000000000000000000000000000000000000..0d9b8df7438c19b74a202d9fe2da557d67814d8d --- /dev/null +++ b/features/componentlibrary/src/main/resources/zh_CN/element/strarray.json @@ -0,0 +1,24 @@ +{ + "strarray": [ + { + "name": "text_picker_data", + "value": [ + { + "value": "苹果" + }, + { + "value": "橘子" + }, + { + "value": "桃子" + }, + { + "value": "葡萄" + }, + { + "value": "香蕉" + } + ] + } + ] +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/resources/zh_CN/element/string.json b/features/componentlibrary/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..aae7a1076a42b2ddb53cdde67231d4673b03fe8a --- /dev/null +++ b/features/componentlibrary/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,340 @@ +{ + "string": [ + { + "name": "component_name", + "value": "组件" + }, + { + "name": "preview", + "value": "预览区域" + }, + { + "name": "changeAttributes", + "value": "调整区域" + }, + { + "name": "code", + "value": "示例代码" + }, + { + "name": "recommend", + "value": "相关推荐" + }, + { + "name": "open", + "value": "打开" + }, + { + "name": "text_placeholder", + "value": "请输入" + }, + { + "name": "textview_text", + "value": "开发者你好" + }, + { + "name": "textarea_text", + "value": "我有一只可爱的玩具熊。它长得很胖,胖得肚子都要爆炸了!它穿的衣服都很漂亮,而且衣服上五颜六色,有紫的、有粉的、还有黄的,非常漂亮!" + }, + { + "name": "camera_picker_button", + "value": "安全使用相机" + }, + { + "name": "button_text", + "value": "按钮示例" + }, + { + "name": "button_text2", + "value": "按钮被长按" + }, + { + "name": "photoViewPicker_text", + "value": "选择图片" + }, + { + "name": "penKit", + "value": "手写笔服务" + }, + { + "name": "edit", + "value": "编辑模式" + }, + { + "name": "editTip", + "value": "编辑模式可以拖拽‘item’方块交换位置,快来体验吧!" + }, + { + "name": "confirmTip", + "value": "知道了" + }, + { + "name": "cancelTip", + "value": "不再提醒" + }, + { + "name": "btn_click", + "value": "按钮被点击" + }, + { + "name": "custom_graphic_dialog", + "value": "自定义图文弹窗" + }, + { + "name": "custom_progress_dialog", + "value": "自定义进度弹窗" + }, + { + "name": "dialog_cancel", + "value": "取消" + }, + { + "name": "dialog_confirm", + "value": "确定" + }, + { + "name": "circle", + "value": "圆形" + }, + { + "name": "square", + "value": "正方形" + }, + { + "name": "triangle", + "value": "三角形" + }, + { + "name": "rectangle", + "value": "长方形" + }, + { + "name": "elliptical", + "value": "椭圆" + }, + { + "name": "trapezium", + "value": "梯形" + }, + { + "name": "lozenge", + "value": "菱形" + }, + { + "name": "hexagon", + "value": "六边形" + }, + { + "name": "aiMatting", + "value": "AI抠图" + }, + { + "name": "aiMatting_tip", + "value": "长按图片中物体可使用AI功能自动抠图,快来体验吧!" + }, + { + "name": "alert_dialog_tip", + "value": "点击警告弹窗" + }, + { + "name": "button_one", + "value": "按钮1" + }, + { + "name": "button_two", + "value": "按钮2" + }, + { + "name": "text_picker_dialog_tip", + "value": "文本选择器弹窗" + }, + { + "name": "action_sheet_tip", + "value": "列表选择器弹窗" + }, + { + "name": "select_document", + "value": "选择文件" + }, + { + "name": "read_pcm_audio", + "value": "读取PCM音频" + }, + { + "name": "dark_mode", + "value": "深色模式" + }, + { + "name": "light_mode", + "value": "浅色模式" + }, + { + "name": "landscape_screen", + "value": "横屏" + }, + { + "name": "portrait_screen", + "value": "竖屏" + }, + { + "name": "copy_fail", + "value": "复制失败" + }, + { + "name": "copy", + "value": "复制" + }, + { + "name": "copy_code", + "value": "拷贝代码" + }, + { + "name": "popup_button_button", + "value": "弹出按钮气泡" + }, + { + "name": "popup_button_text", + "value": "弹出文字气泡" + }, + { + "name": "popup_button_icon", + "value": "弹出图文气泡" + }, + { + "name": "toggle_button_text", + "value": "状态按钮" + }, + { + "name": "title", + "value": "标题" + }, + { + "name": "subtitle", + "value": "副标题" + }, + { + "name": "content", + "value": "内容" + }, + { + "name": "copy_success", + "value": "复制成功" + }, + { + "name": "confirm_click", + "value": "确认按钮被点击" + }, + { + "name": "item_click", + "value": "%1$s被点击" + }, + { + "name": "item1", + "value": "item1 (可点击)" + }, + { + "name": "item2", + "value": "item2 (可点击)" + }, + { + "name": "item3", + "value": "item3 (可点击)" + }, + { + "name": "apples", + "value": "apples (可点击)" + }, + { + "name": "bananas", + "value": "bananas (可点击)" + }, + { + "name": "pears", + "value": "pears (可点击)" + }, + { + "name": "popup_button_message", + "value": "这是一个带按钮的气泡" + }, + { + "name": "popup_text_message", + "value": "这是一个文字气泡" + }, + { + "name": "popup_icon_message", + "value": "这是一个带图标的弹出气泡" + }, + { + "name": "pull_up_page", + "value": "拉起%s页" + }, + { + "name": "sure", + "value": "确定" + }, + { + "name": "not_support_dial", + "value": "该设备不支持拨号" + }, + { + "name": "pull_up_gallery", + "value": "应用市场玩机技巧" + }, + { + "name": "pull_up_map", + "value": "地图" + }, + { + "name": "pull_up_settings", + "value": "设置" + }, + { + "name": "pull_up_dail", + "value": "拨号" + }, + { + "name": "dialog_graphic_title", + "value": "带图形确认框" + }, + { + "name": "dialog_graphic_content", + "value": "必要时可以通过图形化方式展现确认框,以使用户更好理解或认同确认内容" + }, + { + "name": "dialog_graphic_tip", + "value": "我已知晓上述内容,不再提醒" + }, + { + "name": "dialog_progress", + "value": "%d%" + }, + { + "name": "File_path", + "value": "文件路径" + }, + { + "name": "toggle_type", + "value": "Switch样式" + }, + { + "name": "device_camera", + "value": "该设备没有摄像头" + }, + { + "name": "function_handwrite_not_support", + "value": "该设备不支持手写功能" + }, + { + "name": "develop_practice", + "value": "开发实践" + }, + { + "name": "design_practice", + "value": "设计实践" + }, + { + "name": "share_description", + "value": "文本" + } + ] +} \ No newline at end of file diff --git a/features/componentlibrary/src/ohosTest/ets/test/Ability.test.ets b/features/componentlibrary/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..85c78f67579d6e31b5f5aeea463e216b9b141048 --- /dev/null +++ b/features/componentlibrary/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +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/features/componentlibrary/src/ohosTest/ets/test/List.test.ets b/features/componentlibrary/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..794c7dc4ed66bd98fa3865e07922906e2fcef545 --- /dev/null +++ b/features/componentlibrary/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/features/componentlibrary/src/ohosTest/module.json5 b/features/componentlibrary/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..1fda2d887a06ee9bc44f1035799678aa6db0ad21 --- /dev/null +++ b/features/componentlibrary/src/ohosTest/module.json5 @@ -0,0 +1,13 @@ +{ + "module": { + "name": "componentlibrary_test", + "type": "feature", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} \ No newline at end of file diff --git a/features/componentlibrary/src/test/List.test.ets b/features/componentlibrary/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..bb5b5c3731e283dd507c847560ee59bde477bbc7 --- /dev/null +++ b/features/componentlibrary/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/features/componentlibrary/src/test/LocalUnit.test.ets b/features/componentlibrary/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..165fc1615ee8618b4cb6a622f144a9a707eee99f --- /dev/null +++ b/features/componentlibrary/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +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/features/devpractices/.gitignore b/features/devpractices/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/features/devpractices/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/features/devpractices/Index.ets b/features/devpractices/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..1ee958a282447cf94614a96674eb08b34980353b --- /dev/null +++ b/features/devpractices/Index.ets @@ -0,0 +1,5 @@ +export { PracticesView } from './src/main/ets/view/PracticesView'; + +export { SampleModel } from './src/main/ets/model/SampleModel'; + +export { SingleSampleData } from './src/main/ets/model/SampleDetailData'; \ No newline at end of file diff --git a/features/devpractices/build-profile.json5 b/features/devpractices/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..697dff23e224373edb713dc2b8a08ed7341d5b4c --- /dev/null +++ b/features/devpractices/build-profile.json5 @@ -0,0 +1,31 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest" + } + ] +} diff --git a/features/devpractices/consumer-rules.txt b/features/devpractices/consumer-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..41c680955e07ed245551082fb63874288b58d785 --- /dev/null +++ b/features/devpractices/consumer-rules.txt @@ -0,0 +1,3 @@ +-keep +./src/main/ets/model/SampleData.ets +./src/main/ets/model/SampleDetailData.ets \ No newline at end of file diff --git a/features/devpractices/hvigorfile.ts b/features/devpractices/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..42187071482d292588ad40babeda74f7b8d97a23 --- /dev/null +++ b/features/devpractices/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/features/devpractices/obfuscation-rules.txt b/features/devpractices/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..fdbb5b9852d7dd5f39bddaeb21ab5ee1f3346749 --- /dev/null +++ b/features/devpractices/obfuscation-rules.txt @@ -0,0 +1,22 @@ +# 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/features/devpractices/oh-package.json5 b/features/devpractices/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..eb68697696f658437fd4ada211908a9e03ed0c96 --- /dev/null +++ b/features/devpractices/oh-package.json5 @@ -0,0 +1,12 @@ +{ + "name": "devpractices", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@ohos/common": "file:../../common", + "@ohos/commonbusiness": "file:../commonbusiness" + } +} diff --git a/features/devpractices/src/main/ets/common/SampleConstant.ets b/features/devpractices/src/main/ets/common/SampleConstant.ets new file mode 100644 index 0000000000000000000000000000000000000000..0a352e2ce4cd2c96f0e417c1ad2c4b738649d689 --- /dev/null +++ b/features/devpractices/src/main/ets/common/SampleConstant.ets @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 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 SampleConstant { + public static SCROLL_MARGIN_SM: number = -16; + public static SCROLL_MARGIN_MD: number = -24; + public static SCROLL_MARGIN_LG: number = -32; +} + +export enum SampleTypeEnum { + COMMON_CLIENT = 'commonClient', + WEARABLE_CLIENT = 'wearableClient', + COMMON_SAMPLE = 'commonSample', + WEARABLE_SAMPLE = 'wearableSample', +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/component/BaseCategoryView.ets b/features/devpractices/src/main/ets/component/BaseCategoryView.ets new file mode 100644 index 0000000000000000000000000000000000000000..1fede112ad9d52e62659a46e10353cd402c567b8 --- /dev/null +++ b/features/devpractices/src/main/ets/component/BaseCategoryView.ets @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024 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 { EmptyContentView, LoadingFailedView, LoadingStatus, LoadingView, NoNetworkView } from '@ohos/common'; +import type { GlobalInfoModel, LoadingModel } from '@ohos/common'; + +@Component +export struct BaseCategoryView { + @Prop @Require loadingModel: LoadingModel; + @BuilderParam @Require contentView: () => void; + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + scroller: Scroller = new Scroller(); + reloadData?: Function; + + build() { + Scroll() { + if (this.loadingModel.loadingStatus === LoadingStatus.SUCCESS) { + this.contentView() + } else { + Column() { + if (this.loadingModel.loadingStatus === LoadingStatus.FAILED) { + LoadingFailedView(this.globalInfoModel.currentBreakpoint, () => { + this.reloadData?.(); + }) + } else if (this.loadingModel.loadingStatus === LoadingStatus.LOADING) { + LoadingView(this.globalInfoModel.currentBreakpoint) + } else if (this.loadingModel.loadingStatus === LoadingStatus.NO_NETWORK) { + NoNetworkView(this.globalInfoModel.currentBreakpoint, () => { + this.reloadData?.(); + }) + } else { + EmptyContentView($r('app.media.ic_browse_no'), $r('app.string.no_content')) + } + } + .width('100%') + .height($r('app.float.loading_view_height')) + } + } + .align(Alignment.Top) + .scrollBar(BarState.Off) + .backgroundColor($r('sys.color.background_secondary')) + .height('100%') + .width('100%') + .nestedScroll({ + scrollForward: NestedScrollMode.PARENT_FIRST, + scrollBackward: NestedScrollMode.SELF_FIRST, + }) + } +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/component/CategorySamples.ets b/features/devpractices/src/main/ets/component/CategorySamples.ets new file mode 100644 index 0000000000000000000000000000000000000000..468dab7d702181c96cbc3814925eba3fab2ef6c3 --- /dev/null +++ b/features/devpractices/src/main/ets/component/CategorySamples.ets @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2024 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 type { GlobalInfoModel } from '@ohos/common'; +import { BreakpointType, ColumnEnum, CommonConstants, LoadingMore, LoadingStatus, NoMore } from '@ohos/common'; +import { CardStyleTypeEnum, SampleDetailParams } from '@ohos/commonbusiness'; +import { SampleConstant } from '../common/SampleConstant'; +import type { SampleCardData, SampleCategory, SampleContent } from '../model/SampleData'; +import { PracticeEventType, PracticeViewModel } from '../viewmodel/PracticeViewModel'; +import { BaseCategoryView } from './BaseCategoryView'; +import { PictureAboveTextCard } from './PictureAboveTextCard'; +import { PictureCard } from './PictureCard'; +import { SampleListCard } from './SampleListCard'; +import { SampleScrollCard } from './SampleScrollCard'; + +@Component({ freezeWhenInactive: true }) +export struct CategorySamples { + scroller: Scroller = new Scroller(); + viewModel: PracticeViewModel = PracticeViewModel.getInstance(); + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @Prop sampleCategory: SampleCategory; + + aboutToAppear(): void { + if (!this.sampleCategory.sampleCards) { + this.viewModel.sendEvent({ + type: PracticeEventType.LOAD_SAMPLE_LIST, + param: this.sampleCategory, + }); + } + } + + jumpToDetail(currentIndex: number, sampleCardId: number) { + this.viewModel.sendEvent({ + type: PracticeEventType.JUMP_DETAIL_DETAIL, + param: { currentIndex, sampleCardId }, + }); + } + + @Builder + CategoryContentViewBuilder() { + GridRow({ + columns: { sm: ColumnEnum.SM, md: ColumnEnum.MD, lg: ColumnEnum.LG }, + gutter: { y: $r('sys.float.padding_level8'), x: $r('sys.float.padding_level8') }, + direction: GridRowDirection.Row, + }) { + Repeat(this.sampleCategory.sampleCards) + .each((repeatItem: RepeatItem) => { + GridCol({ span: CommonConstants.SPAN_4 }) { + PictureAboveTextCard({ sampleCardData: repeatItem.item }) + .onClick(() => this.jumpToDetail(0, repeatItem.item.id)) + } + }) + .key((item: SampleCardData) => item.id.toString()) + .templateId((item: SampleCardData) => item.cardStyleType.toString()) + .template(CardStyleTypeEnum.PICTURE_TO_SWIPER.toString(), + (repeatItem: RepeatItem) => { + GridCol({ span: CommonConstants.SPAN_4 }) { + PictureAboveTextCard({ sampleCardData: repeatItem.item }) + .onClick(() => this.jumpToDetail(repeatItem.index, repeatItem.item.detailCardId)) + } + }) + .template(CardStyleTypeEnum.PICTURE_ABOVE_TEXT.toString(), + (repeatItem: RepeatItem) => { + GridCol({ span: CommonConstants.SPAN_4 }) { + PictureAboveTextCard({ sampleCardData: repeatItem.item }) + .onClick(() => this.jumpToDetail(0, repeatItem.item.id)) + } + }) + .template(CardStyleTypeEnum.LIST.toString(), (repeatItem: RepeatItem) => { + if (repeatItem.item.sampleContents.length > 2) { + GridCol({ + span: { sm: CommonConstants.SPAN_4, md: CommonConstants.SPAN_8, lg: CommonConstants.SPAN_12 } + }) { + SampleScrollCard({ + sampleCardData: repeatItem.item, + handleItemClick: (index: number, samples: SampleContent[]) => { + this.jumpToDetail(index, repeatItem.item.id); + }, + }) + .margin({ + left: new BreakpointType({ + sm: SampleConstant.SCROLL_MARGIN_SM, + md: SampleConstant.SCROLL_MARGIN_MD, + lg: SampleConstant.SCROLL_MARGIN_LG, + }).getValue(this.globalInfoModel.currentBreakpoint), + right: new BreakpointType({ + sm: SampleConstant.SCROLL_MARGIN_SM, + md: SampleConstant.SCROLL_MARGIN_MD, + lg: SampleConstant.SCROLL_MARGIN_LG, + }).getValue(this.globalInfoModel.currentBreakpoint), + }) + } + .clip(false) + } else { + GridCol({ span: CommonConstants.SPAN_4 }) { + SampleListCard({ + sampleCardData: repeatItem.item, + handleItemClick: (index: number, samples: SampleContent[]) => { + this.jumpToDetail(index, repeatItem.item.id); + }, + }) + } + } + }) + .template(CardStyleTypeEnum.PICTURE.toString(), (repeatItem: RepeatItem) => { + GridCol({ span: { sm: CommonConstants.SPAN_4, md: CommonConstants.SPAN_4, lg: CommonConstants.SPAN_8 } }) { + PictureCard({ sampleCardData: repeatItem.item }) + .onClick(() => this.jumpToDetail(0, repeatItem.item.id)) + } + }) + GridCol({ span: { sm: CommonConstants.SPAN_4, md: CommonConstants.SPAN_8, lg: CommonConstants.SPAN_12 } }) { + Column() { + if (this.sampleCategory.loadingModel.loadingMoreStatus === LoadingStatus.LOADING) { + LoadingMore() + } else if (this.sampleCategory.sampleCards.length > 0 && !this.sampleCategory.loadingModel.hasNextPage) { + NoMore() + } + } + } + .padding({ + bottom: this.globalInfoModel.naviIndicatorHeight + + (new BreakpointType({ + sm: CommonConstants.TAB_BAR_HEIGHT, + md: CommonConstants.TAB_BAR_HEIGHT, + lg: 0, + }).getValue(this.globalInfoModel.currentBreakpoint)), + }) + } + .padding({ + left: new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level12'), + lg: $r('sys.float.padding_level16'), + }).getValue(this.globalInfoModel.currentBreakpoint), + right: new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level12'), + lg: $r('sys.float.padding_level16'), + }).getValue(this.globalInfoModel.currentBreakpoint), + top: $r('sys.float.padding_level4') + }) + .clip(false) + } + + build() { + BaseCategoryView({ + loadingModel: this.sampleCategory.loadingModel, + contentView: () => { + this.CategoryContentViewBuilder(); + }, + reloadData: () => { + this.viewModel.sendEvent({ + type: PracticeEventType.LOAD_SAMPLE_LIST, + param: this.sampleCategory, + }); + }, + }) + } +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/component/PictureAboveTextCard.ets b/features/devpractices/src/main/ets/component/PictureAboveTextCard.ets new file mode 100644 index 0000000000000000000000000000000000000000..ec49025665dd7ebd5e8d470e0dea9f4ff509062a --- /dev/null +++ b/features/devpractices/src/main/ets/component/PictureAboveTextCard.ets @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2024 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 { BreakpointType, BreakpointTypeEnum, CommonConstants, GlobalInfoModel } from '@ohos/common'; +import type { SampleCardData, SampleContent } from '../model/SampleData'; +import { TagLabel } from './TagLabel'; +import { ConfigurationConstant } from '@kit.AbilityKit'; +import { CardStyleTypeEnum } from '@ohos/commonbusiness'; + +const IMAGE_SIZE: number = 40; +const TAG_PADDING: number = 32; + +@Component +export struct PictureAboveTextCard { + @StorageProp('GlobalInfoModel') @Watch('calculateTagMaxWidth') globalInfoModel: GlobalInfoModel = + AppStorage.get('GlobalInfoModel')!; + @StorageProp('systemColorMode') systemColorMode: ConfigurationConstant.ColorMode = AppStorage.get('systemColorMode')!; + @Prop sampleCardData: SampleCardData; + @State sampleContent?: SampleContent = undefined; + @State maxWidth: number = 0; + + aboutToAppear(): void { + this.sampleContent = this.sampleCardData.sampleContents?.[0]; + this.calculateTagMaxWidth(); + } + + calculateTagMaxWidth() { + let barWidth: number = new BreakpointType({ + sm: 0, + md: 0, + lg: CommonConstants.TAB_BAR_WIDTH, + xl: CommonConstants.SIDE_BAR_WIDTH, + }).getValue(this.globalInfoModel.currentBreakpoint); + const paddingVal = new BreakpointType({ + sm: CommonConstants.SPACE_16, + md: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? CommonConstants.SPACE_32 : + CommonConstants.SPACE_24, + lg: CommonConstants.SPACE_32, + xl: CommonConstants.SPACE_32, + }).getValue(this.globalInfoModel.currentBreakpoint); + const currentWidth: number = this.globalInfoModel.deviceWidth - barWidth; + const count: number = new BreakpointType({ + sm: 1, + md: 2, + lg: 3, + xl: 3, + }).getValue(this.globalInfoModel.currentBreakpoint); + const gutter = CommonConstants.SPACE_16; + this.maxWidth = Math.floor((currentWidth - paddingVal * 2 - gutter * (count - 1)) / count) - TAG_PADDING - + IMAGE_SIZE - CommonConstants.SPACE_16; + } + + build() { + Column() { + Row() { + Image($rawfile(this.sampleContent?.mediaUrl)) + .alt($r('app.media.img_placeholder')) + .height('100%') + .height('100%') + .objectFit(ImageFit.Contain) + .draggable(true) + } + .justifyContent(FlexAlign.Center) + .width('100%') + .clip(true) + .layoutWeight(1) + .padding({ top: $r('sys.float.padding_level16'), bottom: $r('sys.float.padding_level8') }) + + Row() { + Column() { + Text(this.sampleContent?.title) + .fontSize($r('sys.float.Subtitle_M')) + .fontWeight(FontWeight.Medium) + .fontColor(this.sampleCardData.cardStyleType === CardStyleTypeEnum.PICTURE_TO_SWIPER ? + $r('app.color.card_font_primary_color') : $r('sys.color.font_primary')) + TagLabel({ + maxWidth: this.maxWidth, + tags: this.sampleContent?.tags || [], + cardStyleType: this.sampleCardData.cardStyleType + }) + } + .alignItems(HorizontalAlign.Start) + .layoutWeight(1) + + Button({ type: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? ButtonType.ROUNDED_RECTANGLE : + ButtonType.Circle }) { + SymbolGlyph($r('sys.symbol.chevron_right')) + .fontColor([this.sampleCardData.cardStyleType === CardStyleTypeEnum.PICTURE_TO_SWIPER ? + $r('app.color.icon_secondary_color') : $r('sys.color.icon_secondary')]) + .fontSize($r('sys.float.Title_M')) + } + .margin({ left: $r('sys.float.padding_level6') }) + .height($r('app.float.card_button_height')) + .aspectRatio(1) + .backgroundColor($r('sys.color.comp_background_tertiary')) + } + .padding({ + left: $r('sys.float.padding_level8'), + right: $r('sys.float.padding_level8'), + top: $r('sys.float.padding_level6'), + bottom: $r('sys.float.padding_level10'), + }) + .height($r('app.float.picture_card_content_height')) + .backgroundColor(this.sampleCardData.cardStyleType === CardStyleTypeEnum.PICTURE_TO_SWIPER ? + $r('app.color.card_color') : $r('sys.color.comp_background_secondary')) + } + .clip(true) + .backgroundColor($r('sys.color.comp_background_list_card')) + .backgroundImage($rawfile(this.sampleCardData.cardImage)) + .backgroundImageSize({ width: '100%', height: '100%' }) + .clickEffect({ level: ClickEffectLevel.MIDDLE }) + .borderRadius($r('sys.float.corner_radius_level8')) + .height($r('app.float.picture_card_height')) + } +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/component/PictureCard.ets b/features/devpractices/src/main/ets/component/PictureCard.ets new file mode 100644 index 0000000000000000000000000000000000000000..8dc86bfc55fa2b421dd601d99b9a8fa79508c379 --- /dev/null +++ b/features/devpractices/src/main/ets/component/PictureCard.ets @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024 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 { CommonConstants, ImageUtil } from '@ohos/common'; +import type { SampleCardData, SampleContent } from '../model/SampleData'; +import { TagLabel } from './TagLabel'; + +@Component +export struct PictureCard { + @Prop sampleCardData: SampleCardData; + @State bgTopColor: string = ''; + @State bgBottomColor: string = ''; + @State sampleContent?: SampleContent = undefined; + + aboutToAppear(): void { + this.sampleContent = this.sampleCardData.sampleContents[0]; + ImageUtil.getColorFromImgUrl(this.sampleContent.mediaUrl, true).then((colorArr: number[]) => { + this.bgTopColor = `rgba(${colorArr[0]},${colorArr[1]},${colorArr[2]},0)`; + this.bgBottomColor = `rgba(${colorArr[0]},${colorArr[1]},${colorArr[2]},1)`; + }); + } + + build() { + Row() { + Stack({ alignContent: Alignment.Bottom }) { + Image($rawfile(this.sampleCardData.cardImage)) + .draggable(false) + .linearGradientBlur(60, + { + fractionStops: [[0, 0], [0, 0.64], [1, 0.82], [1, 1]], + direction: GradientDirection.Bottom, + }) + .alt($r('app.media.img_placeholder')) + .height('100%') + .width('100%') + Row() { + Column() { + Text(this.sampleContent?.subTitle) + .fontSize($r('sys.float.Body_S')) + .fontColor($r('sys.color.font_on_secondary')) + .fontWeight(FontWeight.Medium) + .lightUpEffect(1) + .margin({ bottom: $r('sys.float.padding_level4') }) + Text(this.sampleContent?.title) + .fontSize($r('sys.float.Title_M')) + .fontColor($r('sys.color.font_on_primary')) + .fontWeight(FontWeight.Bold) + .margin({ bottom: $r('sys.float.padding_level6') }) + TagLabel({ tags: this.sampleContent?.tags || [], isDark: true }) + } + .alignItems(HorizontalAlign.Start) + .layoutWeight(1) + + Button({ type: ButtonType.Circle }) { + SymbolGlyph($r('sys.symbol.chevron_right')) + .fontColor([$r('sys.color.icon_on_primary')]) + .fontSize($r('sys.float.Title_M')) + } + .backgroundColor($r('sys.color.comp_background_tertiary')) + .width($r('app.float.card_button_height')) + .aspectRatio(1) + } + .linearGradient({ + angle: CommonConstants.LINEAR_GRADIENT_ANGLE, + colors: [[this.bgTopColor, 0], [this.bgBottomColor, 0.75], [this.bgBottomColor, 1]] + }) + .height($r('app.float.card_content_height')) + .padding($r('sys.float.padding_level8')) + .width('100%') + .alignItems(VerticalAlign.Bottom) + } + .width('100%') + .height($r('app.float.picture_card_height')) + .borderRadius($r('sys.float.corner_radius_level8')) + .clip(true) + } + .clickEffect({ level: ClickEffectLevel.MIDDLE }) + .clip(true) + .backgroundColor($r('sys.color.ohos_id_color_background')) + .borderRadius($r('sys.float.corner_radius_level8')) + } +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/component/SampleCard.ets b/features/devpractices/src/main/ets/component/SampleCard.ets new file mode 100644 index 0000000000000000000000000000000000000000..ecd839bc445f3abbc8b2e5ec2c31573169296e89 --- /dev/null +++ b/features/devpractices/src/main/ets/component/SampleCard.ets @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2024 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 { common } from '@kit.AbilityKit'; +import { uniformTypeDescriptor as utd } from '@kit.ArkData'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { systemShare } from '@kit.ShareKit'; +import { + Logger, + BreakpointType, + BreakpointTypeEnum, + CommonConstants, + WebSheetBuilder, + WebUrlType, + WebUtil, + type GlobalInfoModel, +} from '@ohos/common'; +import { SampleDetailConstant } from '../constant/CommonConstants'; +import { BindSheetEvent, SampleDetailPageVM, LoadSampleEvent } from '../viewmodel/SampleDetailPageVM'; +import type { SampleCardData } from '../viewmodel/SampleDetailState'; + +const TAG = '[SampleCard]'; + +@Component +export struct SampleCard { + viewModel: SampleDetailPageVM = SampleDetailPageVM.getInstance(); + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @Consume currentIndex: number; + @Prop sampleIndex: number; + @ObjectLink sampleCard: SampleCardData; + @State backIconBgColor: ResourceColor = + this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? Color.Transparent : + $r('sys.color.comp_background_tertiary'); + + aboutToAppear(): void { + WebUtil.createWebNode(this.sampleCard.originalUrl, this.getUIContext(), NestedScrollMode.SELF_ONLY); + } + + aboutToDisappear(): void { + WebUtil.removeNode(this.sampleCard.originalUrl); + } + + private handleShare(): void { + if (canIUse('SystemCapability.Collaboration.SystemShare')) { + const shareData: systemShare.SharedData = new systemShare.SharedData({ + utd: utd.UniformDataType.HYPERLINK, + content: this.sampleCard.originalUrl, + title: this.sampleCard.title, + description: this.sampleCard.originalUrl + }); + let controller: systemShare.ShareController = new systemShare.ShareController(shareData); + let context = getContext(this) as common.UIAbilityContext; + controller.show(context, { + selectionMode: systemShare.SelectionMode.SINGLE, + previewMode: systemShare.SharePreviewMode.DEFAULT + }).catch((error: BusinessError) => { + Logger.error(TAG, `Sample link sharing error. Code: ${error.code}, message: ${error.message}`); + }); + } + } + + @Builder + TitleComponent() { + Row() { + Text($r('app.string.sample_code')) + .maxLines(1) + .height($r('app.float.samplename_height')) + .lineHeight($r('app.float.cardtitle_lineheight')) + .fontColor($r('sys.color.font_primary')) + .fontWeight(FontWeight.Bold) + .fontSize($r('sys.float.Title_S')) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + Button({ type: ButtonType.Circle }) { + SymbolGlyph($r('sys.symbol.share')) + .fontColor([$r('sys.color.icon_primary')]) + .fontSize($r('sys.float.Title_M')) + } + .height($r('app.float.back_button_height')) + .aspectRatio(1) + .backgroundColor(this.backIconBgColor) + .onClick(() => this.handleShare()) + .onHover((isHover: boolean) => { + this.backIconBgColor = isHover ? $r('sys.color.comp_background_tertiary') : Color.Transparent; + }) + } + .justifyContent(FlexAlign.SpaceBetween) + .width('100%') + } + + build() { + Column() { + Text(this.sampleCard.title) + .maxLines(1) + .height($r('app.float.samplename_height')) + .lineHeight($r('app.float.cardtitle_lineheight')) + .fontColor($r('sys.color.font_primary')) + .fontWeight(FontWeight.Bold) + .fontSize($r('sys.float.Subtitle_L')) + .margin({ bottom: $r('sys.float.padding_level1') }) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + + Text(this.sampleCard.desc) + .maxLines(SampleDetailConstant.SUBTITLE_MAXLINE) + .height($r('app.float.sampledesc_height')) + .fontSize($r('sys.float.Subtitle_S')) + .fontWeight(FontWeight.Regular) + .lineHeight($r('app.float.cardsubtitle_lineheight')) + .fontColor($r('sys.color.font_secondary')) + .margin({ + bottom: $r('sys.float.padding_level12'), + }) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + + Row({ + space: new BreakpointType({ + sm: CommonConstants.SPACE_16, + md: CommonConstants.SPACE_16, + lg: CommonConstants.SPACE_24, + }).getValue(this.globalInfoModel.currentBreakpoint), + }) { + Button($r('app.string.read_code')) + .cardButtonStyle($r('sys.color.comp_background_tertiary'), $r('sys.color.background_emphasize')) + .onClick(() => { + this.viewModel.sendEvent(new BindSheetEvent(this.sampleIndex, true)); + }) + .bindSheet(this.sampleCard.bindSheetShow, + WebSheetBuilder(this.sampleCard.originalUrl, WebUrlType.GITEE), { + title: () => this.TitleComponent(), + preferType: SheetType.CENTER, + height: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? + ((this.globalInfoModel.deviceHeight - this.globalInfoModel.decorHeight) * + CommonConstants.SHEET_HEIGHT_RATIO_XL) : SheetSize.LARGE, + width: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? CommonConstants.SHEET_WIDTH_XL : + undefined, + onWillDisappear: () => { + WebUtil.getWebNode(this.sampleCard.originalUrl)?.remove(); + this.viewModel.sendEvent(new BindSheetEvent(this.sampleIndex, false)); + }, + onWillSpringBackWhenDismiss: () => { + Logger.info(TAG, + `The springBack is not registered, causing the half-modal sheet to lack bounce-back behavior when pulled down.`); + } + }) + + if (this.sampleCard.downloading && this.currentIndex === this.sampleIndex && + (this.sampleCard.downloadProgress >= 0)) { + Progress({ + value: SampleDetailConstant.PROGRESS_START, + total: SampleDetailConstant.PROGRESS_FINISH, + type: ProgressType.Capsule, + }) + .value(this.sampleCard.downloadProgress) + .layoutWeight(1) + .borderRadius($r('sys.float.corner_radius_level7')) + .height($r('app.float.progress_height')) + .color($r('sys.color.background_emphasize')) + .backgroundColor($r('sys.color.comp_background_primary_contrary')) + .style({ + borderColor: $r('sys.color.background_emphasize'), + borderWidth: $r('sys.float.border_small'), + content: `${this.sampleCard.downloadProgress}%`, + font: { size: $r('sys.float.Subtitle_S'), style: FontStyle.Normal, weight: FontWeight.Medium }, + fontColor: Color.Black, + }) + } else { + Button(this.sampleCard.installed ? $r('app.string.experience_sample') : $r('app.string.download_sample')) + .cardButtonStyle($r('sys.color.background_emphasize'), $r('sys.color.font_on_primary')) + .onClick(() => { + this.viewModel.sendEvent(new LoadSampleEvent()); + }) + } + } + .width('100%') + .justifyContent(FlexAlign.SpaceAround) + } + .borderRadius($r('sys.float.corner_radius_level8')) + .backgroundColor($r('sys.color.comp_background_primary')) + .height($r('app.float.sample_card_height')) + .alignItems(HorizontalAlign.Start) + .clip(true) + .margin({ + top: new BreakpointType({ + sm: $r('app.float.margin_36'), + md: $r('sys.float.padding_level12'), + lg: $r('sys.float.padding_level12'), + }).getValue(this.globalInfoModel.currentBreakpoint), + }) + .padding({ + top: $r('sys.float.padding_level9'), + bottom: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG ? $r('sys.float.padding_level12') : + $r('sys.float.padding_level8'), + left: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG ? $r('sys.float.padding_level16') : + $r('sys.float.padding_level8'), + right: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG ? $r('sys.float.padding_level16') : + $r('sys.float.padding_level8'), + }) + } +} + +@Extend(Button) +function cardButtonStyle(backgroundColor: ResourceColor, fontColor: ResourceColor) { + .layoutWeight(1) + .fontSize($r('sys.float.Subtitle_S')) + .fontWeight(FontWeight.Medium) + .fontColor(fontColor) + .backgroundColor(backgroundColor) + .controlSize(ControlSize.NORMAL) + .borderRadius($r('sys.float.corner_radius_level7')) +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/component/SampleComponent.ets b/features/devpractices/src/main/ets/component/SampleComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..0abaddf1dfb3a7118b89d03e5e14695b1c372492 --- /dev/null +++ b/features/devpractices/src/main/ets/component/SampleComponent.ets @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024 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 type { GlobalInfoModel } from '@ohos/common'; +import { BreakpointType, ColumnEnum, CommonConstants, VideoComponent } from '@ohos/common'; +import { MediaTypeEnum } from '@ohos/commonbusiness'; +import { SampleDetailData } from '../viewmodel/SampleDetailState'; +import { SampleCard } from './SampleCard'; + +@Component +export struct SampleComponent { + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @ObjectLink singleSampleData: SampleDetailData; + @Prop @Require sampleIndex: number; + @Prop showIndicator: boolean = false; + + build() { + Column() { + Column() { + if (this.singleSampleData.mediaType === MediaTypeEnum.IMAGE) { + Image($rawfile(this.singleSampleData.mediaUrl)) + .draggable(false) + .alt($r('app.media.img_placeholder')) + .objectFit(ImageFit.Contain) + .layoutWeight(1) + } else { + VideoComponent({ + mediaSrc: this.singleSampleData.mediaUrl, + autoPlay: true, + loopPlay: true, + clickPause: false, + startVisibleRatio: 0.5, + }) + } + } + .layoutWeight(1) + + GridRow({ + columns: { sm: ColumnEnum.SM, md: ColumnEnum.MD, lg: ColumnEnum.LG }, + gutter: { + x: { + sm: $r('sys.float.padding_level6'), + md: $r('sys.float.padding_level6'), + lg: $r('sys.float.padding_level8'), + }, + }, + }) { + GridCol({ + span: { sm: CommonConstants.SPAN_4, md: CommonConstants.SPAN_6, lg: CommonConstants.SPAN_8 }, + offset: { sm: 0, md: 1, lg: 2 }, + }) { + SampleCard({ sampleCard: this.singleSampleData.sampleCard, sampleIndex: this.sampleIndex, }) + } + } + .margin({ + bottom: this.showIndicator ? $r('sys.float.padding_level16') : $r('sys.float.padding_level8'), + }) + } + .backgroundColor($r('sys.color.background_secondary')) + .height('100%') + .width('100%') + .padding({ + top: $r('sys.float.padding_level4'), + left: new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level12'), + lg: $r('sys.float.padding_level16'), + }).getValue(this.globalInfoModel.currentBreakpoint), + right: new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level12'), + lg: $r('sys.float.padding_level16'), + }).getValue(this.globalInfoModel.currentBreakpoint), + }) + } +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/component/SampleListCard.ets b/features/devpractices/src/main/ets/component/SampleListCard.ets new file mode 100644 index 0000000000000000000000000000000000000000..52fb3bb23a81123e841c40d77a0e9f290812cb72 --- /dev/null +++ b/features/devpractices/src/main/ets/component/SampleListCard.ets @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024 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 type { SampleCardData, SampleContent } from '../model/SampleData'; +import { TagLabel } from './TagLabel'; + +@Component +export struct SampleListCard { + @Prop sampleCardData: SampleCardData; + handleItemClick?: Function; + + build() { + Column() { + Text(this.sampleCardData.cardTitle) + .fontSize($r('sys.float.Subtitle_L')) + .fontColor($r('sys.color.font_primary')) + .fontWeight(FontWeight.Bold) + Text(this.sampleCardData.cardSubTitle) + .fontSize($r('sys.float.Body_S')) + .fontColor($r('sys.color.font_secondary')) + .fontWeight(FontWeight.Regular) + .margin({ top: $r('sys.float.padding_level2') }) + ForEach(this.sampleCardData.sampleContents.slice(0, 2), (item: SampleContent, index: number) => { + Row() { + Column() { + Image($rawfile(item.mediaUrl)) + .draggable(false) + .alt($r('app.media.img_placeholder')) + .objectFit(ImageFit.Contain) + .height('100%') + } + .padding({ top: $r('sys.float.padding_level4'), bottom: $r('sys.float.padding_level2') }) + .width($r('app.float.card_img_width')) + .backgroundColor($r('sys.color.comp_background_secondary')) + + Column() { + Text(item.title) + .fontSize($r('sys.float.Subtitle_M')) + .fontWeight(FontWeight.Medium) + .fontColor($r('sys.color.font_primary')) + TagLabel({ tags: item.tags }) + } + .height('100%') + .padding($r('sys.float.padding_level8')) + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.Center) + .layoutWeight(1) + .backgroundColor($r('sys.color.comp_background_tertiary')) + } + .onClick(() => this.handleItemClick?.(index, this.sampleCardData.sampleContents)) + .borderRadius($r('sys.float.corner_radius_level5')) + .clip(true) + .margin({ top: $r('sys.float.padding_level8') }) + .backgroundColor($r('app.color.blur_bg')) + .width('100%') + .height($r('app.float.card_list_height')) + }, (item: SampleContent) => item.id.toString()) + } + .clip(true) + .alignItems(HorizontalAlign.Start) + .backgroundColor($r('sys.color.comp_background_list_card')) + .clickEffect({ level: ClickEffectLevel.MIDDLE }) + .borderRadius($r('sys.float.corner_radius_level8')) + .padding($r('sys.float.padding_level8')) + } +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/component/SampleScrollCard.ets b/features/devpractices/src/main/ets/component/SampleScrollCard.ets new file mode 100644 index 0000000000000000000000000000000000000000..ec6136c5859d8d08df293c55be41aee70dd5d18d --- /dev/null +++ b/features/devpractices/src/main/ets/component/SampleScrollCard.ets @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2024 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 type { GlobalInfoModel } from '@ohos/common'; +import { BreakpointType, BreakpointTypeEnum, CommonConstants } from '@ohos/common'; +import { SampleCardData, SampleContent } from '../model/SampleData'; +import { TagLabel } from './TagLabel'; + +const ITEM_HEIGHT: number = 340; +const ITEM_ASPECT: number = 0.74; +const LOG_MAX_WIDTH: number = 202; + +@Component +export struct SampleScrollCard { + @StorageProp('GlobalInfoModel') @Watch('handleBreakPointChange') globalInfoModel: GlobalInfoModel = + AppStorage.get('GlobalInfoModel')!; + @Prop sampleCardData: SampleCardData; + handleItemClick?: Function; + @State sampleItemWidth: number = ITEM_HEIGHT * ITEM_ASPECT; + @State horizontalPadding: Resource = new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level12'), + lg: $r('sys.float.padding_level16'), + }).getValue(this.globalInfoModel.currentBreakpoint); + @State contentOffset: number = new BreakpointType({ + sm: CommonConstants.SPACE_16, + md: CommonConstants.SPACE_24, + lg: CommonConstants.SPACE_32, + }).getValue(this.globalInfoModel.currentBreakpoint); + + aboutToAppear(): void { + this.handleBreakPointChange(); + } + + handleBreakPointChange() { + const barWidth = + this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? CommonConstants.SIDE_BAR_WIDTH : + CommonConstants.TAB_BAR_WIDTH; + this.horizontalPadding = new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level12'), + lg: $r('sys.float.padding_level16'), + }).getValue(this.globalInfoModel.currentBreakpoint); + this.contentOffset = new BreakpointType({ + sm: CommonConstants.SPACE_16, + md: CommonConstants.SPACE_24, + lg: CommonConstants.SPACE_32, + }).getValue(this.globalInfoModel.currentBreakpoint); + if ((this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG || + this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL) && + this.sampleCardData.sampleContents.length === 3) { + this.sampleItemWidth = (this.globalInfoModel.deviceWidth - barWidth - 64 - 32) / 3; + } else if (this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM || + this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.MD) { + this.sampleItemWidth = ITEM_HEIGHT * ITEM_ASPECT; + } else { + this.sampleItemWidth = Math.floor((this.globalInfoModel.deviceWidth - barWidth - 64 - 32) / 3.5); + } + } + + @Builder + SampleItemBuilder(item: SampleContent, index: number) { + Column() { + Column() { + Image($rawfile(item.mediaUrl)) + .draggable(false) + .alt($r('app.media.img_placeholder')) + .objectFit(ImageFit.Contain) + .height('100%') + } + .layoutWeight(1) + + Column() { + Text(item.title) + .fontSize($r('sys.float.Body_L')) + .fontWeight(FontWeight.Bold) + .fontColor($r('sys.color.font_primary')) + TagLabel({ tags: item.tags, maxWidth: LOG_MAX_WIDTH }) + } + .width('100%') + .padding({ top: $r('sys.float.padding_level8') }) + .renderGroup(true) + } + .clickEffect({ level: ClickEffectLevel.MIDDLE }) + .onClick(() => this.handleItemClick?.(index, this.sampleCardData.sampleContents)) + .padding($r('sys.float.padding_level8')) + .backgroundColor($r('sys.color.comp_background_list_card')) + .borderRadius($r('sys.float.corner_radius_level8')) + .height($r('app.float.card_scroll_height')) + .width(this.sampleItemWidth) + } + + build() { + Column() { + Column() { + Text(this.sampleCardData.cardTitle) + .fontSize($r('sys.float.Subtitle_L')) + .fontColor($r('sys.color.ohos_id_color_foreground')) + .fontWeight(FontWeight.Bold) + Text(this.sampleCardData.cardSubTitle) + .fontSize($r('sys.float.Body_S')) + .fontColor($r('sys.color.font_secondary')) + .fontWeight(FontWeight.Regular) + .margin({ + top: $r('sys.float.padding_level2'), + bottom: $r('sys.float.padding_level6'), + }) + } + .alignItems(HorizontalAlign.Start) + .width('100%') + .padding({ + left: this.horizontalPadding, + right: this.horizontalPadding, + }) + + List({ space: CommonConstants.SPACE_16 }) { + Repeat(this.sampleCardData.sampleContents).each((repeatItem: RepeatItem) => { + ListItem() { + this.SampleItemBuilder(repeatItem.item, repeatItem.index) + } + }) + .virtualScroll() + .key((item: SampleContent) => item.id.toString()) + } + .cachedCount(3) + .contentStartOffset(this.contentOffset) + .contentEndOffset(this.contentOffset) + .scrollBar(BarState.Off) + .listDirection(Axis.Horizontal) + .width('100%') + .height($r('app.float.card_scroll_height')) + } + .alignItems(HorizontalAlign.Start) + } +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/component/TagLabel.ets b/features/devpractices/src/main/ets/component/TagLabel.ets new file mode 100644 index 0000000000000000000000000000000000000000..46b75ee1f7cbe9093e08ab98675cc18f8fe2e81c --- /dev/null +++ b/features/devpractices/src/main/ets/component/TagLabel.ets @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2024 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 { MeasureText } from '@kit.ArkUI'; +import type { GlobalInfoModel } from '@ohos/common'; +import { BreakpointTypeEnum, CommonConstants } from '@ohos/common'; +import { CardStyleTypeEnum } from '@ohos/commonbusiness'; +import { SampleDetailConstant } from '../constant/CommonConstants'; + +interface TagInfo { + tag: string; + width: number; +} + +const PADDING_WIDTH: number = 24; +const MIN_WIDTH: number = 42; +const ELLIPSIS = '...'; + +@Component +export struct TagLabel { + @Prop tags: string[]; + @Prop @Watch('changeTagList') maxWidth: number; + @Prop isDark: boolean; + @Prop cardStyleType: CardStyleTypeEnum; + @State tagList: TagInfo[] = []; + @State showEllipsis: boolean = false; + @State globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @State displayPopup: boolean = false; + private allTagStr: string = ''; + + aboutToAppear(): void { + this.changeTagList(); + } + + changeTagList() { + const tempList: TagInfo[] = []; + let currentWidth: number = 0; + const ellipsisWidth = this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? MIN_WIDTH : 0; + this.tags.forEach((tag: string) => { + const tagWidth: number = Math.ceil(px2vp(MeasureText.measureText({ + textContent: tag, + fontSize: $r('sys.float.Body_S') + }))) + PADDING_WIDTH + CommonConstants.SPACE_8; + if (!this.maxWidth || currentWidth + tagWidth < this.maxWidth) { + currentWidth += tagWidth; + tempList.push({ tag: tag, width: tagWidth - CommonConstants.SPACE_8 }); + } else if (currentWidth < this.maxWidth - ellipsisWidth) { + const hideEllipse: boolean = this.globalInfoModel.currentBreakpoint !== BreakpointTypeEnum.XL || + (this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL && + tempList.length === 0) + const width: number = this.maxWidth - currentWidth - CommonConstants.SPACE_8; + if (hideEllipse) { + this.showEllipsis = false; + } else { + this.showEllipsis = true; + } + if (width > MIN_WIDTH && hideEllipse) { + tempList.push({ tag, width }); + } + currentWidth = this.maxWidth; + } + }); + this.tagList = tempList; + if (this.tags.length > 0) { + this.allTagStr = this.tags.join(';'); + } + } + + build() { + Row({ space: CommonConstants.SPACE_8 }) { + ForEach(this.tagList, (item: TagInfo) => { + Row() { + Text(item.tag) + .fontColor(this.cardStyleType === CardStyleTypeEnum.PICTURE_TO_SWIPER ? + $r('app.color.card_font_secondary_color') : $r('sys.color.font_secondary')) + .width(item.width - PADDING_WIDTH) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontSize($r('sys.float.Body_S')) + .fontWeight(FontWeight.Regular) + } + .justifyContent(FlexAlign.Center) + .borderRadius('50%') + .padding({ + left: $r('sys.float.padding_level6'), + right: $r('sys.float.padding_level6'), + top: $r('sys.float.padding_level2'), + bottom: $r('sys.float.padding_level2'), + }) + .width(item.width) + .backgroundColor(this.isDark ? $r('sys.color.icon_on_tertiary') : + $r('sys.color.comp_background_tertiary')) + }, (item: TagInfo) => item.tag) + if (this.showEllipsis) { + Row() { + Text(ELLIPSIS) + .fontColor(this.isDark ? $r('sys.color.font_on_secondary') : $r('sys.color.font_secondary')) + .width(MIN_WIDTH - PADDING_WIDTH) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontSize($r('sys.float.Body_S')) + .fontWeight(FontWeight.Regular) + .textAlign(TextAlign.Center) + } + .width(MIN_WIDTH) + .padding({ + left: $r('sys.float.padding_level6'), + right: $r('sys.float.padding_level6'), + top: $r('sys.float.padding_level2'), + bottom: $r('sys.float.padding_level2') + }) + .borderRadius('50%') + .backgroundColor(this.isDark ? $r('sys.color.icon_on_tertiary') : + $r('sys.color.comp_background_tertiary')) + .margin({ right: $r('sys.float.padding_level8') }) + .bindPopup(this.displayPopup, { + message: this.allTagStr, + mask: false, + popupColor: $r('sys.color.ohos_id_blur_style_component_regular_color'), + shadow: ShadowStyle.OUTER_DEFAULT_SM, + offset: { x: SampleDetailConstant.HOVER_POPUP_LEFT, y: 0 }, + autoCancel: true, + radius: ($r('sys.float.corner_radius_level4')), + messageOptions: { + textColor: $r('sys.color.font_primary'), + font: { size: $r('sys.float.Body_M') } + } + }) + .onHover((isHover: boolean) => { + this.displayPopup = isHover; + if (this.tags.length == 0) { + this.displayPopup = false; + } + }) + } + } + .margin({ top: $r('sys.float.padding_level4') }) + } +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/constant/CommonConstants.ets b/features/devpractices/src/main/ets/constant/CommonConstants.ets new file mode 100644 index 0000000000000000000000000000000000000000..130f2b64788b72bfe40b7fccf73ad2c9230b01c3 --- /dev/null +++ b/features/devpractices/src/main/ets/constant/CommonConstants.ets @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 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 SampleDetailConstant { + // Progress + public static readonly PROGRESS_START: number = 0; + public static readonly PROGRESS_FINISH: number = 100; + // MaxLine + public static readonly SUBTITLE_MAXLINE: number = 3; + // The offset of the popup component relative to the display position defined by the placement setting. + public static HOVER_POPUP_LEFT: number = 10; +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/model/SampleData.ets b/features/devpractices/src/main/ets/model/SampleData.ets new file mode 100644 index 0000000000000000000000000000000000000000..9f16658436ee8e3ea4a621a3448c12f56763c20f --- /dev/null +++ b/features/devpractices/src/main/ets/model/SampleData.ets @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 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 { LoadingModel } from '@ohos/common'; +import type { BannerData, } from '@ohos/commonbusiness'; +import { CardStyleTypeEnum, CardTypeEnum, MediaTypeEnum } from '@ohos/commonbusiness'; + +export class SampleContent { + public id: number = 0; + public type: CardTypeEnum = CardTypeEnum.UNKNOWN; + public mediaType: MediaTypeEnum = MediaTypeEnum.IMAGE; + public mediaUrl: string = ''; + public title: string = ''; + public subTitle: string = ''; + public tags: string[] = []; +} + +export class SampleCardData { + public id: number = 0; + public cardTitle: string = ''; + public cardSubTitle: string = ''; + public cardType: CardTypeEnum = CardTypeEnum.UNKNOWN; + public cardStyleType: CardStyleTypeEnum = CardStyleTypeEnum.LIST; + public cardImage: string = ''; + public version: string = ''; + public sampleContents: SampleContent[] = []; + public detailCardId: number = 0; +} + +@Observed +export class SampleCategory { + public loadingModel: LoadingModel = new LoadingModel(); + public currentPage: number = 1; + public id: number = 0; + public categoryName: string = ''; + public categoryType: number = 0; + public tabIcon: string = ''; + public tabIconSelected: string = ''; + public sampleCards: SampleCardData[] = []; +} + +@Observed +export class SampleData { + public bannerInfos?: BannerData[]; + public sampleCategories: SampleCategory[] = []; +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/model/SampleDetailData.ets b/features/devpractices/src/main/ets/model/SampleDetailData.ets new file mode 100644 index 0000000000000000000000000000000000000000..6c45b305005f39f1146d4086dc992b10e5a22fc2 --- /dev/null +++ b/features/devpractices/src/main/ets/model/SampleDetailData.ets @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 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 { SampleTypeEnum } from '../common/SampleConstant'; + +export class SingleSampleData { + public id: number = 0; + public title: string = ''; + public desc: string = ''; + public preInstalled: boolean = false; + public sampleType: SampleTypeEnum = SampleTypeEnum.COMMON_SAMPLE; + public isFavorite: boolean = false; + public mediaType: number = 0; + public mediaUrl: string = ''; + public originalUrl: string = ''; + public moduleName: string = ''; + public abilityName: string = ''; +} + +export class SampleCardDetail { + public id: number = 0; + public categoryType: number = 0; + public sampleDetail: SingleSampleData[] = []; +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/model/SampleDetailModel.ets b/features/devpractices/src/main/ets/model/SampleDetailModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..4800cbb73425720b75bd8ecd73d19818cff5c31c --- /dev/null +++ b/features/devpractices/src/main/ets/model/SampleDetailModel.ets @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 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 type { SingleSampleData } from '../model/SampleDetailData'; +import { SampleService } from '../service/SampleService'; + +export class SampleDetailModel { + private service: SampleService = new SampleService(); + private static instance: SampleDetailModel; + + private constructor() { + } + + public static getInstance(): SampleDetailModel { + if (!SampleDetailModel.instance) { + SampleDetailModel.instance = new SampleDetailModel(); + } + return SampleDetailModel.instance; + } + + public getSampleCardDetails(sampleCardId: number): Promise { + return this.service.getSampleDetailsByPreference(sampleCardId) + .then((data: SingleSampleData[]) => { + return data; + }) + .catch(() => { + return this.service.getSampleDetails(sampleCardId) + .then((data: SingleSampleData[]) => { + this.service.setSampleDetailToPreference(sampleCardId, data); + return data; + }); + }); + } +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/model/SampleModel.ets b/features/devpractices/src/main/ets/model/SampleModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..8b1bb33ecf80a598b8202a8271c4c9dd10f49d4e --- /dev/null +++ b/features/devpractices/src/main/ets/model/SampleModel.ets @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024 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 type { BusinessError } from '@kit.BasicServicesKit'; +import type { ResponseData } from '@ohos/common'; +import { Logger } from '@ohos/common'; +import { SampleService } from '../service/SampleService'; +import type { SampleCardData, SampleData } from './SampleData'; +import type { SampleCardDetail } from './SampleDetailData'; + +const TAG = '[SampleModel]'; + +export class SampleModel { + private service: SampleService = new SampleService(); + private static instance: SampleModel; + + private constructor() { + } + + public static getInstance(): SampleModel { + if (!SampleModel.instance) { + SampleModel.instance = new SampleModel(); + } + return SampleModel.instance; + } + + public getSamplePage(currentPage: number, pageSize: number): Promise> { + return this.service.getSamplePageByPreference(currentPage, pageSize) + .then((data: ResponseData) => { + return data; + }).catch((err: string) => { + Logger.error(TAG, + `getSamplePage data from network failed! try to get data form preference. ${err}`); + return this.service.getSamplePage(currentPage, pageSize) + .then((data: ResponseData) => { + this.service.setSamplePageToPreference(data); + return data; + }); + }); + } + + public getSampleList(categoryType: number, currentPage: number, + pageSize: number): Promise> { + return this.service.getSampleListByPreference(categoryType, currentPage, pageSize) + .then((data: ResponseData) => { + return data; + }).catch((err: string) => { + Logger.error(TAG, + `getSamplePage data from network failed! try to get data form preference. ${err}`); + return this.service.getSampleList(categoryType, currentPage, pageSize) + .then((data: ResponseData) => { + this.service.setSampleListToPreference(categoryType, currentPage, pageSize, data); + return data; + }); + }); + } + + public preloadSamplePageData(): Promise { + this.service.getSampleDetailsByMock().then((sampleDetailList: SampleCardDetail[]) => { + this.service.setSampleDetailsToPreference(sampleDetailList); + }); + return this.service.getSamplePage() + .then((result: ResponseData) => { + this.service.setSamplePageToPreference(result); + }).catch((err: BusinessError) => { + Logger.error(TAG, + `preloadDiscoveryData data from network failed. ${err.code}, ${err.message}`); + }); + } +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/service/SampleService.ets b/features/devpractices/src/main/ets/service/SampleService.ets new file mode 100644 index 0000000000000000000000000000000000000000..131f5796c29ead82155e592eeba702bde6a6f552 --- /dev/null +++ b/features/devpractices/src/main/ets/service/SampleService.ets @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2024 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 type { BusinessError } from '@kit.BasicServicesKit'; +import type { ResponseData } from '@ohos/common'; +import { Logger, MockRequest, PreferenceManager } from '@ohos/common'; +import type { SampleCardData, SampleCategory, SampleData } from '../model/SampleData'; +import type { SampleCardDetail, SingleSampleData } from '../model/SampleDetailData'; + +const TAG: string = '[SampleService]'; + +export class SampleService { + public constructor() { + } + + public getSampleListByMock(): Promise> { + return new Promise((resolve: (value: ResponseData) => void, + reject: (reason?: BusinessError) => void) => { + MockRequest.call>(SampleTrigger.SAMPLE_PAGE) + .then((result: Object) => { + resolve(result as ResponseData); + }) + .catch((error: BusinessError) => { + reject(error); + }); + }); + } + + public getSampleDetailsByMock(): Promise { + return new Promise((resolve: (value: SampleCardDetail[]) => void, + reject: (reason?: BusinessError) => void) => { + MockRequest.call(SampleTrigger.SAMPLE_DETAILS_ALL) + .then((result: SampleCardDetail[]) => { + resolve(result as SampleCardDetail[]); + }) + .catch((error: BusinessError) => { + reject(error); + }); + }); + } + + public getSamplePage(currentPage?: number, pageSize?: number): Promise> { + Logger.info(TAG, `getComponentList param: currentPage ${currentPage}, pageSize: ${pageSize} `); + return this.getSampleListByMock(); + } + + public getSamplePageByPreference(currentPage: number, pageSize: number): Promise> { + return new Promise((resolve: (value: ResponseData) => void, + reject: (reason?: string) => void) => { + PreferenceManager.getInstance() + .getValue>>(SampleTrigger.SAMPLE_PAGE) + .then((resp) => { + if (!resp) { + reject('There is no data in the Preference'); + } + resp = (resp as Record>); + const ret = resp[`${currentPage}_${pageSize}`]; + if (!ret) { + reject('There is no data in the Preference'); + } + resolve(ret); + }); + }); + } + + public setSamplePageToPreference(data: ResponseData): Promise { + return new Promise((resolve: () => void) => { + PreferenceManager.getInstance().hasValue(SampleTrigger.SAMPLE_PAGE) + .then((result) => { + if (result) { + PreferenceManager.getInstance() + .getValue>>(SampleTrigger.SAMPLE_PAGE) + .then((resp) => { + resp = (resp as Record>); + resp[`${data.currentPage}_${data.pageSize}`] = data; + PreferenceManager.getInstance().setValue(SampleTrigger.SAMPLE_PAGE, resp); + resolve(); + }); + } else { + const record: Record> = {}; + record[`${data.currentPage}_${data.pageSize}`] = data; + PreferenceManager.getInstance().setValue(SampleTrigger.SAMPLE_PAGE, record); + } + }); + }); + } + + public getSampleList(categoryType: number, currentPage: number, + pageSize: number): Promise> { + return new Promise((resolve: (value: ResponseData) => void, + reject: (reason?: Object) => void) => { + this.getSampleListByMock().then((result: ResponseData) => { + const sampleCategoryList = result.data.sampleCategories; + const categoryData = + sampleCategoryList.find((category: SampleCategory) => category.categoryType === categoryType); + const response: ResponseData = { + currentPage, + pageSize, + totalSize: 0, + data: [], + }; + if (categoryData) { + response.totalSize = categoryData.sampleCards.length; + response.data = categoryData.sampleCards; + } + resolve(response); + }).catch(() => { + reject(); + }); + }); + } + + public getSampleListByPreference(categoryType: number, currentPage: number, + pageSize: number): Promise> { + return new Promise((resolve: (value: ResponseData) => void, + reject: (reason?: string) => void) => { + PreferenceManager.getInstance() + .getValue>>(SampleTrigger.SAMPLE_LIST) + .then((resp) => { + if (!resp) { + reject('There is no data in the Preference'); + } + resp = (resp as Record>); + const ret = resp[`${categoryType}_${currentPage}_${pageSize}`]; + if (!ret) { + reject('There is no data in the Preference'); + } + resolve(ret); + }) + }) + } + + public setSampleListToPreference(categoryType: number, currentPage: number, + pageSize: number, data: ResponseData): Promise { + return new Promise((resolve: () => void) => { + PreferenceManager.getInstance().hasValue(SampleTrigger.SAMPLE_LIST) + .then((result) => { + if (result) { + PreferenceManager.getInstance() + .getValue>>(SampleTrigger.SAMPLE_LIST) + .then((resp) => { + resp = (resp as Record>); + resp[`${categoryType}_${currentPage}_${pageSize}`] = data; + PreferenceManager.getInstance().setValue(SampleTrigger.SAMPLE_LIST, resp); + resolve(); + }); + } else { + const record: Record> = {}; + record[`${categoryType}_${currentPage}_${pageSize}`] = data; + PreferenceManager.getInstance().setValue(SampleTrigger.SAMPLE_LIST, record); + } + }); + }); + } + + public getSampleDetails(sampleCardId: number): Promise { + return new Promise((resolve: (value: SingleSampleData[]) => void, reject: (reason?: Object) => void) => { + this.getSampleDetailsByMock().then((result: SampleCardDetail[]) => { + const cardDetail = result.find((item: SampleCardDetail) => item.id === sampleCardId); + if (cardDetail) { + resolve(cardDetail.sampleDetail); + } else { + reject(`Cann't find this Sample Card. id: ${sampleCardId}`); + } + }).catch((error: BusinessError) => { + reject(error); + }); + }); + } + + public getSampleDetailsByPreference(sampleCardId: number): Promise { + return new Promise((resolve: (value: SingleSampleData[]) => void, + reject: (reason?: string) => void) => { + PreferenceManager.getInstance() + .getValue>(SampleTrigger.SAMPLE_DETAILS) + .then((resp) => { + if (!resp) { + reject('There is no data in the Preference'); + } + resp = (resp as Record); + const ret = resp[`SampleCardDetail_${sampleCardId}`]; + if (!ret) { + reject('There is no data in the Preference'); + } + resolve(ret); + }); + }); + } + + public setSampleDetailsToPreference(data: SampleCardDetail[]): Promise { + return new Promise((resolve: () => void) => { + PreferenceManager.getInstance().hasValue(SampleTrigger.SAMPLE_DETAILS) + .then((result) => { + if (result) { + PreferenceManager.getInstance() + .getValue>(SampleTrigger.SAMPLE_DETAILS) + .then((resp) => { + const res: Record = (resp as Record) || {}; + data.forEach((item: SampleCardDetail) => { + res[`SampleCardDetail_${item.id}`] = item.sampleDetail; + }); + PreferenceManager.getInstance().setValue(SampleTrigger.SAMPLE_DETAILS, res); + resolve(); + }); + } else { + const record: Record = {}; + data.forEach((item: SampleCardDetail) => { + record[`SampleCardDetail_${item.id}`] = item.sampleDetail; + }); + PreferenceManager.getInstance().setValue(SampleTrigger.SAMPLE_DETAILS, record); + } + }); + }); + } + + public setSampleDetailToPreference(sampleCardId: number, data: SingleSampleData[]): Promise { + return new Promise((resolve: () => void) => { + PreferenceManager.getInstance().hasValue(SampleTrigger.SAMPLE_DETAILS) + .then((result) => { + if (result) { + PreferenceManager.getInstance() + .getValue>(SampleTrigger.SAMPLE_DETAILS) + .then((resp) => { + const res: Record = (resp as Record) || {}; + res[`SampleCardDetail_${sampleCardId}`] = data; + PreferenceManager.getInstance().setValue(SampleTrigger.SAMPLE_DETAILS, res); + resolve(); + }); + } else { + const record: Record = {}; + record[`SampleCardDetail_${sampleCardId}`] = data; + PreferenceManager.getInstance().setValue(SampleTrigger.SAMPLE_DETAILS, record); + } + }); + }); + } +} + +enum SampleTrigger { + SAMPLE_PAGE = 'sample-page', + SAMPLE_LIST = 'sample-list', + SAMPLE_DETAILS = 'sample-details', + SAMPLE_DETAILS_ALL = 'sample-details-all', +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/view/PracticeDetailView.ets b/features/devpractices/src/main/ets/view/PracticeDetailView.ets new file mode 100644 index 0000000000000000000000000000000000000000..2c2695c03f9f5f1b44d53575d3741bf13fed46c8 --- /dev/null +++ b/features/devpractices/src/main/ets/view/PracticeDetailView.ets @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2024 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 { ConfigurationConstant } from '@kit.AbilityKit'; +import { promptAction } from '@kit.ArkUI'; +import type { GlobalInfoModel } from '@ohos/common'; +import { CommonConstants, LoadingStatus, TopNavigationView, WindowUtil } from '@ohos/common'; +import { BaseDetailComponent, SampleDetailParams } from '@ohos/commonbusiness'; +import { SampleComponent } from '../component/SampleComponent'; +import type { SampleDetailData, SampleDetailState } from '../viewmodel/SampleDetailState'; +import { + InitSampleDetailEvent, + PopEvent, + SampleDetailPageVM, + SetIndexEvent, +} from '../viewmodel/SampleDetailPageVM'; + +@Builder +export function SampleDetailViewBuilder() { + SampleDetailView() +} + +@Component +export struct SampleDetailView { + @StorageProp('systemColorMode') @Watch('handleColorModeChange') systemColorMode: ConfigurationConstant.ColorMode = + AppStorage.get('systemColorMode')!; + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + viewModel: SampleDetailPageVM = SampleDetailPageVM.getInstance(); + @State sampleDetailState: SampleDetailState = this.viewModel.getState(); + @State loadingStatus: LoadingStatus = LoadingStatus.IDLE; + @Provide currentIndex: number = -1; + private sampleCardId: number = -1; + + aboutToAppear(): void { + this.handleColorModeChange(); + } + + handleColorModeChange() { + const isSystemDark: boolean = (this.systemColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK); + WindowUtil.updateStatusBarColor(getContext(this), isSystemDark); + } + + private onBack(): void { + if (!this.sampleDetailState.isBackPressed) { + this.viewModel.sendEvent(new PopEvent()); + } + } + + @Builder + SampleDetailBuilder() { + Swiper() { + ForEach(this.sampleDetailState.sampleDatas, (item: SampleDetailData, index: number) => { + SampleComponent({ + singleSampleData: item, + sampleIndex: index, + showIndicator: this.sampleDetailState.sampleCount > 1, + }) + }, (item: SampleDetailData) => item.id.toString()) + } + .layoutWeight(1) + .width('100%') + .padding({ top: (this.globalInfoModel.statusBarHeight + CommonConstants.NAVIGATION_HEIGHT) }) + .indicator(this.sampleDetailState.sampleCount > 1) + .index(this.currentIndex) + .disableSwipe(this.sampleDetailState.sampleCount === 1 || this.sampleDetailState.installingStatus || + this.sampleDetailState.downloadingStatus) + .onChange((index: number) => { + this.viewModel.sendEvent(new SetIndexEvent(index)); + this.currentIndex = index; + }) + .gesture( + SwipeGesture({ direction: SwipeDirection.Horizontal }) + .onAction(() => { + if (this.sampleDetailState.sampleDatas[this.currentIndex].sampleCard.downloadProgress >= 0 && + this.sampleDetailState.sampleCount > 1) { + promptAction.showToast({ message: $r('app.string.swiper_gesture') }); + } + }) + ) + } + + @Builder + TopTitleViewBuilder() { + TopNavigationView({ + topNavigationData: { + title: $r('app.string.sample_title'), + onBackClick: () => { + this.onBack(); + } + }, + }) + } + + build() { + NavDestination() { + BaseDetailComponent({ + detailContentView: () => { + this.SampleDetailBuilder() + }, + topTitleView: () => { + this.TopTitleViewBuilder() + }, + loadingStatus: this.sampleDetailState.loadingStatus, + }) + } + .onReady((cxt: NavDestinationContext) => { + const params = cxt.pathInfo.param as SampleDetailParams; + this.sampleCardId = params.sampleCardId; + this.currentIndex = params.currentIndex; + this.viewModel.sendEvent(new InitSampleDetailEvent(this.sampleCardId, params.currentIndex)); + }) + .onBackPressed(() => { + this.onBack(); + return true; + }) + .onWillShow(() => { + this.sampleDetailState.isBackPressed = false; + }) + .onWillHide(() => { + this.sampleDetailState.isBackPressed = false; + }) + .hideTitleBar(true) + .padding({ bottom: this.globalInfoModel.naviIndicatorHeight, top: this.globalInfoModel.statusBarHeight }) + .width('100%') + .height('100%') + .backgroundColor($r('sys.color.background_secondary')) + } +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/view/PracticesView.ets b/features/devpractices/src/main/ets/view/PracticesView.ets new file mode 100644 index 0000000000000000000000000000000000000000..ed1b7acda9db9d870155dab8b46f4e33a75546c0 --- /dev/null +++ b/features/devpractices/src/main/ets/view/PracticesView.ets @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2024 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 { ConfigurationConstant } from '@kit.AbilityKit'; +import type { GlobalInfoModel, PageContext } from '@ohos/common'; +import { BreakpointType, BreakpointTypeEnum, CommonConstants } from '@ohos/common'; +import type { BannerData } from '@ohos/commonbusiness'; +import { + BannerCard, + BaseHomeEventType, + BaseHomeView, + CalculateHeightParam, + FullScreenNavigation, + OffsetParam, + TabBarType, +} from '@ohos/commonbusiness'; +import { CategorySamples } from '../component/CategorySamples'; +import type { SampleCategory } from '../model/SampleData'; +import type { PracticeState } from '../viewmodel/PracticeState'; +import { LoadSamplePageParam, PracticeEventType, PracticeViewModel } from '../viewmodel/PracticeViewModel'; + +@Component({ freezeWhenInactive: true }) +export struct PracticesView { + viewModel: PracticeViewModel = PracticeViewModel.getInstance(); + @StorageProp('GlobalInfoModel') @Watch('handleBreakPointChange') globalInfoModel: GlobalInfoModel = + AppStorage.get('GlobalInfoModel')!; + @StorageProp('systemColorMode') @Watch('handleColorModeChange') systemColorMode: ConfigurationConstant.ColorMode = + AppStorage.get('systemColorMode')!; + @State naviStatusHeight: number = CommonConstants.NAVIGATION_HEIGHT + this.globalInfoModel.statusBarHeight; + @State currentIndex: number = 0; + @State practiceState: PracticeState = this.viewModel.getState(); + @State showTopTab: boolean = true; + private categoryTabController: TabsController = new TabsController(); + private listScroller: Scroller = new Scroller(); + private headerScroller: Scroller = new Scroller(); + private samplePageContext: PageContext = AppStorage.get('samplePageContext')!; + private cornerNum: Length = this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? $r('sys.float.corner_radius_level4') : '50%'; + + aboutToAppear(): void { + this.viewModel.sendEvent({ + type: PracticeEventType.LOAD_SAMPLE_PAGE, + param: { + callback: () => { + const categorySize: number = this.practiceState.sampleCategories.length; + const preloadIndexList: number[] = []; + for (let i = 0; i < categorySize; i++) { + preloadIndexList.push(i); + } + this.categoryTabController.preloadItems(preloadIndexList); + } + }, + }); + } + + handleBreakPointChange() { + this.viewModel.sendEvent({ + type: BaseHomeEventType.HANDLE_BREAKPOINT_CHANGE, + param: { yOffset: (this.listScroller?.currentOffset()?.yOffset || 0), tabIndex: TabBarType.SAMPLE }, + }); + } + + handleColorModeChange() { + this.viewModel.sendEvent({ + type: BaseHomeEventType.HANDLE_COLOR_CHANGE, + param: { yOffset: (this.listScroller?.currentOffset()?.yOffset || 0), tabIndex: TabBarType.SAMPLE }, + }); + } + + @Builder + CategoryHeaderBuilder() { + if (this.practiceState.sampleCategories.length > 1) { + Scroll(this.headerScroller) { + Row({ + space: new BreakpointType({ + sm: CommonConstants.SPACE_10, + md: CommonConstants.SPACE_12, + lg: CommonConstants.SPACE_12, + }).getValue(this.globalInfoModel.currentBreakpoint), + }) { + ForEach(this.practiceState.sampleCategories, (item: SampleCategory, index: number) => { + Row() { + if (item.tabIcon) { + Image($rawfile(this.currentIndex === index ? item.tabIcon : item.tabIconSelected)) + .height($r('app.float.tab_icon_height')) + .objectFit(ImageFit.Contain) + .margin({ right: $r('sys.float.padding_level2') }) + } + Text(item.categoryName) + .fontSize($r('sys.float.Body_M')) + .fontWeight(FontWeight.Medium) + .fontColor(this.currentIndex === index ? $r('sys.color.font_on_primary') : + $r('sys.color.font_tertiary')) + } + .backgroundColor(this.currentIndex === index ? $r('sys.color.brand') : + $r('sys.color.ohos_id_color_button_normal')) + .padding({ + left: $r('sys.float.padding_level8'), + right: $r('sys.float.padding_level8'), + top: $r('sys.float.padding_level4'), + bottom: $r('sys.float.padding_level4'), + }) + .borderRadius(this.cornerNum) + .onClick(() => { + this.currentIndex = index; + if (!this.practiceState.sampleCategories[index].sampleCards) { + const topOffsetY = + (this.practiceState.bannerState.bannerHeight - this.globalInfoModel.naviIndicatorHeight - + CommonConstants.NAVIGATION_HEIGHT); + if (this.listScroller?.currentOffset()?.yOffset > topOffsetY) { + this.listScroller.scrollTo({ yOffset: topOffsetY, xOffset: 0 }); + } + } + }) + }, (item: SampleCategory) => item.id.toString()) + } + .padding({ + left: new BreakpointType({ + sm: $r('sys.float.padding_level6'), + md: $r('sys.float.padding_level12'), + lg: $r('sys.float.padding_level16'), + }).getValue(this.globalInfoModel.currentBreakpoint), + right: $r('sys.float.padding_level6'), + bottom: $r('sys.float.padding_level5'), + top: $r('sys.float.padding_level8'), + }) + } + .align(Alignment.Start) + .width('100%') + .scrollBar(BarState.Off) + .scrollable(ScrollDirection.Horizontal) + } + } + + @Builder + ContentViewBuilder() { + List({ + scroller: this.listScroller, + space: this.practiceState.sampleCategories.length === 1 ? CommonConstants.SPACE_12 : 0, + }) { + ListItem() { + BannerCard({ + tabViewType: TabBarType.SAMPLE, + bannerState: this.practiceState.bannerState, + handleItemClick: (banner: BannerData) => { + this.viewModel.sendEvent({ type: BaseHomeEventType.JUMP_BANNER_DETAIL, param: banner }); + }, + }) + } + .height(this.practiceState.bannerHeight) + + ListItemGroup({ header: this.CategoryHeaderBuilder() }) { + ListItem() { + Tabs({ index: this.currentIndex, controller: this.categoryTabController }) { + ForEach(this.practiceState.sampleCategories, (sampleCategory: SampleCategory) => { + TabContent() { + CategorySamples({ sampleCategory }) + } + }, (item: SampleCategory) => `${item.categoryType}_${item.sampleCards?.length}`) + } + .scrollable(false) + .barHeight(0) + } + } + .padding({ + left: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG ? CommonConstants.TAB_BAR_WIDTH : 0, + }) + } + .scrollBar(BarState.Off) + .width('100%') + .height('100%') + .clip(false) + .edgeEffect(this.practiceState.hasEdgeEffect ? EdgeEffect.Spring : EdgeEffect.None) + .sticky(StickyStyle.Header) + .onScrollFrameBegin((offset: number, state: ScrollState) => { + const param: CalculateHeightParam = { offset, state, yOffset: this.listScroller.currentOffset().yOffset }; + const bannerChangeHeight: boolean | void = this.viewModel.sendEvent({ + type: BaseHomeEventType.CALCULATE_BANNER_HEIGHT, + param, + }); + if (bannerChangeHeight) { + return { offsetRemain: 0 }; + } + return { offsetRemain: offset }; + }) + .onDidScroll(() => { + this.viewModel.sendEvent({ + type: BaseHomeEventType.HANDLE_SCROLL_OFFSET, + param: { yOffset: this.listScroller.currentOffset().yOffset, tabIndex: TabBarType.SAMPLE }, + }); + }) + .backgroundColor($r('sys.color.background_secondary')) + } + + @Builder + TopTitleViewBuilder() { + FullScreenNavigation({ + topNavigationData: this.practiceState.topNavigationData, + tabView: () => { + this.CategoryHeaderBuilder() + }, + }) + } + + build() { + Navigation(this.samplePageContext.navPathStack) { + BaseHomeView({ + loadingModel: this.practiceState.loadingModel, + contentView: () => { + this.ContentViewBuilder() + }, + topTitleView: () => { + this.TopTitleViewBuilder() + }, + reloadData: () => { + this.viewModel.sendEvent({ type: PracticeEventType.LOAD_SAMPLE_PAGE, param: null }); + }, + }) + } + .mode(NavigationMode.Stack) + .hideTitleBar(true) + } +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/viewmodel/PracticeState.ets b/features/devpractices/src/main/ets/viewmodel/PracticeState.ets new file mode 100644 index 0000000000000000000000000000000000000000..33b08f2e1803fc0383337ab9759dd3abcbba00a9 --- /dev/null +++ b/features/devpractices/src/main/ets/viewmodel/PracticeState.ets @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 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 { BaseHomeState } from '@ohos/commonbusiness'; +import type { SampleCategory } from '../model/SampleData'; + +@Observed +export class PracticeState extends BaseHomeState { + public sampleCategories: SampleCategory[] = []; + + constructor() { + super(); + } +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/viewmodel/PracticeViewModel.ets b/features/devpractices/src/main/ets/viewmodel/PracticeViewModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..941cb5259ff322538c955b85c4fab3c190da608e --- /dev/null +++ b/features/devpractices/src/main/ets/viewmodel/PracticeViewModel.ets @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2024 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 { ConfigurationConstant } from '@kit.AbilityKit'; +import type { BusinessError } from '@kit.BasicServicesKit'; +import type { GlobalInfoModel, ResponseData } from '@ohos/common'; +import { + BreakpointTypeEnum, + LoadingModel, + LoadingStatus, + Logger, + PageContext, + RequestErrorCode, + StatusBarColorType, + WindowUtil, +} from '@ohos/common'; +import type { BannerData } from '@ohos/commonbusiness'; +import { + BaseHomeEventParam, + BaseHomeEventType, + BaseHomeViewModel, + SampleDetailParams, + TAB_CONTENT_STATUSES, + TabBarType, +} from '@ohos/commonbusiness'; +import type { SampleCardData, SampleCategory, SampleData } from '../model/SampleData'; +import { SampleModel } from '../model/SampleModel'; +import { PracticeState } from './PracticeState'; + +const TAG = '[PracticeViewModel]'; + +export class PracticeViewModel extends BaseHomeViewModel { + private static instance: PracticeViewModel; + private sampleModel: SampleModel = SampleModel.getInstance(); + + private constructor() { + super(new PracticeState()); + this.state.topNavigationData.title = $r('app.string.sample_name'); + } + + static getInstance(): PracticeViewModel { + if (!PracticeViewModel.instance) { + PracticeViewModel.instance = new PracticeViewModel(); + } + return PracticeViewModel.instance; + } + + sendEvent(eventParam: PracticeEventParam): void | boolean { + const eventType: PracticeEventType | BaseHomeEventType = eventParam.type; + if (eventType === PracticeEventType.LOAD_SAMPLE_PAGE) { + return this.loadSamplePage(eventParam.param as LoadSamplePageParam); + } else if (eventType === PracticeEventType.LOAD_SAMPLE_LIST) { + return this.loadSampleList(eventParam.param as SampleCategory); + } else if (eventType === PracticeEventType.JUMP_DETAIL_DETAIL) { + return this.jumpDetailView(eventParam.param as SampleDetailParams); + } else { + return super.sendEvent(eventParam as BaseHomeEventParam); + } + } + + protected loadSamplePage(param: LoadSamplePageParam): void { + const isDark: boolean = AppStorage.get('systemColorMode') === ConfigurationConstant.ColorMode.COLOR_MODE_DARK; + this.state.loadingModel.loadingStatus = LoadingStatus.LOADING; + this.state.topNavigationData.titleColor = isDark ? StatusBarColorType.WHITE : StatusBarColorType.BLACK; + this.state.topNavigationData.isBlur = false; + WindowUtil.updateStatusBarColor(getContext(), isDark); + this.sampleModel.getSamplePage(1, this.pageSize) + .then((result: ResponseData) => { + if (result.data.bannerInfos) { + result.data.bannerInfos.forEach((item: BannerData) => { + item.tabViewType = TabBarType.SAMPLE; + }); + this.state.bannerState.bannerResource.setDataArray([...result.data.bannerInfos]); + } + const categoryList: SampleCategory[] = []; + result.data.sampleCategories.forEach((sampleCategory: SampleCategory) => { + sampleCategory.currentPage = 1; + sampleCategory.loadingModel = new LoadingModel(); + sampleCategory.loadingModel.loadingStatus = + sampleCategory.sampleCards?.length === 0 ? LoadingStatus.OFF : LoadingStatus.SUCCESS; + sampleCategory.loadingModel.hasNextPage = false; + categoryList.push(sampleCategory); + }); + this.state.sampleCategories = categoryList; + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + if (globalInfoModel.currentBreakpoint !== BreakpointTypeEnum.LG && + globalInfoModel.currentBreakpoint !== BreakpointTypeEnum.XL) { + this.state.topNavigationData.titleColor = StatusBarColorType.WHITE; + WindowUtil.updateStatusBarColor(getContext(), true); + TAB_CONTENT_STATUSES[TabBarType.SAMPLE] = true; + } + this.state.loadingModel.loadingStatus = LoadingStatus.SUCCESS; + param.callback(); + }) + .catch((error: BusinessError) => { + Logger.error(TAG, `load PracticePage Failed, ${error.code} ${error.message}`); + WindowUtil.updateStatusBarColor(getContext(), isDark); + TAB_CONTENT_STATUSES[TabBarType.SAMPLE] = isDark; + if (error.code === RequestErrorCode.ERROR_NETWORK_CONNECT_FAILED) { + this.state.loadingModel.loadingStatus = LoadingStatus.NO_NETWORK; + } else { + this.state.loadingModel.loadingStatus = LoadingStatus.FAILED; + } + }); + } + + protected loadSampleList(currentCategory: SampleCategory): void { + currentCategory.loadingModel = new LoadingModel(LoadingStatus.LOADING); + this.changeSampleCategories(currentCategory); + this.sampleModel.getSampleList(currentCategory.categoryType, currentCategory.currentPage, this.pageSize) + .then((result: ResponseData) => { + currentCategory.loadingModel.loadingStatus = LoadingStatus.SUCCESS; + currentCategory.loadingModel.hasNextPage = + result.data.length === this.pageSize && (result.totalSize > currentCategory.currentPage * this.pageSize); + currentCategory.sampleCards = (currentCategory.sampleCards || []).concat(result.data); + this.changeSampleCategories(currentCategory); + }) + .catch((error: BusinessError) => { + Logger.error(TAG, `getSampleList failed,cause ${error.code} ${error.message}`); + if (error.code === RequestErrorCode.ERROR_NETWORK_CONNECT_FAILED) { + currentCategory.loadingModel = new LoadingModel(LoadingStatus.NO_NETWORK); + } else { + currentCategory.loadingModel = new LoadingModel(LoadingStatus.FAILED); + } + this.changeSampleCategories(currentCategory); + }); + } + + protected jumpDetailView(param: SampleDetailParams): void { + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + const pageContext: PageContext = + globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? AppStorage.get('samplePageContext')! : + AppStorage.get('pageContext') as PageContext; + pageContext.openPage({ + routerName: 'SampleDetailView', + param: param, + }, true); + } + + private changeSampleCategories(currentCategory: SampleCategory): void { + const categoryList: SampleCategory[] = []; + this.state.sampleCategories.forEach((sampleCategory: SampleCategory) => { + if (sampleCategory.categoryType === currentCategory.categoryType) { + categoryList.push(currentCategory); + } else { + categoryList.push(sampleCategory); + } + }); + this.state.sampleCategories = categoryList; + } +} + +export enum PracticeEventType { + JUMP_DETAIL_DETAIL = 'jumpDetailView', + LOAD_SAMPLE_LIST = 'loadSampleList', + LOAD_SAMPLE_PAGE = 'loadSamplePage', +} + +export interface PracticeEventParam { + type: PracticeEventType | BaseHomeEventType; + param: T; +} + +export interface LoadSamplePageParam { + callback: Function; +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/viewmodel/SampleDetailPageVM.ets b/features/devpractices/src/main/ets/viewmodel/SampleDetailPageVM.ets new file mode 100644 index 0000000000000000000000000000000000000000..1a069b88bb14d4f70276d10d2a8c65c928599ba1 --- /dev/null +++ b/features/devpractices/src/main/ets/viewmodel/SampleDetailPageVM.ets @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2024 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 type { common } from '@kit.AbilityKit'; +import { promptAction } from '@kit.ArkUI'; +import { BusinessError, emitter } from '@kit.BasicServicesKit'; +import { connection } from '@kit.NetworkKit'; +import { moduleInstallManager } from '@kit.StoreKit'; +import { + BaseVM, + BaseVMEvent, + BreakpointTypeEnum, + CommonConstants, + DynamicInstallManager, + GlobalInfoModel, + LoadingStatus, + Logger, + PageContext, +} from '@ohos/common'; +import { SampleCardData, SampleDetailData, SampleDetailState } from './SampleDetailState'; +import type { SingleSampleData } from '../model/SampleDetailData'; +import { SampleDetailModel } from '../model/SampleDetailModel'; +import { SampleDetailConstant } from '../constant/CommonConstants'; +import { SampleTypeEnum } from '../common/SampleConstant'; + +const TAG = '[SampleDetailPageVM]'; + +export class SampleDetailPageVM extends BaseVM { + private static instance: SampleDetailPageVM; + private sampleDetailModel = SampleDetailModel.getInstance(); + + private constructor() { + super(new SampleDetailState()); + } + + public static getInstance(): SampleDetailPageVM { + if (!SampleDetailPageVM.instance) { + SampleDetailPageVM.instance = new SampleDetailPageVM(); + } + return SampleDetailPageVM.instance; + } + + public sendEvent(baseVMEvent: BaseVMEvent): void { + if (baseVMEvent instanceof TerminateTaskEvent) { + this.terminateTask(baseVMEvent); + } else if (baseVMEvent instanceof LoadSampleEvent) { + this.loadSample(); + } else if (baseVMEvent instanceof ChangeDownloadProgressEvent) { + this.changeDownloadProgress(baseVMEvent); + } else if (baseVMEvent instanceof BindSheetEvent) { + this.changeBindSheet(baseVMEvent); + } else if (baseVMEvent instanceof PopEvent) { + this.pop(); + } else if (baseVMEvent instanceof SetIndexEvent) { + this.setCurrentIndex(baseVMEvent); + } else if (baseVMEvent instanceof SetInstalledEvent) { + this.setInstalledStatus(baseVMEvent); + } else if (baseVMEvent instanceof InitSampleDetailEvent) { + this.initSampleDetail(baseVMEvent); + } + } + + private initSampleDetail(event: InitSampleDetailEvent): void { + this.state.loadingStatus = LoadingStatus.LOADING; + this.state.currentIndex = event.currentIndex; + this.state.downloadingStatus = false; + this.state.installingStatus = false; + this.initSampleData(event.sampleCardId); + } + + private setInstalledStatus(event: SetInstalledEvent): void { + if (DynamicInstallManager.getModuleStatus(this.state.sampleDatas[event.sampleIndex].sampleCard.moduleName) === + moduleInstallManager.InstallStatus.INSTALLED) { + this.setSampleInstalledStatus(event.sampleIndex, true); + } + } + + private setCurrentIndex(event: SetIndexEvent): void { + this.state.currentIndex = event.currentIndex; + } + + private terminateTask(event: TerminateTaskEvent): void { + if (this.state.downloadingStatus) { + DynamicInstallManager.cancelDownloadTask(this.state.taskId); + } + this.state.taskId = ''; + this.setSampleDownloadingStatus(event.sampleIndex, false, -1); + } + + private changeDownloadProgress(event: ChangeDownloadProgressEvent): void { + this.state.sampleDatas[event.sampleIndex].sampleCard.downloadProgress = event.downloadProgress; + } + + private changeBindSheet(event: BindSheetEvent): void { + this.state.sampleDatas[event.sampleIndex].sampleCard.bindSheetShow = event.dataValue; + } + + private pop(): void { + if (this.state.installingStatus) { + promptAction.showToast({ message: $r('app.string.installing_status') }); + } else { + this.state.isBackPressed = true; + if (this.state.downloadingStatus) { + this.state.downloadingStatus = false; + DynamicInstallManager.cancelDownloadTask(this.state.taskId); + this.state.taskId = ''; + } + emitter.off(CommonConstants.DYNAMIC_INSTALL_EVENT); + Logger.info(TAG, 'cancelDownloadListener success'); + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + const pageContext: PageContext = + globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? AppStorage.get('samplePageContext')! : + AppStorage.get('pageContext') as PageContext; + pageContext.popPage(); + } + } + + private initSampleData(sampleCardId: number): void { + const sampleDetailData: SampleDetailData[] = []; + this.state.sampleDatas = []; + this.sampleDetailModel.getSampleCardDetails(sampleCardId).then((result: SingleSampleData[]) => { + this.state.sampleCount = result.length; + result.forEach((singleSampleData: SingleSampleData) => { + const sampleData: SampleDetailData = new SampleDetailData(); + sampleData.sampleCard = new SampleCardData(); + sampleData.id = sampleData.sampleCard.sampleId = singleSampleData.id; + sampleData.isFavorite = singleSampleData.isFavorite; + sampleData.mediaUrl = singleSampleData.mediaUrl; + sampleData.mediaType = singleSampleData.mediaType; + sampleData.sampleCard.title = singleSampleData.title; + sampleData.sampleCard.installed = singleSampleData.preInstalled; + sampleData.sampleCard.sampleType = singleSampleData.sampleType; + sampleData.sampleCard.desc = singleSampleData.desc; + sampleData.sampleCard.originalUrl = singleSampleData.originalUrl; + sampleData.sampleCard.moduleName = singleSampleData.moduleName; + sampleData.sampleCard.abilityName = singleSampleData.abilityName; + if (DynamicInstallManager.getModuleStatus(sampleData.sampleCard.moduleName) === + moduleInstallManager.InstallStatus.INSTALLED) { + sampleData.sampleCard.installed = true; + } + sampleDetailData.push(sampleData); + }); + this.state.sampleDatas = sampleDetailData; + this.initDownloadListener(); + this.state.loadingStatus = LoadingStatus.SUCCESS; + }).catch(() => { + this.state.loadingStatus = LoadingStatus.FAILED; + }); + } + + private initDownloadListener(): void { + emitter.on(CommonConstants.DYNAMIC_INSTALL_EVENT, (eventData) => { + if (eventData.data?.taskStatus === moduleInstallManager.TaskStatus.DOWNLOADING) { + if (eventData.data?.downloadedSize === eventData.data?.totalSize) { + this.setInstallingStatus(true); + } + this.setSampleDownloadingStatus(this.state.currentIndex, true, + Math.floor(((eventData.data?.downloadedSize ?? SampleDetailConstant.PROGRESS_START) / + (eventData.data?.totalSize ?? SampleDetailConstant.PROGRESS_FINISH)) * + SampleDetailConstant.PROGRESS_FINISH)); + Logger.info(TAG, `loadSampleCallback downloading size: ${eventData.data?.downloadedSize}`); + } else if (eventData.data?.taskStatus === moduleInstallManager.TaskStatus.INSTALL_SUCCESSFUL) { + this.setSampleInstalledStatus(this.state.currentIndex, true); + this.setSampleDownloadingStatus(this.state.currentIndex, false, -1); + this.setInstallingStatus(false); + DynamicInstallManager.loadModule(getContext(this) as common.UIAbilityContext, + this.state.sampleDatas[this.state.currentIndex].sampleCard.abilityName); + Logger.info(TAG, + `loadSampleCallback installed : ${this.state.sampleDatas[this.state.currentIndex].sampleCard.abilityName}`); + } else if (eventData.data?.taskStatus === moduleInstallManager.TaskStatus.INSTALL_WAITING || + eventData.data?.taskStatus === moduleInstallManager.TaskStatus.INSTALLING) { + this.setInstallingStatus(true); + Logger.info(TAG, `loadSampleCallback installing`); + } else if (eventData.data?.taskStatus === moduleInstallManager.TaskStatus.TASK_UNFOUND) { + DynamicInstallManager.unsubscribeDownloadProgress(); + } + }); + Logger.info(TAG, 'initDownloadListener success'); + } + + private loadSample(): void { + if (this.state.currentIndex < 0 || !this.state.sampleDatas[this.state.currentIndex]) { + return; + } + const currentSampleType = this.state.sampleDatas[this.state.currentIndex].sampleCard.sampleType; + if (currentSampleType === SampleTypeEnum.COMMON_CLIENT) { + promptAction.showToast({ message: $r('app.string.client_prompt') }); + } else if (currentSampleType === SampleTypeEnum.WEARABLE_CLIENT) { + promptAction.showToast({ message: $r('app.string.watch_prompt') }); + } else { + this.startTask(); + } + } + + private startTask(): void { + if (this.state.sampleDatas[this.state.currentIndex].sampleCard.installed) { + const ctx: common.UIAbilityContext = getContext() as common.UIAbilityContext; + try { + DynamicInstallManager.loadModule(ctx, this.state.sampleDatas[this.state.currentIndex].sampleCard.abilityName); + } catch (error) { + Logger.error(TAG, `Failed to loadModule, error code: ${error.code}, error data: ${error.message}`); + } + } else { + //Init progress listener + DynamicInstallManager.subscribeDownloadProgress(); + this.setSampleDownloadingStatus(this.state.currentIndex, false, -1); + connection.getDefaultNet().then((netHandle: connection.NetHandle) => { + connection.getNetCapabilities(netHandle).then((data: connection.NetCapabilities) => { + Logger.info(TAG, `succeeded to get connection data: ${data.bearerTypes[0]}`); + if (data.bearerTypes[0] === connection.NetBearType.BEARER_WIFI) { + this.setSampleDownloadingStatus(this.state.currentIndex, true, 0); + } + }).catch((error: BusinessError) => { + Logger.error(TAG, `Failed to getNetCapabilities, error code: ${error.code}, error data: ${error.message}`); + }) + }).catch((error: BusinessError) => { + Logger.error(TAG, `Failed to get connection error code: ${error.code}, error data: ${error.message}`); + }); + this.startDownload(); + } + } + + private startDownload(): void { + Logger.info(TAG, `start download sample: ${this.state.sampleDatas[this.state.currentIndex].sampleCard.title}`); + const ctx: common.UIAbilityContext = getContext() as common.UIAbilityContext; + DynamicInstallManager.fetchModule(ctx, this.state.sampleDatas[this.state.currentIndex].sampleCard.moduleName) + .then((data: moduleInstallManager.ModuleInstallSessionState) => { + if (data.code === moduleInstallManager.RequestErrorCode.SUCCESS) { + this.state.taskId = data.taskId; + } else if (data.code === moduleInstallManager.RequestErrorCode.DOWNLOAD_WAIT_WIFI) { + moduleInstallManager.showCellularDataConfirmation(ctx, data.taskId); + this.state.taskId = data.taskId; + } else { + if (data.code === moduleInstallManager.RequestErrorCode.MODULE_UNAVAILABLE) { + promptAction.showToast({ message: $r('app.string.module_nonexistent') }); + } else if (data.code === moduleInstallManager.RequestErrorCode.NETWORK_ERROR) { + promptAction.showToast({ message: $r('app.string.internet_error') }); + } else if (data.code === moduleInstallManager.RequestErrorCode.INVALID_REQUEST) { + promptAction.showToast({ message: $r('app.string.requestinfo_error') }); + } + this.setSampleDownloadingStatus(this.state.currentIndex, false, -1); + } + }).catch((error: BusinessError) => { + Logger.error(TAG, `Failed to fetchModule. error code: ${error.code}, error data: ${error.message}`); + }); + } + + private setSampleDownloadingStatus(sampleIndex: number, downloading: boolean, progress: number): void { + this.state.sampleDatas[sampleIndex].sampleCard.downloading = downloading; + this.state.sampleDatas[sampleIndex].sampleCard.downloadProgress = progress; + this.state.downloadingStatus = downloading; + } + + private setSampleInstalledStatus(sampleIndex: number, installed: boolean): void { + this.state.sampleDatas[sampleIndex].sampleCard.installed = installed; + } + + private setInstallingStatus(installing: boolean): void { + this.state.installingStatus = installing; + } +} + +export class BindSheetEvent implements BaseVMEvent { + public readonly sampleIndex: number; + public readonly dataValue: boolean; + + public constructor(sampleIndex: number, dataValue: boolean) { + this.sampleIndex = sampleIndex; + this.dataValue = dataValue; + } +} + +export class ChangeSampleDataEvent implements BaseVMEvent { + public readonly sampleCardId: number; + + public constructor(sampleCardId: number) { + this.sampleCardId = sampleCardId; + } +} + +export class TerminateTaskEvent implements BaseVMEvent { + public readonly sampleIndex: number; + + public constructor(sampleIndex: number) { + this.sampleIndex = sampleIndex; + } +} + +export class LoadSampleEvent implements BaseVMEvent { +} + +export class ChangeDownloadProgressEvent implements BaseVMEvent { + public readonly sampleIndex: number; + public readonly downloadProgress: number; + + public constructor(sampleIndex: number, downloadProgress: number) { + this.sampleIndex = sampleIndex; + this.downloadProgress = downloadProgress; + } +} + +export class PopEvent implements BaseVMEvent { +} + +export class SetIndexEvent implements BaseVMEvent { + public readonly currentIndex: number; + + public constructor(currentIndex: number) { + this.currentIndex = currentIndex; + } +} + +export class SetInstalledEvent implements BaseVMEvent { + public readonly sampleIndex: number; + + public constructor(sampleIndex: number) { + this.sampleIndex = sampleIndex; + } +} + +export class InitSampleDetailEvent implements BaseVMEvent { + public readonly sampleCardId: number; + public readonly currentIndex: number; + + public constructor(sampleCardId: number, currentIndex: number) { + this.sampleCardId = sampleCardId; + this.currentIndex = currentIndex; + } +} \ No newline at end of file diff --git a/features/devpractices/src/main/ets/viewmodel/SampleDetailState.ets b/features/devpractices/src/main/ets/viewmodel/SampleDetailState.ets new file mode 100644 index 0000000000000000000000000000000000000000..092d51106b87947f08de80b4cd1190e617fb58b3 --- /dev/null +++ b/features/devpractices/src/main/ets/viewmodel/SampleDetailState.ets @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 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 { BaseState, LoadingStatus } from '@ohos/common'; +import { SampleTypeEnum } from '../common/SampleConstant'; + +@Observed +export class SampleDetailState extends BaseState { + public sampleDatas: SampleDetailData[] = []; + public sampleCount: number = 0; + public loadingStatus: LoadingStatus = LoadingStatus.LOADING; + public taskId?: string = ''; + public currentIndex: number = 0; + public installingStatus: boolean = false; + public downloadingStatus: boolean = false; + public isBackPressed: boolean = false; +} + +@Observed +export class SampleDetailData { + public id: number = 0; + public isFavorite: boolean = false; + public mediaType: number = 0; + public mediaUrl: string = ''; + public sampleCard: SampleCardData = new SampleCardData(); +} + +@Observed +export class SampleCardData { + public title: string = ''; + public desc: string = ''; + public sampleType: SampleTypeEnum = SampleTypeEnum.COMMON_SAMPLE; + public sampleId: number = 0; + public originalUrl: string = ''; + public moduleName: string = ''; + public abilityName: string = ''; + public bindSheetShow: boolean = false; + public downloading: boolean = false; + public downloadProgress: number = -1; + public installed: boolean = false; +} \ No newline at end of file diff --git a/features/devpractices/src/main/module.json5 b/features/devpractices/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..8bac35d9ab79a9094fdd3fddd64eeec011725d5b --- /dev/null +++ b/features/devpractices/src/main/module.json5 @@ -0,0 +1,12 @@ +{ + "module": { + "name": "devpractices", + "type": "har", + "routerMap": "$profile:router_map", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ], + } +} diff --git a/features/devpractices/src/main/resources/base/element/color.json b/features/devpractices/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..1c0ac45d19c88b09153e640391ded32a0e0bfd77 --- /dev/null +++ b/features/devpractices/src/main/resources/base/element/color.json @@ -0,0 +1,20 @@ +{ + "color": [ + { + "name": "card_color", + "value": "#19000000" + }, + { + "name": "card_font_primary_color", + "value": "#E5000000" + }, + { + "name": "card_font_secondary_color", + "value": "#99000000" + }, + { + "name": "icon_secondary_color", + "value": "#99000000" + } + ] +} \ No newline at end of file diff --git a/features/devpractices/src/main/resources/base/element/float.json b/features/devpractices/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..5a213661ce0c7cffa5fcc07a46c608e61b48721d --- /dev/null +++ b/features/devpractices/src/main/resources/base/element/float.json @@ -0,0 +1,68 @@ +{ + "float": [ + { + "name": "loading_view_height", + "value": "300vp" + }, + { + "name": "card_button_height", + "value": "40.0vp" + }, + { + "name": "sample_card_height", + "value": "170vp" + }, + { + "name": "samplename_height", + "value": "24vp" + }, + { + "name": "sampledesc_height", + "value": "45vp" + }, + { + "name": "card_content_height", + "value": "136.0vp" + }, + { + "name": "picture_card_height", + "value": "400vp" + }, + { + "name": "picture_card_content_height", + "value": "82vp" + }, + { + "name": "card_img_width", + "value": "107vp" + }, + { + "name": "card_list_height", + "value": "146vp" + }, + { + "name": "card_scroll_height", + "value": "340vp" + }, + { + "name": "progress_height", + "value": "40vp" + }, + { + "name": "cardtitle_lineheight", + "value": "21" + }, + { + "name": "cardsubtitle_lineheight", + "value": "16" + }, + { + "name": "margin_36", + "value": "36" + }, + { + "name": "tab_icon_height", + "value": "16vp" + } + ] +} \ No newline at end of file diff --git a/features/devpractices/src/main/resources/base/element/string.json b/features/devpractices/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..bbe990de0d43f6cae38fdb2025ccb7b8a4840b81 --- /dev/null +++ b/features/devpractices/src/main/resources/base/element/string.json @@ -0,0 +1,60 @@ +{ + "string": [ + { + "name": "sample_name", + "value": "Sample" + }, + { + "name": "no_content", + "value": "No data available." + }, + { + "name": "sample_title", + "value": "Development Sample" + }, + { + "name": "sample_code", + "value": "Sample Code" + }, + { + "name": "read_code", + "value": "Read Code" + }, + { + "name": "experience_sample", + "value": "Experience" + }, + { + "name": "download_sample", + "value": "Download" + }, + { + "name": "module_nonexistent", + "value": "module does not exist" + }, + { + "name": "internet_error", + "value": "network is not connected" + }, + { + "name": "requestinfo_error", + "value": "request information error" + }, + { + "name": "installing_status", + "value": "installing" + }, + { + "name": "swiper_gesture", + "value": "please do not switch during download" + }, + { + "name": "watch_prompt", + "value": "This case only supports the watch side, welcome to experience" + }, + { + "name": "client_prompt", + "value": "Sample in HarmonyOS supports 1+8 multi-device operation. Welcome to experience it" + } + ] +} \ No newline at end of file diff --git a/features/devpractices/src/main/resources/base/media/ic_browse_no.svg b/features/devpractices/src/main/resources/base/media/ic_browse_no.svg new file mode 100644 index 0000000000000000000000000000000000000000..d1aca7d4d2fb3043e3a8d937f298094dac7315e4 --- /dev/null +++ b/features/devpractices/src/main/resources/base/media/ic_browse_no.svg @@ -0,0 +1,17 @@ + + + png_browse_no + + + + + + + + + + + + + + \ No newline at end of file diff --git a/features/devpractices/src/main/resources/base/media/img_placeholder.png b/features/devpractices/src/main/resources/base/media/img_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..f9b5440a2b7abc295a852ff8ca8e3b0ad284c77a Binary files /dev/null and b/features/devpractices/src/main/resources/base/media/img_placeholder.png differ diff --git a/features/devpractices/src/main/resources/base/profile/router_map.json b/features/devpractices/src/main/resources/base/profile/router_map.json new file mode 100644 index 0000000000000000000000000000000000000000..45c93612fc7f603e17db73c4e9ad553419808165 --- /dev/null +++ b/features/devpractices/src/main/resources/base/profile/router_map.json @@ -0,0 +1,9 @@ +{ + "routerMap": [ + { + "name": "SampleDetailView", + "pageSourceFile": "src/main/ets/view/PracticeDetailView.ets", + "buildFunction": "SampleDetailViewBuilder" + } + ] +} \ No newline at end of file diff --git a/features/devpractices/src/main/resources/en_US/element/string.json b/features/devpractices/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..23c8c575017c62be0c9ebc40da04ea0180173952 --- /dev/null +++ b/features/devpractices/src/main/resources/en_US/element/string.json @@ -0,0 +1,60 @@ +{ + "string": [ + { + "name": "sample_name", + "value": "Sample" + }, + { + "name": "no_content", + "value": "No data available." + }, + { + "name": "sample_title", + "value": "Development Sample" + }, + { + "name": "sample_code", + "value": "Sample Code" + }, + { + "name": "experience_sample", + "value": "Experience" + }, + { + "name": "read_code", + "value": "Read Code" + }, + { + "name": "download_sample", + "value": "Download" + }, + { + "name": "module_nonexistent", + "value": "module does not exist" + }, + { + "name": "internet_error", + "value": "network is not connected" + }, + { + "name": "requestinfo_error", + "value": "request information error" + }, + { + "name": "installing_status", + "value": "installing" + }, + { + "name": "swiper_gesture", + "value": "please do not switch during download" + }, + { + "name": "watch_prompt", + "value": "This case only supports the watch side, welcome to experience" + }, + { + "name": "client_prompt", + "value": "Sample in HarmonyOS supports 1+8 multi-device operation. Welcome to experience it" + } + ] +} \ No newline at end of file diff --git a/features/devpractices/src/main/resources/zh_CN/element/string.json b/features/devpractices/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..8bae6e1eb914878f45129bbc79a6dd34db108e96 --- /dev/null +++ b/features/devpractices/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,60 @@ +{ + "string": [ + { + "name": "sample_name", + "value": "样例" + }, + { + "name": "no_content", + "value": "暂无数据" + }, + { + "name": "sample_title", + "value": "开发样例" + }, + { + "name": "sample_code", + "value": "示例代码" + }, + { + "name": "experience_sample", + "value": "立即体验" + }, + { + "name": "read_code", + "value": "阅读源码" + }, + { + "name": "download_sample", + "value": "下载体验" + }, + { + "name": "module_nonexistent", + "value": "该模块不存在" + }, + { + "name": "internet_error", + "value": "网络未连接,请检查网络设置" + }, + { + "name": "requestinfo_error", + "value": "请求信息错误" + }, + { + "name": "installing_status", + "value": "正在安装" + }, + { + "name": "swiper_gesture", + "value": "下载中,请勿切换" + }, + { + "name": "watch_prompt", + "value": "本案例仅支持手表端,欢迎体验" + }, + { + "name": "client_prompt", + "value": "HarmonyOS代码工坊支持1+8多设备运行,欢迎体验" + } + ] +} \ No newline at end of file diff --git a/features/devpractices/src/ohosTest/ets/test/Ability.test.ets b/features/devpractices/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..85c78f67579d6e31b5f5aeea463e216b9b141048 --- /dev/null +++ b/features/devpractices/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +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/features/devpractices/src/ohosTest/ets/test/List.test.ets b/features/devpractices/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..794c7dc4ed66bd98fa3865e07922906e2fcef545 --- /dev/null +++ b/features/devpractices/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/features/devpractices/src/ohosTest/module.json5 b/features/devpractices/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c997c465d2ae00054f04889fd5f2f4b92f7f630a --- /dev/null +++ b/features/devpractices/src/ohosTest/module.json5 @@ -0,0 +1,13 @@ +{ + "module": { + "name": "devpractices_test", + "type": "feature", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} \ No newline at end of file diff --git a/features/devpractices/src/test/List.test.ets b/features/devpractices/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..bb5b5c3731e283dd507c847560ee59bde477bbc7 --- /dev/null +++ b/features/devpractices/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/features/devpractices/src/test/LocalUnit.test.ets b/features/devpractices/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..165fc1615ee8618b4cb6a622f144a9a707eee99f --- /dev/null +++ b/features/devpractices/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +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/features/exploration/.gitignore b/features/exploration/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/features/exploration/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/features/exploration/Index.ets b/features/exploration/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..1eae25bcea3d0ff984bbccc9c570eef41d809cf6 --- /dev/null +++ b/features/exploration/Index.ets @@ -0,0 +1,3 @@ +export { ExplorationView } from './src/main/ets/view/ExplorationView'; + +export { DiscoverModel } from './src/main/ets/model/DiscoverModel'; \ No newline at end of file diff --git a/features/exploration/build-profile.json5 b/features/exploration/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..697dff23e224373edb713dc2b8a08ed7341d5b4c --- /dev/null +++ b/features/exploration/build-profile.json5 @@ -0,0 +1,31 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest" + } + ] +} diff --git a/features/exploration/consumer-rules.txt b/features/exploration/consumer-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..fd706a103c73b5959582e48d9317986169a37ead --- /dev/null +++ b/features/exploration/consumer-rules.txt @@ -0,0 +1,5 @@ +-keep +./src/main/ets/model/DiscoverData.ets +-keep-property-name +webSheet +jumpPage \ No newline at end of file diff --git a/features/exploration/hvigorfile.ts b/features/exploration/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..42187071482d292588ad40babeda74f7b8d97a23 --- /dev/null +++ b/features/exploration/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/features/exploration/obfuscation-rules.txt b/features/exploration/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..fdbb5b9852d7dd5f39bddaeb21ab5ee1f3346749 --- /dev/null +++ b/features/exploration/obfuscation-rules.txt @@ -0,0 +1,22 @@ +# 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/features/exploration/oh-package.json5 b/features/exploration/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..dd4ff39e93a572821b2ba3c3101f91bc87839f3f --- /dev/null +++ b/features/exploration/oh-package.json5 @@ -0,0 +1,12 @@ +{ + "name": "exploration", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@ohos/common": "file:../../common", + "@ohos/commonbusiness": "file:../commonbusiness" + } +} diff --git a/features/exploration/src/main/ets/common/DiscoveryConstant.ets b/features/exploration/src/main/ets/common/DiscoveryConstant.ets new file mode 100644 index 0000000000000000000000000000000000000000..dcc9bf1005afeb301fcfb7aeab0c1cb4c8aa1051 --- /dev/null +++ b/features/exploration/src/main/ets/common/DiscoveryConstant.ets @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024 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 DiscoveryConstant { + public static DEVELOPER_ITEM_HEIGHT: number = 256; +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/component/ArticleWebComponent.ets b/features/exploration/src/main/ets/component/ArticleWebComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..8514ea2a0de5388be4b02e61d9ff74e116f321b9 --- /dev/null +++ b/features/exploration/src/main/ets/component/ArticleWebComponent.ets @@ -0,0 +1,205 @@ +/* + * 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 { ConfigurationConstant } from '@kit.AbilityKit'; +import { webview } from '@kit.ArkWeb'; +import type { BusinessError } from '@kit.BasicServicesKit'; +import { + BreakpointTypeEnum, + CommonConstants, + GlobalInfoModel, + javascriptProxyPermission, + LoadingStatus, + Logger, + NativeActionData, + TopNavigationView, + WebSheetBuilder, + WebUtil, + WindowUtil +} from '@ohos/common'; +import { BaseDetailComponent } from '@ohos/commonbusiness'; +import { + ArticleDetailViewModel, + ExplorationDetailEventType, + NativePageParam, +} from '../viewmodel/ArticleDetailViewModel'; +import { ExplorationDetailState } from '../viewmodel/ExplorationDetailState'; + +const TAG = '[ArticleWebComponent]'; + +@Component +export struct ArticleWebComponent { + webController: webview.WebviewController = new webview.WebviewController(); + @Prop @Require viewModel: ArticleDetailViewModel; + @Prop @Require detailsUrl: string; + @Prop tabViewType: number = -1; + @Link loadingStatus: LoadingStatus; + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @StorageProp('systemColorMode') @Watch('handleColorModeChange') systemColorMode: ConfigurationConstant.ColorMode = + AppStorage.get('systemColorMode')!; + @State bindSheetShow: boolean = false; + @State bindSheetSrc: string = ''; + @State detailState: ExplorationDetailState = this.viewModel.getState(); + @State isBlur: boolean = false; + @State title: string = ''; + bindSheetSrcSet: Set = new Set(); + webUrlType: number = 0; + + aboutToAppear(): void { + this.handleColorModeChange(); + this.registerJsFunction(); + } + + aboutToDisappear(): void { + try { + this.bindSheetSrcSet.forEach((item: string) => { + WebUtil.removeNode(item); + }); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, `Web load Data error. ${err.code}, ${err.message}`); + } + } + + handleWebScroll(yOffset: number) { + this.title = yOffset > CommonConstants.NAVIGATION_HEIGHT ? this.detailState.content.title : ''; + this.isBlur = yOffset > CommonConstants.SPACE_12; + } + + handleColorModeChange() { + const isSystemDark: boolean = (this.systemColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK); + WindowUtil.updateStatusBarColor(getContext(this), isSystemDark); + } + + registerJsFunction() { + WebUtil.setWebSheetAction(this.detailsUrl, (src: string, type: number) => { + this.webUrlType = type; + if (!this.bindSheetSrcSet.has(src)) { + WebUtil.createWebNode(src, this.getUIContext(), NestedScrollMode.SELF_ONLY); + this.bindSheetSrcSet.add(src); + } + this.bindSheetShow = true; + this.bindSheetSrc = src; + }); + WebUtil.setJumpPageAction(this.detailsUrl, + (type: string, id: number, currentIndex?: number, componentName?: string) => { + this.viewModel.sendEvent({ + type: ExplorationDetailEventType.JUMP_NATIVE_PAGE, + param: { + tabBarView: this.tabViewType, + type, + id, + currentIndex, + componentName, + }, + }); + }) + } + + @Builder + WebContentBuilder() { + Web({ src: this.detailsUrl, controller: this.webController }) + .zoomAccess(false) + .fileAccess(true) + .mixedMode(MixedMode.None) + .verticalScrollBarAccess(false) + .horizontalScrollBarAccess(false) + .imageAccess(true) + .cacheMode(CacheMode.Default) + .domStorageAccess(true) + .javaScriptAccess(true) + .javaScriptProxy({ + object: new NativeActionData(this.detailsUrl), + name: 'nativeActionData', + methodList: ['webSheet', 'jumpPage'], + controller: this.webController, + permission: javascriptProxyPermission, + }) + .geolocationAccess(false) + .backgroundColor($r('sys.color.background_secondary')) + .overScrollMode(OverScrollMode.ALWAYS) + .darkMode(WebDarkMode.Auto) + .forceDarkAccess(true) + .allowDrop(null) + .onPageEnd(() => { + this.loadingStatus = LoadingStatus.SUCCESS; + WebUtil.setTrustList(this.detailsUrl); + }) + .onLoadIntercept((event: OnLoadInterceptEvent) => { + const tempUrl = event.data.getRequestUrl(); + return WebUtil.checkUrl(tempUrl); + }) + .onConsole((event: OnConsoleEvent) => { + Logger.error(TAG, event.message.getMessage()) + return false; + }) + .onSslErrorEventReceive((event) => { + Logger.error(TAG, `SSL checked failed, error: ${event.error.toString()}`); + event.handler.handleCancel(); + }) + .onControllerAttached(() => { + WebUtil.setWebController(this.detailsUrl, this.webController); + // Setting the local file path that allows cross-domain access. + this.webController.setPathAllowingUniversalAccess([getContext().resourceDir]); + }) + .onScroll((event) => { + this.handleWebScroll(event.yOffset); + }) + .width('100%') + .height('100%') + } + + @Builder + TopTitleViewBuilder() { + TopNavigationView({ + topNavigationData: { + title: this.title, + isBlur: this.isBlur, + isFullScreen: true, + onBackClick: () => { + this.detailState.topNavigationData.onBackClick?.(); + } + }, + }) + } + + build() { + BaseDetailComponent({ + detailContentView: () => { + this.WebContentBuilder() + }, + topTitleView: () => { + this.TopTitleViewBuilder() + }, + loadingStatus: this.loadingStatus, + }) + .backgroundColor(Color.Transparent) + .width('100%') + .height('100%') + .bindSheet(this.bindSheetShow, + WebSheetBuilder(this.bindSheetSrc, this.webUrlType), { + title: { title: '' }, + preferType: SheetType.CENTER, + height: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? + ((this.globalInfoModel.deviceHeight - this.globalInfoModel.decorHeight) * + CommonConstants.SHEET_HEIGHT_RATIO_XL) : SheetSize.LARGE, + width: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? CommonConstants.SHEET_WIDTH_XL : + undefined, + onDisappear: () => { + this.bindSheetShow = false; + }, + }) + } +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/component/DeveloperCard.ets b/features/exploration/src/main/ets/component/DeveloperCard.ets new file mode 100644 index 0000000000000000000000000000000000000000..960d0ca248326f3c6c9f08ccd05e4ffe690ea537 --- /dev/null +++ b/features/exploration/src/main/ets/component/DeveloperCard.ets @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024 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 { deviceInfo } from '@kit.BasicServicesKit'; +import type { GlobalInfoModel } from '@ohos/common'; +import { BreakpointType, BreakpointTypeEnum, CommonConstants, ProductSeriesEnum } from '@ohos/common'; +import type { DiscoverContent } from '../model/DiscoverData'; +import { DeveloperItem } from './DeveloperItem'; + +const DEVELOPER_ITEM_RATIO = 960 / 1312; +const DEVELOPER_ITEM_RATIO_VERDE = 240 / 408; + +@Component +export struct DeveloperCard { + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @Prop @Require discoverContents: DiscoverContent[]; + handleItemClick?: Function; + + build() { + Swiper() { + Repeat(this.discoverContents) + .each((repeatItem: RepeatItem) => { + Column() { + DeveloperItem({ discoverContent: repeatItem.item }) + .onClick(() => { + this.handleItemClick?.(repeatItem.item); + }) + } + .padding({ + left: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? + $r('sys.float.padding_level8') : 0, + right: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? + $r('sys.float.padding_level8') : 0, + }) + }) + } + .size(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? + { + height: ((this.globalInfoModel.deviceWidth - CommonConstants.SPACE_32) * + (deviceInfo.productSeries === ProductSeriesEnum.VDE ? DEVELOPER_ITEM_RATIO_VERDE : DEVELOPER_ITEM_RATIO) + + CommonConstants.SPACE_16) * this.discoverContents.length + } : + { height: $r('app.float.list_card_height') }) + .effectMode(EdgeEffect.None) + .loop(false) + .indicator(false) + .disableSwipe(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM) + .vertical(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM) + .prevMargin(new BreakpointType({ + sm: 0, + md: $r('sys.float.padding_level6'), + lg: CommonConstants.SPACE_16 + CommonConstants.TAB_BAR_WIDTH, + xl: CommonConstants.SPACE_16, + }).getValue(this.globalInfoModel.currentBreakpoint)) + .nextMargin(new BreakpointType({ + sm: 0, + md: $r('sys.float.padding_level6'), + lg: $r('sys.float.padding_level8'), + }).getValue(this.globalInfoModel.currentBreakpoint)) + .displayCount( + new BreakpointType({ + sm: this.discoverContents.length, + md: CommonConstants.LANE_MD, + lg: CommonConstants.LANE_LG, + }).getValue(this.globalInfoModel.currentBreakpoint) + ) + .itemSpace(new BreakpointType({ + sm: CommonConstants.SPACE_16, + md: CommonConstants.SPACE_12, + lg: CommonConstants.SPACE_16, + }).getValue(this.globalInfoModel.currentBreakpoint)) + } +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/component/DeveloperItem.ets b/features/exploration/src/main/ets/component/DeveloperItem.ets new file mode 100644 index 0000000000000000000000000000000000000000..dc0bb8c339c77887c9b05cd46855e7e38d57d851 --- /dev/null +++ b/features/exploration/src/main/ets/component/DeveloperItem.ets @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 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 type { DiscoverContent } from '../model/DiscoverData'; + +@Component +export struct DeveloperItem { + @Prop discoverContent: DiscoverContent; + + + build() { + Stack({ alignContent: Alignment.Bottom }) { + Image($rawfile(this.discoverContent.mediaUrl)) + .draggable(false) + .alt($r('app.media.img_placeholder')) + .width('100%') + .height('100%') + Column() { + Row() { + Text(this.discoverContent.title) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Body_L')) + .fontWeight(FontWeight.Bold) + Text(this.discoverContent.subTitle) + .margin({ bottom: $r('sys.float.padding_level1') }) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Caption_M')) + .fontWeight(FontWeight.Regular) + } + .justifyContent(FlexAlign.SpaceBetween) + .alignItems(VerticalAlign.Bottom) + .width('100%') + + Text(this.discoverContent.desc) + .margin({ top: $r('sys.float.padding_level2') }) + .fontColor($r('sys.color.font_secondary')) + .fontSize($r('sys.float.Body_S')) + .width('100%') + .maxLines(2) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontWeight(FontWeight.Regular) + } + .alignItems(HorizontalAlign.Start) + .backgroundColor($r('app.color.blur_bg')) + .renderGroup(true) + .padding({ + top: $r('sys.float.padding_level6'), + right: $r('sys.float.padding_level8'), + bottom: $r('sys.float.padding_level8'), + left: $r('sys.float.padding_level8'), + }) + } + .clickEffect({ level: ClickEffectLevel.HEAVY }) + .borderRadius($r('sys.float.corner_radius_level8')) + .clip(true) + .backgroundColor($r('sys.color.comp_background_list_card')) + } +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/component/ExperienceCard.ets b/features/exploration/src/main/ets/component/ExperienceCard.ets new file mode 100644 index 0000000000000000000000000000000000000000..37667f2aa67cde5aceff5b3d1f0fefd7cf77e961 --- /dev/null +++ b/features/exploration/src/main/ets/component/ExperienceCard.ets @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2024 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 { curves } from '@kit.ArkUI'; +import { deviceInfo } from '@kit.BasicServicesKit'; +import type { GlobalInfoModel } from '@ohos/common'; +import { BreakpointType, BreakpointTypeEnum, CommonConstants, ProductSeriesEnum } from '@ohos/common'; +import type { DiscoverContent } from '../model/DiscoverData'; +import { ExperienceItem } from './ExperienceItem'; + +const ROW_PADDING = 64; +const SHOW_COUNT = 3; +const ENLARGE_COEFFICIENTS = 2; + +@Component +export struct ExperienceCard { + @StorageProp('GlobalInfoModel') @Watch('calculateItemWidth') globalInfoModel: GlobalInfoModel = + AppStorage.get('GlobalInfoModel')!; + @Prop @Require discoverContents: DiscoverContent[]; + handleItemClick?: Function; + componentWidth: number = this.globalInfoModel.deviceWidth; + scroller: Scroller = new Scroller() + @State enlargeIndex: number = -1; + @State itemWidth: number = 0; + + aboutToAppear(): void { + this.calculateItemWidth(); + } + + calculateItemWidth() { + if (this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL) { + this.componentWidth = this.globalInfoModel.deviceWidth - CommonConstants.SIDE_BAR_WIDTH - ROW_PADDING; + this.itemWidth = (this.componentWidth - CommonConstants.SPACE_16 * (SHOW_COUNT - 1)) / SHOW_COUNT; + } + } + + onHoverAction(isHover: boolean, index: number) { + animateTo({ curve: curves.interpolatingSpring(0, 1, 288, 30) }, () => { + if (isHover && this.enlargeIndex !== index) { + const currentOffsetX: number = this.scroller.currentOffset().xOffset; + // Items at the edge of the screen are not processed. + if (((index * (this.itemWidth + CommonConstants.SPACE_16) - currentOffsetX) < -CommonConstants.SPACE_16) || + ((index + 1) * this.itemWidth - currentOffsetX) > this.componentWidth) { + return; + } + // Calculate the position of the current item on the screen. + const currentItemEdgePosition: number = + (index + ENLARGE_COEFFICIENTS) * this.itemWidth - currentOffsetX; + this.enlargeIndex = index; + if (currentItemEdgePosition > this.componentWidth) { + this.scroller.scrollTo({ + xOffset: (currentOffsetX + this.itemWidth + + CommonConstants.SPACE_16), + yOffset: 0, + }); + } + } else if (!isHover) { + this.enlargeIndex = -1; + } + }); + } + + build() { + if (this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL) { + Scroll(this.scroller) { + Row({ space: CommonConstants.SPACE_16 }) { + ForEach(this.discoverContents, (discoverContent: DiscoverContent, index: number) => { + ExperienceItem({ discoverContent: discoverContent }) + .height('100%') + .width(index === this.enlargeIndex ? + (this.itemWidth * ENLARGE_COEFFICIENTS + CommonConstants.SPACE_16) : this.itemWidth) + .onHover((isHover: boolean) => this.onHoverAction(isHover, index)) + .onClick(() => { + this.handleItemClick?.(discoverContent); + }) + }, (discoverContent: DiscoverContent) => discoverContent.id.toString()) + } + .padding({ + left: $r('sys.float.padding_level16'), + right: $r('sys.float.padding_level16'), + }) + .height('100%') + } + .scrollSnap({ snapAlign: ScrollSnapAlign.START, snapPagination: this.itemWidth + CommonConstants.SPACE_16 }) + .scrollBar(BarState.Off) + .edgeEffect(EdgeEffect.None) + .scrollable(ScrollDirection.Horizontal) + .height($r('app.float.img_card_height')) + .width('100%') + } else { + Swiper() { + ForEach(this.discoverContents, (discoverContent: DiscoverContent) => { + ExperienceItem({ discoverContent: discoverContent }) + .onClick(() => { + this.handleItemClick?.(discoverContent); + }) + }, (discoverContent: DiscoverContent) => discoverContent.id.toString()) + } + .prevMargin(new BreakpointType({ + sm: 0, + md: $r('sys.float.padding_level6'), + lg: CommonConstants.SPACE_16 + CommonConstants.TAB_BAR_WIDTH, + }).getValue(this.globalInfoModel.currentBreakpoint)) + .nextMargin(new BreakpointType({ + sm: 0, + md: $r('sys.float.padding_level6'), + lg: $r('sys.float.padding_level8'), + }).getValue(this.globalInfoModel.currentBreakpoint)) + .loop(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM) + .itemSpace(new BreakpointType({ + sm: CommonConstants.SPACE_8, + md: CommonConstants.SPACE_12, + lg: CommonConstants.SPACE_16, + }).getValue(this.globalInfoModel.currentBreakpoint)) + .displayCount(new BreakpointType({ + sm: CommonConstants.LANE_SM, + md: CommonConstants.LANE_MD, + lg: CommonConstants.LANE_LG, + }).getValue(this.globalInfoModel.currentBreakpoint)) + .effectMode(EdgeEffect.None) + .indicator((this.discoverContents.length > 1 && + this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM) ? + new DotIndicator() + .color($r('sys.color.icon_on_tertiary')) + .selectedColor($r('sys.color.icon_on_primary')) : + false) + .width('100%') + .height(deviceInfo.productSeries === ProductSeriesEnum.VDE ? $r('app.float.img_card_height_verde') : + $r('app.float.img_card_height')) + } + } +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/component/ExperienceItem.ets b/features/exploration/src/main/ets/component/ExperienceItem.ets new file mode 100644 index 0000000000000000000000000000000000000000..277166921b8262709d56e6f05fec6d80d72d3278 --- /dev/null +++ b/features/exploration/src/main/ets/component/ExperienceItem.ets @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024 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 { deviceInfo } from '@kit.BasicServicesKit'; +import { ProductSeriesEnum } from '@ohos/common'; +import type { DiscoverContent } from '../model/DiscoverData'; + +@Component +export struct ExperienceItem { + @Prop discoverContent: DiscoverContent; + @State bgTopColor: string = ''; + @State bgBottomColor: string = ''; + + build() { + Stack({ alignContent: Alignment.Bottom }) { + Image($rawfile(this.discoverContent.mediaUrl)) + .alt($r('app.media.img_placeholder')) + .objectFit(ImageFit.Cover) + .draggable(false) + .width('100%') + .height('100%') + Column() { + Text(this.discoverContent.subTitle) + .fontColor($r('sys.color.font_on_primary')) + .fontSize($r('sys.float.Body_S')) + .fontWeight(FontWeight.Regular) + Text(this.discoverContent.title) + .margin({ top: $r('sys.float.padding_level3'), bottom: $r('sys.float.padding_level2') }) + .fontColor($r('sys.color.font_on_primary')) + .fontSize($r('sys.float.Title_S')) + .fontWeight(FontWeight.Bold) + Text(this.discoverContent.desc) + .fontColor($r('sys.color.font_on_secondary')) + .maxLines(2) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontSize($r('sys.float.Body_M')) + .fontWeight(FontWeight.Regular) + } + .padding({ + left: $r('sys.float.padding_level8'), + right: $r('sys.float.padding_level8'), + bottom: $r('sys.float.padding_level8'), + top: $r('sys.float.padding_level6'), + }) + .height($r('app.float.img_card_content_height')) + .width('100%') + .justifyContent(FlexAlign.End) + .alignItems(HorizontalAlign.Start) + } + .clickEffect({ level: ClickEffectLevel.HEAVY }) + .height(deviceInfo.productSeries === ProductSeriesEnum.VDE ? $r('app.float.img_card_height_verde') : + $r('app.float.img_card_height')) + .backgroundColor($r('sys.color.comp_background_list_card')) + .borderRadius($r('sys.float.corner_radius_level8')) + .clip(true) + } +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/component/FeedCard.ets b/features/exploration/src/main/ets/component/FeedCard.ets new file mode 100644 index 0000000000000000000000000000000000000000..a4530732d21a04e9c4df80fca8147b830a173fcc --- /dev/null +++ b/features/exploration/src/main/ets/component/FeedCard.ets @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024 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 type { GlobalInfoModel } from '@ohos/common'; +import { BreakpointType, BreakpointTypeEnum, CommonConstants } from '@ohos/common'; +import type { DiscoverContent } from '../model/DiscoverData'; +import { FeedItem } from './FeedItem'; + +@Component +export struct FeedCard { + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @Prop @Require discoverContents: DiscoverContent[]; + handleItemClick?: Function; + + build() { + Swiper() { + Repeat(this.discoverContents) + .each((repeatItem: RepeatItem) => { + FeedItem({ discoverContent: repeatItem.item }) + .onClick(() => { + this.handleItemClick?.(repeatItem.item); + }) + }) + .key((item: DiscoverContent) => item.id.toString()) + } + .cachedCount(3) + .effectMode(EdgeEffect.None) + .loop(false) + .indicator(false) + .prevMargin(new BreakpointType({ + sm: CommonConstants.SPACE_8, + md: CommonConstants.SPACE_12, + lg: CommonConstants.SPACE_16 + CommonConstants.TAB_BAR_WIDTH, + xl: CommonConstants.SPACE_16, + }).getValue(this.globalInfoModel.currentBreakpoint)) + .nextMargin(new BreakpointType({ + sm: CommonConstants.SPACE_8, + md: CommonConstants.SPACE_12, + lg: CommonConstants.SPACE_16, + xl: CommonConstants.SPACE_16, + }).getValue(this.globalInfoModel.currentBreakpoint)) + .displayCount(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? CommonConstants.LANE_MD : + CommonConstants.LANE_LG) + .itemSpace(new BreakpointType({ + sm: CommonConstants.SPACE_8, + md: CommonConstants.SPACE_12, + lg: CommonConstants.SPACE_16, + xl: CommonConstants.SPACE_16, + }).getValue(this.globalInfoModel.currentBreakpoint)) + } +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/component/FeedItem.ets b/features/exploration/src/main/ets/component/FeedItem.ets new file mode 100644 index 0000000000000000000000000000000000000000..ca35c51304bb5fb135b968eac48ba0029a1fb595 --- /dev/null +++ b/features/exploration/src/main/ets/component/FeedItem.ets @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024 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 type { GlobalInfoModel } from '@ohos/common'; +import { BreakpointType } from '@ohos/common'; +import type { DiscoverContent } from '../model/DiscoverData'; + +@Component +export struct FeedItem { + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @Prop discoverContent: DiscoverContent; + + build() { + Column() { + Image($rawfile(this.discoverContent.mediaUrl)) + .draggable(false) + .borderRadius($r('sys.float.corner_radius_level4')) + .alt($r('app.media.img_placeholder')) + .width('100%') + .layoutWeight(1) + Column() { + Text(this.discoverContent.title) + .fontSize($r('sys.float.Body_M')) + .fontColor($r('sys.color.font_primary')) + .fontWeight(FontWeight.Medium) + .lineHeight($r('app.float.feed_item_card_title_height')) + .maxLines(2) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + Text(this.discoverContent.desc) + .fontSize($r('sys.float.Body_S')) + .fontColor($r('sys.color.font_secondary')) + .fontWeight(FontWeight.Regular) + .lineHeight($r('app.float.feed_item_card_subtitle_height')) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .margin({ top: $r('sys.float.padding_level1') }) + } + .width('100%') + .padding({ + left: $r('sys.float.padding_level2'), + right: $r('sys.float.padding_level2'), + }) + .renderGroup(true) + .margin({ top: $r('sys.float.padding_level4') }) + .alignItems(HorizontalAlign.Start) + } + .height(new BreakpointType({ + sm: $r('app.float.feed_item_card_height_sm'), + md: $r('app.float.feed_item_card_height_md'), + lg: $r('app.float.feed_item_card_height_lg'), + xl: $r('app.float.feed_item_card_height_xl'), + }).getValue(this.globalInfoModel.currentBreakpoint)) + .width('100%') + .borderRadius($r('sys.float.corner_radius_level8')) + .backgroundColor($r('sys.color.comp_background_list_card')) + .padding({ + left: $r('sys.float.padding_level4'), + right: $r('sys.float.padding_level4'), + top: $r('sys.float.padding_level4'), + bottom: $r('sys.float.padding_level8'), + }) + .clickEffect({ level: ClickEffectLevel.HEAVY }) + } +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/model/DiscoverData.ets b/features/exploration/src/main/ets/model/DiscoverData.ets new file mode 100644 index 0000000000000000000000000000000000000000..6e429f3a5df91fe48c12806c30152c36f92b2671 --- /dev/null +++ b/features/exploration/src/main/ets/model/DiscoverData.ets @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 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 type { BannerData } from '@ohos/commonbusiness'; +import { MediaTypeEnum } from '@ohos/commonbusiness'; + +export class DiscoverContent { + public id: number = 0; + public type: ArticleTypeEnum = ArticleTypeEnum.UNKNOWN; + public mediaType: MediaTypeEnum = MediaTypeEnum.IMAGE; + public mediaUrl: string = ''; + public title: string = ''; + public subTitle: string = ''; + public desc: string = ''; + public publishTime: string = ''; + public author: string = ''; + public detailsUrl: string = ''; + public urlData: string = ''; +} + +@Observed +export class DiscoverCardData { + public id: number = 0; + public name: string = ''; + public type: ArticleTypeEnum = ArticleTypeEnum.UNKNOWN; + public contents: DiscoverContent[] = []; +} + +export class DiscoverData { + public bannerInfos?: BannerData[]; + public discoveryData: DiscoverCardData[] = []; +} + +export enum ArticleTypeEnum { + FEED = 1, + EXPERIENCES = 2, + ARCHITECTURE = 3, + DEVELOPER = 4, + UNKNOWN = 0, +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/model/DiscoverModel.ets b/features/exploration/src/main/ets/model/DiscoverModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..a10e731f4a9a301495190d158529e271347177e2 --- /dev/null +++ b/features/exploration/src/main/ets/model/DiscoverModel.ets @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 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 type { BusinessError } from '@kit.BasicServicesKit'; +import { Logger } from '@ohos/common'; +import { DiscoverService } from '../service/DiscoverService'; +import type { DiscoverData } from './DiscoverData'; + +const TAG = '[DiscoverModel]'; + +export class DiscoverModel { + private service: DiscoverService = new DiscoverService(); + private static instance: DiscoverModel; + + private constructor() { + } + + public static getInstance(): DiscoverModel { + if (!DiscoverModel.instance) { + DiscoverModel.instance = new DiscoverModel(); + } + return DiscoverModel.instance; + } + + getDiscoveryPage(): Promise { + return this.service.getDiscoveryPageByPreference() + .then((data: DiscoverData) => { + return data; + }) + .catch((err: string) => { + Logger.error(TAG, + `Call getDiscoveryPage data from network failed! try to get data form preference. ${err}`); + return this.service.getDiscoverPage() + .then((data: DiscoverData) => { + this.service.setDiscoveryPageToPreference(data); + return data; + }); + }); + } + + preloadDiscoveryData(): Promise { + return this.service.getDiscoverPage() + .then((result: DiscoverData) => { + this.service.setDiscoveryPageToPreference(result); + }).catch((err: BusinessError) => { + Logger.error(TAG, + `Call preloadDiscoveryData data from network failed. ${err.code}, ${err.message}`); + }); + } +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/service/DiscoverService.ets b/features/exploration/src/main/ets/service/DiscoverService.ets new file mode 100644 index 0000000000000000000000000000000000000000..3c7851f72900462f8d0c360633e5a8aecdbd1a9a --- /dev/null +++ b/features/exploration/src/main/ets/service/DiscoverService.ets @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024 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 type { BusinessError } from '@kit.BasicServicesKit'; +import { MockRequest, PreferenceManager } from '@ohos/common'; +import type { DiscoverData } from '../model/DiscoverData'; + +export class DiscoverService { + constructor() { + } + + public getSampleListByMock(): Promise { + return new Promise((resolve: (value: DiscoverData) => void, + reject: (reason?: Object) => void) => { + MockRequest.call(DiscoverTrigger.DISCOVER_PAGE) + .then((result: Object) => { + resolve(result as DiscoverData); + }) + .catch((error: BusinessError) => { + reject(error); + }); + }); + } + + getDiscoverPage(): Promise { + return this.getSampleListByMock(); + } + + getDiscoveryPageByPreference(): Promise { + return new Promise((resolve: (value: DiscoverData) => void, + reject: (reason?: string) => void) => { + PreferenceManager.getInstance() + .getValue>(DiscoverTrigger.DISCOVER_PAGE) + .then((resp) => { + if (!resp) { + reject('There is no data in the Preference'); + } + resp = (resp as Record); + const ret = resp[DiscoverTrigger.DISCOVER_PAGE]; + if (!ret) { + reject('There is no data in the Preference'); + } + resolve(ret); + }); + }); + } + + setDiscoveryPageToPreference(data: DiscoverData): Promise { + return new Promise((resolve: () => void) => { + PreferenceManager.getInstance().hasValue(DiscoverTrigger.DISCOVER_PAGE) + .then((result) => { + if (result) { + PreferenceManager.getInstance() + .getValue>(DiscoverTrigger.DISCOVER_PAGE) + .then((resp) => { + resp = (resp as Record); + resp[DiscoverTrigger.DISCOVER_PAGE] = data; + PreferenceManager.getInstance().setValue(DiscoverTrigger.DISCOVER_PAGE, resp); + resolve(); + }); + } else { + const record: Record = {}; + record[DiscoverTrigger.DISCOVER_PAGE] = data; + PreferenceManager.getInstance().setValue(DiscoverTrigger.DISCOVER_PAGE, record); + } + }); + }); + } +} + +enum DiscoverTrigger { + DISCOVER_PAGE = 'discovery-page', + DISCOVER_ARTICLE = 'discovery-article', +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/view/ArticleDetailView.ets b/features/exploration/src/main/ets/view/ArticleDetailView.ets new file mode 100644 index 0000000000000000000000000000000000000000..3de2ef0394182468e80cba840e0568d3a6aa3d1b --- /dev/null +++ b/features/exploration/src/main/ets/view/ArticleDetailView.ets @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2024 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 { webview } from '@kit.ArkWeb'; +import { CommonConstants, LoadingStatus, Logger, WebUtil } from '@ohos/common'; +import { ArticleWebComponent } from '../component/ArticleWebComponent'; +import { DiscoverContent } from '../model/DiscoverData'; +import { + ArticleDetailViewModel, + DetailParam, + ExplorationDetailEventType, + PopParam, +} from '../viewmodel/ArticleDetailViewModel'; +import type { ExplorationDetailState } from '../viewmodel/ExplorationDetailState'; + +@Builder +export function ArticleDetailViewBuilder() { + ArticleDetailView() +} + +const TAG = '[ArticleDetailView]'; + +@Component +struct ArticleDetailView { + viewModel: ArticleDetailViewModel = new ArticleDetailViewModel(); + webController: webview.WebviewController = new webview.WebviewController(); + discoverContent: DiscoverContent = new DiscoverContent(); + @State detailState: ExplorationDetailState = this.viewModel.getState(); + @State loadingStatus: LoadingStatus = LoadingStatus.IDLE; + + checkPreview(method: string): Promise { + return new Promise((resolve, reject) => { + this.webController = WebUtil.getWebController(this.discoverContent.detailsUrl)!; + try { + if (WebUtil.ARTICLE_WHITE_METHODS.indexOf(method) >= 0) { + this.webController.runJavaScriptExt( + method, + (error, result) => { + if (error) { + reject(`Run javascript error. ${JSON.stringify(error)}`); + } + if (result) { + const type = result.getType(); + if (type === webview.JsMessageType.BOOLEAN) { + resolve(result.getBoolean()); + } else { + reject(`CheckPreview error, type:${type}`); + } + } + }); + } else { + Logger.error(TAG, `Input method ${method} not in whitelist`); + } + } catch (error) { + reject(`Run javascript failed error.${JSON.stringify(error)}`); + } + }); + } + + customBackPressed(): boolean { + this.checkPreview('checkPreview()') + .then((res) => { + if (res) { + const closePreviewMethod: string = 'closePreview()'; + if (WebUtil.ARTICLE_WHITE_METHODS.indexOf(closePreviewMethod) >= 0) { + this.webController.runJavaScript(closePreviewMethod); + } else { + Logger.error(TAG, `Input method ${closePreviewMethod} not in whitelist`); + } + } else { + this.popAction(true); + } + }) + .catch((error: string) => { + Logger.error(TAG, `Run javascript error: ${error}`); + this.popAction(true); + }) + return true; + } + + popAction(isAnimation: boolean) { + this.webController.onInactive(); + this.viewModel.sendEvent({ + type: ExplorationDetailEventType.POP, + param: { animation: isAnimation, tabBarView: -1 }, + }); + } + + build() { + NavDestination() { + ArticleWebComponent({ + viewModel: this.viewModel, + detailsUrl: this.detailState.content.detailsUrl, + tabViewType: -1, + loadingStatus: this.loadingStatus, + }) + } + .defaultFocus(true) + .onReady((cxt: NavDestinationContext) => { + const params = cxt.pathInfo.param as Record; + this.discoverContent.id = params.id as number; + this.discoverContent.detailsUrl = params.detailsUrl as string; + this.discoverContent.title = params.title as string; + this.viewModel.sendEvent({ + type: ExplorationDetailEventType.GET_ARTICLE_DETAIL, + param: { + content: this.discoverContent, + onBackClick: () => { + this.customBackPressed?.(); + }, + }, + }); + CommonConstants.PROMISE_WAIT(CommonConstants.ANIMATION_DELAY).then(() => { + this.loadingStatus = LoadingStatus.LOADING; + }); + }) + .hideTitleBar(true) + .onBackPressed((): boolean => { + return this.customBackPressed(); + }) + .backgroundColor($r('sys.color.background_secondary')) + } +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/view/BannerDetailView.ets b/features/exploration/src/main/ets/view/BannerDetailView.ets new file mode 100644 index 0000000000000000000000000000000000000000..6c4490e662e2c73c4ae2a2ca3d21b7d9361e7536 --- /dev/null +++ b/features/exploration/src/main/ets/view/BannerDetailView.ets @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2024 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 { curves } from '@kit.ArkUI'; +import { webview } from '@kit.ArkWeb'; +import { CommonConstants, LoadingStatus, Logger, WebUtil } from '@ohos/common'; +import { ArticleWebComponent } from '../component/ArticleWebComponent'; +import { DiscoverContent } from '../model/DiscoverData'; +import { + ArticleDetailViewModel, + DetailParam, + ExplorationDetailEventType, + PopParam, +} from '../viewmodel/ArticleDetailViewModel'; +import type { ExplorationDetailState } from '../viewmodel/ExplorationDetailState'; + +@Builder +export function BannerDetailViewBuilder() { + BannerDetailView() +} + +const TAG = '[BannerDetailView]'; + +@Component +struct BannerDetailView { + viewModel: ArticleDetailViewModel = new ArticleDetailViewModel(); + webController: webview.WebviewController = new webview.WebviewController(); + tabViewType: number = -1; + discoverContent: DiscoverContent = new DiscoverContent(); + @State animationDelay: boolean = false; + @State detailState: ExplorationDetailState = this.viewModel.getState(); + @State isChangePage: boolean = true; // Check whether to set geometryTransition id. + @State isPopTransition: boolean = false; + @State loadingStatus: LoadingStatus = LoadingStatus.IDLE; + + checkPreview(method: string): Promise { + return new Promise((resolve, reject) => { + this.webController = WebUtil.getWebController(this.discoverContent.detailsUrl)!; + try { + if (WebUtil.ARTICLE_WHITE_METHODS.indexOf(method) >= 0) { + this.webController.runJavaScriptExt( + method, + (error, result) => { + if (error) { + reject(`Run javascript error. ${JSON.stringify(error)}`); + } + if (result) { + const type = result.getType(); + if (type === webview.JsMessageType.BOOLEAN) { + resolve(result.getBoolean()); + } else { + reject(`CheckPreview error, type:${type}`); + } + } + }); + } else { + Logger.error(TAG, `Input method ${method} not in whitelist`); + } + } catch (error) { + reject(`Run javascript failed error.${JSON.stringify(error)}`); + } + }); + } + + customBackPressed(): boolean { + this.checkPreview('checkPreview()') + .then((res) => { + if (res) { + const closePreviewMethod: string = 'closePreview()'; + if (WebUtil.ARTICLE_WHITE_METHODS.indexOf(closePreviewMethod) >= 0) { + this.webController.runJavaScript(closePreviewMethod); + } else { + Logger.error(TAG, `Input method ${closePreviewMethod} not in whitelist`); + } + } else { + this.popAction(false); + } + }) + .catch((error: string) => { + Logger.error(TAG, `Run javascript error: ${error}`); + this.popAction(false); + }) + return true; + } + + popAction(isAnimation: boolean) { + this.isPopTransition = true; + this.isChangePage = true; + animateTo({ + curve: curves.interpolatingSpring(0, 1, 363, 38), + }, () => { + WebUtil.getWebController(this.discoverContent.detailsUrl)?.onInactive(); + this.viewModel.sendEvent({ + type: ExplorationDetailEventType.POP, + param: { animation: isAnimation, tabBarView: this.tabViewType }, + }); + }); + } + + build() { + NavDestination() { + ArticleWebComponent({ + viewModel: this.viewModel, + detailsUrl: this.detailState.content.detailsUrl, + tabViewType: this.tabViewType, + loadingStatus: this.loadingStatus, + }) + .geometryTransition(this.isChangePage ? CommonConstants.BANNER_GEOMETRY + this.tabViewType.toString() : '') + .transition(this.isChangePage ? (this.isPopTransition ? TransitionEffect.OPACITY : + TransitionEffect.OPACITY.animation({ duration: CommonConstants.TRANSITION_DURATION })) : undefined, + (transitionIn: boolean) => { + if (transitionIn) { + this.isChangePage = false; + this.loadingStatus = LoadingStatus.LOADING; + } + }) + } + .defaultFocus(true) + .onReady((cxt: NavDestinationContext) => { + const params = cxt.pathInfo.param as Record; + this.discoverContent.id = params.id as number; + this.discoverContent.detailsUrl = params.detailsUrl as string; + this.discoverContent.title = params.title as string; + this.tabViewType = params.tabViewType as number; + this.viewModel.sendEvent({ + type: ExplorationDetailEventType.GET_ARTICLE_DETAIL, + param: { + content: this.discoverContent, + onBackClick: () => { + this.customBackPressed?.(); + }, + }, + }); + }) + .hideTitleBar(true) + .onBackPressed((): boolean => { + return this.customBackPressed(); + }) + .backgroundColor(Color.Transparent) + } +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/view/ExplorationView.ets b/features/exploration/src/main/ets/view/ExplorationView.ets new file mode 100644 index 0000000000000000000000000000000000000000..2327cd797af698ab5ee84c59197b6ec31d4ea952 --- /dev/null +++ b/features/exploration/src/main/ets/view/ExplorationView.ets @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2024 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 { ConfigurationConstant } from '@kit.AbilityKit'; +import type { GlobalInfoModel, PageContext } from '@ohos/common'; +import { BreakpointType, BreakpointTypeEnum, CommonConstants } from '@ohos/common'; +import type { BannerData } from '@ohos/commonbusiness'; +import { + BannerCard, + BaseHomeEventType, + BaseHomeView, + CalculateHeightParam, + FullScreenNavigation, + LoadingMoreItemBuilder, + OffsetParam, + TabBarType, +} from '@ohos/commonbusiness'; +import { DeveloperCard } from '../component/DeveloperCard'; +import { ExperienceCard } from '../component/ExperienceCard'; +import { FeedCard } from '../component/FeedCard'; +import type { DiscoverCardData, DiscoverContent } from '../model/DiscoverData'; +import { ArticleTypeEnum } from '../model/DiscoverData'; +import type { ExplorationState } from '../viewmodel/ExplorationState'; +import { ExplorationEventType, ExplorationViewModel } from '../viewmodel/ExplorationViewModel'; + +@Component({ freezeWhenInactive: true }) +export struct ExplorationView { + viewModel: ExplorationViewModel = ExplorationViewModel.getInstance(); + @StorageProp('GlobalInfoModel') @Watch('handleBreakPointChange') globalInfoModel: GlobalInfoModel = + AppStorage.get('GlobalInfoModel')!; + @StorageProp('systemColorMode') @Watch('handleColorModeChange') systemColorMode: ConfigurationConstant.ColorMode = + AppStorage.get('systemColorMode')!; + @State explorationState: ExplorationState = this.viewModel.getState(); + private listScroller: Scroller = new Scroller(); + private explorationPageContext: PageContext = AppStorage.get('explorationPageContext')!; + + aboutToAppear(): void { + this.viewModel.sendEvent({ type: ExplorationEventType.LOAD_DISCOVERY_PAGE, param: null }); + } + + handleBreakPointChange() { + this.viewModel.sendEvent({ + type: BaseHomeEventType.HANDLE_BREAKPOINT_CHANGE, + param: { yOffset: (this.listScroller?.currentOffset()?.yOffset || 0), tabIndex: TabBarType.PRACTICE }, + }); + } + + handleColorModeChange() { + this.viewModel.sendEvent({ + type: BaseHomeEventType.HANDLE_COLOR_CHANGE, + param: { yOffset: (this.listScroller?.currentOffset()?.yOffset || 0), tabIndex: TabBarType.PRACTICE }, + }); + } + + jumpArticleDetail(componentContent: DiscoverContent) { + this.viewModel.sendEvent({ + type: ExplorationEventType.JUMP_DETAIL_DETAIL, + param: componentContent, + }); + } + + jumpBannerDetail(banner: BannerData) { + this.viewModel.sendEvent({ type: BaseHomeEventType.JUMP_BANNER_DETAIL, param: banner }); + } + + @Builder + CategoryHeaderBuilder(groupItem: string) { + Row() { + Text(groupItem) + .fontSize($r('sys.float.Subtitle_L')) + .fontWeight(FontWeight.Bold) + .fontColor($r('sys.color.font_primary')) + } + .justifyContent(FlexAlign.Start) + .width('100%') + .padding({ + top: $r('sys.float.padding_level4'), + bottom: $r('sys.float.padding_level4'), + left: new BreakpointType({ + sm: $r('sys.float.padding_level8'), + md: $r('sys.float.padding_level12'), + lg: CommonConstants.SPACE_32 + CommonConstants.TAB_BAR_WIDTH, + xl: $r('sys.float.padding_level16'), + }).getValue(this.globalInfoModel.currentBreakpoint), + }) + } + + @Builder + ContentViewBuilder() { + List({ scroller: this.listScroller, space: CommonConstants.SPACE_16 }) { + ListItem() { + BannerCard({ + tabViewType: TabBarType.PRACTICE, + bannerState: this.explorationState.bannerState, + handleItemClick: (banner: BannerData) => { + this.viewModel.sendEvent({ type: BaseHomeEventType.JUMP_BANNER_DETAIL, param: banner }); + }, + }) + } + .height(this.explorationState.bannerHeight) + + Repeat(this.explorationState.discoveryData) + .each((repeatItem: RepeatItem) => { + ListItem() { + Column() { + this.CategoryHeaderBuilder(repeatItem.item.name) + FeedCard({ + discoverContents: repeatItem.item.contents, + handleItemClick: (content: DiscoverContent) => { + this.jumpArticleDetail(content); + }, + }) + } + } + }) + .key((item: DiscoverCardData) => item.id.toString()) + .templateId((item: DiscoverCardData) => item.type.toString()) + .template(ArticleTypeEnum.EXPERIENCES.toString(), (repeatItem: RepeatItem) => { + ListItem() { + Column() { + this.CategoryHeaderBuilder(repeatItem.item.name) + ExperienceCard({ + discoverContents: repeatItem.item.contents, + handleItemClick: (content: DiscoverContent) => { + this.jumpArticleDetail(content); + }, + }) + .margin({ + left: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? + $r('sys.float.padding_level8') : 0, + right: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? + $r('sys.float.padding_level8') : 0, + }) + } + } + }) + .template(ArticleTypeEnum.DEVELOPER.toString(), (repeatItem: RepeatItem) => { + ListItem() { + Column() { + this.CategoryHeaderBuilder(repeatItem.item.name) + DeveloperCard({ + discoverContents: repeatItem.item.contents, + handleItemClick: (content: DiscoverContent) => { + this.jumpArticleDetail(content); + }, + }) + } + } + }) + ListItem() { + LoadingMoreItemBuilder(this.explorationState.loadingModel) + } + .padding({ + left: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG ? + CommonConstants.TAB_BAR_WIDTH + CommonConstants.SPACE_16 : CommonConstants.SPACE_16, + right: CommonConstants.SPACE_16, + bottom: (this.globalInfoModel.naviIndicatorHeight + + (new BreakpointType({ + sm: CommonConstants.TAB_BAR_HEIGHT, + md: CommonConstants.TAB_BAR_HEIGHT, + lg: 0, + }).getValue(this.globalInfoModel.currentBreakpoint))), + }) + } + .width('100%') + .height('100%') + .clip(false) + .scrollBar(BarState.Off) + .edgeEffect(this.explorationState.hasEdgeEffect ? EdgeEffect.Spring : EdgeEffect.None) + .onScrollFrameBegin((offset: number, state: ScrollState) => { + const param: CalculateHeightParam = { offset, state, yOffset: this.listScroller.currentOffset().yOffset }; + const bannerChangeHeight: boolean | void = this.viewModel.sendEvent({ + type: BaseHomeEventType.CALCULATE_BANNER_HEIGHT, + param, + }); + if (bannerChangeHeight) { + return { offsetRemain: 0 }; + } + return { offsetRemain: offset }; + }) + .onDidScroll(() => { + this.viewModel.sendEvent({ + type: BaseHomeEventType.HANDLE_SCROLL_OFFSET, + param: { yOffset: this.listScroller.currentOffset().yOffset, tabIndex: TabBarType.PRACTICE }, + }); + }) + .backgroundColor($r('sys.color.background_secondary')) + } + + @Builder + TopTitleViewBuilder() { + FullScreenNavigation({ + topNavigationData: this.explorationState.topNavigationData, + }) + } + + build() { + Navigation(this.explorationPageContext.navPathStack) { + BaseHomeView({ + loadingModel: this.explorationState.loadingModel, + contentView: () => { + this.ContentViewBuilder() + }, + topTitleView: () => { + this.TopTitleViewBuilder() + }, + reloadData: () => { + this.viewModel.sendEvent({ type: ExplorationEventType.LOAD_DISCOVERY_PAGE, param: null }); + }, + }) + } + .mode(NavigationMode.Stack) + .hideTitleBar(true) + } +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/viewmodel/ArticleDetailViewModel.ets b/features/exploration/src/main/ets/viewmodel/ArticleDetailViewModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..930a1aa77d1b2142b148aad03f4b4cb683575b5a --- /dev/null +++ b/features/exploration/src/main/ets/viewmodel/ArticleDetailViewModel.ets @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2024 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 { BaseVM, BreakpointTypeEnum, GlobalInfoModel, LoadingStatus, PageContext } from '@ohos/common'; +import { ComponentDetailParams, SampleDetailParams, TabBarType } from '@ohos/commonbusiness'; +import type { DiscoverContent } from '../model/DiscoverData'; +import { ExplorationDetailState } from './ExplorationDetailState'; + +export class ArticleDetailViewModel extends BaseVM { + public constructor() { + super(new ExplorationDetailState()); + this.state.topNavigationData.isBlur = true; + } + + sendEvent(eventParam: ExplorationDetailEventParam): Promise | void { + if (eventParam.type === ExplorationDetailEventType.GET_ARTICLE_DETAIL) { + return this.getArticleDetail(eventParam.param as DetailParam); + } else if (eventParam.type === ExplorationDetailEventType.POP) { + const param = eventParam.param as PopParam; + return this.pop(param.animation, param.tabBarView); + } else if (eventParam.type === ExplorationDetailEventType.JUMP_NATIVE_PAGE) { + return this.jumpNativePage(eventParam.param as NativePageParam); + } + throw new Error('Method not implemented.'); + } + + private getArticleDetail(detailParam: DetailParam): Promise { + this.state.loadingModel.loadingStatus = LoadingStatus.LOADING; + this.state.content = detailParam.content; + this.state.content.detailsUrl = `resource://resfile/${detailParam.content.detailsUrl}`; + this.state.topNavigationData.onBackClick = detailParam.onBackClick; + return Promise.resolve(); + } + + private pop(animated: boolean = true, tabBarView: number = TabBarType.HOME): void { + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + let currentPathStack: PageContext = AppStorage.get('pageContext') as PageContext; + if (globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL) { + if (tabBarView === TabBarType.HOME) { + currentPathStack = AppStorage.get('componentListPageContext')!; + } else if (tabBarView === TabBarType.SAMPLE) { + currentPathStack = AppStorage.get('samplePageContext')!; + } else { + currentPathStack = AppStorage.get('explorationPageContext')!; + } + } + currentPathStack.popPage(animated); + } + + + private jumpNativePage(param: NativePageParam): void { + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + let pageContext: PageContext = AppStorage.get('pageContext') as PageContext; + if (globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL) { + if (param.tabBarView === TabBarType.HOME) { + pageContext = AppStorage.get('componentListPageContext') as PageContext; + } else if (param.tabBarView === TabBarType.SAMPLE) { + pageContext = AppStorage.get('samplePageContext') as PageContext; + } else { + pageContext = AppStorage.get('explorationPageContext') as PageContext; + } + } + pageContext.openPage({ + routerName: param.type === 'component' ? 'ComponentDetailView' : 'SampleDetailView', + param: param.type === 'component' ? + { + componentName: param.componentName, + componentId: param.id, + } as ComponentDetailParams : + { + currentIndex: param.currentIndex, + sampleCardId: param.id, + } as SampleDetailParams, + }, true); + } +} + +export enum ExplorationDetailEventType { + GET_ARTICLE_DETAIL = 'getArticleDetail', + POP = 'pop', + HANDLE_TITLE_EFFECT = 'handleTitleEffect', + JUMP_NATIVE_PAGE = 'jumpNativePage', +} + +export interface ExplorationDetailEventParam { + type: ExplorationDetailEventType; + param: T; +} + +export interface DetailParam { + content: DiscoverContent; + onBackClick: Function; +} + +export interface PopParam { + animation: boolean; + tabBarView: number; +} + +export interface NativePageParam { + tabBarView: number; + type: string; + id: number; + currentIndex?: number; + componentName?: string; +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/viewmodel/ExplorationDetailState.ets b/features/exploration/src/main/ets/viewmodel/ExplorationDetailState.ets new file mode 100644 index 0000000000000000000000000000000000000000..9b229073e2b70043d6aa7dc2c358b59efaebe730 --- /dev/null +++ b/features/exploration/src/main/ets/viewmodel/ExplorationDetailState.ets @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 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 { BaseHomeState } from '@ohos/commonbusiness'; +import { DiscoverContent } from '../model/DiscoverData'; + +@Observed +export class ExplorationDetailState extends BaseHomeState { + public content: DiscoverContent = new DiscoverContent(); + + public constructor() { + super(); + } +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/viewmodel/ExplorationState.ets b/features/exploration/src/main/ets/viewmodel/ExplorationState.ets new file mode 100644 index 0000000000000000000000000000000000000000..f89b761a7f7c23248b15d6f67208e34a07952c3f --- /dev/null +++ b/features/exploration/src/main/ets/viewmodel/ExplorationState.ets @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 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 { BaseHomeState } from '@ohos/commonbusiness'; +import type { DiscoverCardData } from '../model/DiscoverData'; + +@Observed +export class ExplorationState extends BaseHomeState { + public discoveryData: DiscoverCardData[] = []; + + public constructor() { + super(); + } +} \ No newline at end of file diff --git a/features/exploration/src/main/ets/viewmodel/ExplorationViewModel.ets b/features/exploration/src/main/ets/viewmodel/ExplorationViewModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..136b239e179d1bccb01a8d9c31f5fd2178bb8e41 --- /dev/null +++ b/features/exploration/src/main/ets/viewmodel/ExplorationViewModel.ets @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2024 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 { ConfigurationConstant } from '@kit.AbilityKit'; +import type { BusinessError } from '@kit.BasicServicesKit'; +import type { GlobalInfoModel } from '@ohos/common'; +import { + BreakpointTypeEnum, + LoadingStatus, + Logger, + PageContext, + RequestErrorCode, + StatusBarColorType, + WindowUtil, +} from '@ohos/common'; +import type { BannerData } from '@ohos/commonbusiness'; +import { + ArticleDetailParams, + BaseHomeEventParam, + BaseHomeEventType, + BaseHomeViewModel, + TabBarType, + TAB_CONTENT_STATUSES, +} from '@ohos/commonbusiness'; +import type { DiscoverContent, DiscoverData } from '../model/DiscoverData'; +import { DiscoverModel } from '../model/DiscoverModel'; +import { ExplorationState } from './ExplorationState'; + +const TAG = '[ExplorationViewModel]'; + +export class ExplorationViewModel extends BaseHomeViewModel { + private static instance: ExplorationViewModel; + private model: DiscoverModel = DiscoverModel.getInstance(); + + private constructor() { + super(new ExplorationState()); + this.state.topNavigationData.title = $r('app.string.practice_name'); + } + + public static getInstance(): ExplorationViewModel { + if (!ExplorationViewModel.instance) { + ExplorationViewModel.instance = new ExplorationViewModel(); + } + return ExplorationViewModel.instance; + } + + sendEvent(eventParam: ExplorationEventParam): void | boolean { + const eventType: ExplorationEventType | BaseHomeEventType = eventParam.type; + if (eventType === ExplorationEventType.LOAD_DISCOVERY_PAGE) { + return this.loadDiscoverList(); + } else if (eventType === ExplorationEventType.JUMP_DETAIL_DETAIL) { + return this.jumpDetailView(eventParam.param as DiscoverContent); + } else { + return super.sendEvent(eventParam as BaseHomeEventParam); + } + } + + protected loadDiscoverList(): void { + const isDark: boolean = AppStorage.get('systemColorMode') === ConfigurationConstant.ColorMode.COLOR_MODE_DARK; + this.state.loadingModel.loadingStatus = LoadingStatus.LOADING; + this.state.topNavigationData.titleColor = isDark ? StatusBarColorType.WHITE : StatusBarColorType.BLACK; + this.state.topNavigationData.isBlur = false; + WindowUtil.updateStatusBarColor(getContext(), isDark); + this.model.getDiscoveryPage() + .then((result: DiscoverData) => { + result.bannerInfos?.forEach((item: BannerData) => { + item.tabViewType = TabBarType.PRACTICE; + }); + this.state.bannerState.bannerResource.setDataArray([...(result.bannerInfos || [])]); + this.state.discoveryData = result.discoveryData; + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + if (globalInfoModel.currentBreakpoint !== BreakpointTypeEnum.LG && + globalInfoModel.currentBreakpoint !== BreakpointTypeEnum.XL) { + this.state.topNavigationData.titleColor = StatusBarColorType.WHITE; + WindowUtil.updateStatusBarColor(getContext(), true); + TAB_CONTENT_STATUSES[TabBarType.PRACTICE] = true; + } + this.state.loadingModel.loadingStatus = LoadingStatus.SUCCESS; + Logger.info(TAG, `Request DiscoveryPage Success`); + }) + .catch((error: BusinessError) => { + WindowUtil.updateStatusBarColor(getContext(), isDark); + TAB_CONTENT_STATUSES[TabBarType.PRACTICE] = isDark; + if (error.code === RequestErrorCode.ERROR_NETWORK_CONNECT_FAILED) { + this.state.loadingModel.loadingStatus = LoadingStatus.NO_NETWORK; + } else { + this.state.loadingModel.loadingStatus = LoadingStatus.FAILED; + } + }); + } + + protected jumpDetailView(content: DiscoverContent): void { + const globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + const pathStack: PageContext = + globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? AppStorage.get('explorationPageContext')! : + AppStorage.get('pageContext') as PageContext; + const articleParam: ArticleDetailParams = { + id: content.id, + isArticle: true, + title: content.title, + detailsUrl: content.detailsUrl, + }; + pathStack.openPage({ + routerName: 'ArticleDetailView', + param: articleParam, + }, true); + } +} + +export enum ExplorationEventType { + JUMP_DETAIL_DETAIL = 'jumpDetailView', + LOAD_DISCOVERY_PAGE = 'loadDiscoverList', +} + +export interface ExplorationEventParam { + type: ExplorationEventType | BaseHomeEventType; + param: T; +} \ No newline at end of file diff --git a/features/exploration/src/main/module.json5 b/features/exploration/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..a052b38b3c540ad14186498d20f3f535ff8b3748 --- /dev/null +++ b/features/exploration/src/main/module.json5 @@ -0,0 +1,12 @@ +{ + "module": { + "name": "exploration", + "type": "har", + "routerMap": "$profile:router_map", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ] + } +} diff --git a/features/exploration/src/main/resources/base/element/float.json b/features/exploration/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..e0057b4ef2de0b7a1be62fe357eb242f3f896f44 --- /dev/null +++ b/features/exploration/src/main/resources/base/element/float.json @@ -0,0 +1,44 @@ +{ + "float": [ + { + "name": "feed_item_card_height_sm", + "value": "180vp" + }, + { + "name": "feed_item_card_height_md", + "value": "208vp" + }, + { + "name": "feed_item_card_height_lg", + "value": "280vp" + }, + { + "name": "feed_item_card_height_xl", + "value": "346vp" + }, + { + "name": "feed_item_card_title_height", + "value": "19vp" + }, + { + "name": "feed_item_card_subtitle_height", + "value": "16vp" + }, + { + "name": "img_card_height", + "value": "400vp" + }, + { + "name": "img_card_height_verde", + "value": "456vp" + }, + { + "name": "img_card_content_height", + "value": "144vp" + }, + { + "name": "list_card_height", + "value": "240vp" + } + ] +} \ No newline at end of file diff --git a/features/exploration/src/main/resources/base/element/string.json b/features/exploration/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..e414643837c1ded6b28531c6dbd14e171b72f2bf --- /dev/null +++ b/features/exploration/src/main/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "practice_name", + "value": "Practice" + } + ] +} diff --git a/features/exploration/src/main/resources/base/media/img_placeholder.png b/features/exploration/src/main/resources/base/media/img_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..f9b5440a2b7abc295a852ff8ca8e3b0ad284c77a Binary files /dev/null and b/features/exploration/src/main/resources/base/media/img_placeholder.png differ diff --git a/features/exploration/src/main/resources/base/profile/router_map.json b/features/exploration/src/main/resources/base/profile/router_map.json new file mode 100644 index 0000000000000000000000000000000000000000..26f97e70bc2d687d20b8edb8110bbfeddcafe8dd --- /dev/null +++ b/features/exploration/src/main/resources/base/profile/router_map.json @@ -0,0 +1,14 @@ +{ + "routerMap": [ + { + "name": "ArticleDetailView", + "pageSourceFile": "src/main/ets/view/ArticleDetailView.ets", + "buildFunction": "ArticleDetailViewBuilder" + }, + { + "name": "BannerDetailView", + "pageSourceFile": "src/main/ets/view/BannerDetailView.ets", + "buildFunction": "BannerDetailViewBuilder" + } + ] +} \ No newline at end of file diff --git a/features/exploration/src/main/resources/en_US/element/string.json b/features/exploration/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..e414643837c1ded6b28531c6dbd14e171b72f2bf --- /dev/null +++ b/features/exploration/src/main/resources/en_US/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "practice_name", + "value": "Practice" + } + ] +} diff --git a/features/exploration/src/main/resources/zh_CN/element/string.json b/features/exploration/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..65395efbea43d9274162ff99ee80d63a107f5bbe --- /dev/null +++ b/features/exploration/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "practice_name", + "value": "实践" + } + ] +} diff --git a/features/exploration/src/ohosTest/ets/test/Ability.test.ets b/features/exploration/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..85c78f67579d6e31b5f5aeea463e216b9b141048 --- /dev/null +++ b/features/exploration/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +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/features/exploration/src/ohosTest/ets/test/List.test.ets b/features/exploration/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..794c7dc4ed66bd98fa3865e07922906e2fcef545 --- /dev/null +++ b/features/exploration/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/features/exploration/src/ohosTest/module.json5 b/features/exploration/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c7f7d4537a09bba3b132760f73f0f17b7e3eaf0a --- /dev/null +++ b/features/exploration/src/ohosTest/module.json5 @@ -0,0 +1,13 @@ +{ + "module": { + "name": "exploration_test", + "type": "feature", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} \ No newline at end of file diff --git a/features/exploration/src/test/List.test.ets b/features/exploration/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..bb5b5c3731e283dd507c847560ee59bde477bbc7 --- /dev/null +++ b/features/exploration/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/features/exploration/src/test/LocalUnit.test.ets b/features/exploration/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..165fc1615ee8618b4cb6a622f144a9a707eee99f --- /dev/null +++ b/features/exploration/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +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/features/mine/.gitignore b/features/mine/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/features/mine/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/features/mine/Index.ets b/features/mine/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..cc1474f696e6ae878026c954385d9d06c1e4bd86 --- /dev/null +++ b/features/mine/Index.ets @@ -0,0 +1 @@ +export { MineView } from './src/main/ets/view/MineView'; \ No newline at end of file diff --git a/features/mine/build-profile.json5 b/features/mine/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..697dff23e224373edb713dc2b8a08ed7341d5b4c --- /dev/null +++ b/features/mine/build-profile.json5 @@ -0,0 +1,31 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest" + } + ] +} diff --git a/features/mine/consumer-rules.txt b/features/mine/consumer-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/features/mine/hvigorfile.ts b/features/mine/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..42187071482d292588ad40babeda74f7b8d97a23 --- /dev/null +++ b/features/mine/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/features/mine/obfuscation-rules.txt b/features/mine/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..fdbb5b9852d7dd5f39bddaeb21ab5ee1f3346749 --- /dev/null +++ b/features/mine/obfuscation-rules.txt @@ -0,0 +1,22 @@ +# 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/features/mine/oh-package.json5 b/features/mine/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c16fe264d47b353dd40eb63236d5bb22ee181124 --- /dev/null +++ b/features/mine/oh-package.json5 @@ -0,0 +1,11 @@ +{ + "name": "mine", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@ohos/common": "file:../../common" + } +} diff --git a/features/mine/src/main/ets/component/AboutItemCard.ets b/features/mine/src/main/ets/component/AboutItemCard.ets new file mode 100644 index 0000000000000000000000000000000000000000..732a1e8d4e766f5a6c2a549b0a0983058b5d60d5 --- /dev/null +++ b/features/mine/src/main/ets/component/AboutItemCard.ets @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024 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 type { BundleInfoData } from '@ohos/common'; +import type { AboutState } from '../viewmodel/AboutState'; +import { AboutVM, CheckVersionEvent, UpdateVersionEvent } from '../viewmodel/AboutVM'; + +@Component +export struct AboutItemCard { + viewModel: AboutVM = AboutVM.getInstance(); + @State aboutState: AboutState = this.viewModel.getState(); + + aboutToAppear(): void { + this.viewModel.sendEvent(new CheckVersionEvent()); + } + + build() { + Row() { + Column() { + if (this.aboutState.laterVersionExist) { + Badge({ + value: '', + style: { badgeSize: 6, badgeColor: $r('app.color.about_badge_color') }, + position: BadgePosition.Right, + }) { + Text($r('app.string.version_information')) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_primary')) + .margin({ right: $r('sys.float.padding_level6') }) + } + } else { + Text($r('app.string.version_information')) + .fontWeight(FontWeight.Medium) + .fontSize($r('sys.float.Body_L')) + .fontColor($r('sys.color.font_primary')) + } + + Text(AppStorage.get('BundleInfoData')?.versionName as string) + .margin({ top: $r('sys.float.padding_level2') }) + .fontWeight(FontWeight.Regular) + .fontSize($r('sys.float.Subtitle_S')) + .fontColor($r('sys.color.font_secondary')) + } + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.Center) + + Row() { + if (!this.aboutState.isLoadingUpdate) { + Text(this.aboutState.laterVersionExist ? $r('app.string.updated_version') : $r('app.string.latest_version')) + .fontWeight(FontWeight.Regular) + .fontSize($r('sys.float.Subtitle_S')) + .fontColor($r('sys.color.font_secondary')) + .margin({ right: $r('sys.float.padding_level2') }) + SymbolGlyph($r('sys.symbol.chevron_right')) + .fontSize($r('sys.float.Title_S')) + .fontColor([$r('sys.color.icon_fourth')]) + } else { + LoadingProgress() + .width($r('app.float.about_loadingProgress_size')) + .height($r('app.float.about_loadingProgress_size')) + } + } + .onClick(() => { + if (this.aboutState.laterVersionExist) { + this.viewModel.sendEvent(new UpdateVersionEvent()); + } + }) + } + .width('100%') + .height($r('app.float.about_item_card_height')) + .borderRadius($r('sys.float.corner_radius_level7')) + .backgroundColor($r('sys.color.comp_background_list_card')) + .justifyContent(FlexAlign.SpaceBetween) + .alignItems(VerticalAlign.Center) + .padding({ + left: $r('sys.float.padding_level6'), + top: $r('sys.float.padding_level7'), + bottom: $r('sys.float.padding_level7'), + right: $r('sys.float.padding_level6'), + }) + } +} \ No newline at end of file diff --git a/features/mine/src/main/ets/component/CardItem.ets b/features/mine/src/main/ets/component/CardItem.ets new file mode 100644 index 0000000000000000000000000000000000000000..ce068fc4b4ddbc689c19191f4c63561e4e2f3705 --- /dev/null +++ b/features/mine/src/main/ets/component/CardItem.ets @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 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 type { GlobalInfoModel } from '@ohos/common'; +import { BreakpointTypeEnum, CommonConstants } from '@ohos/common'; +import { AboutBuilder } from '../view/AboutView'; + +@Component +export struct CardItem { + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @Prop isShow: boolean; + textContent?: Resource; + symbolSrc?: Resource; + onclick: Function = () => { + }; + onClose: Function = () => { + }; + + build() { + Row() { + SymbolGlyph(this.symbolSrc) + .fontSize($r('sys.float.Title_M')) + .fontColor([$r('sys.color.icon_secondary')]) + Text(this.textContent) + .fontSize($r('sys.float.Subtitle_M')) + .fontWeight(FontWeight.Medium) + .fontColor($r('sys.color.font_primary')) + .margin({ left: $r('sys.float.padding_level8') }) + Blank() + SymbolGlyph($r('sys.symbol.chevron_forward')) + .fontSize($r('sys.float.Subtitle_L')) + .fontColor([$r('sys.color.icon_fourth')]) + } + .width('100%') + .height($r('app.float.mine_listItem_height')) + .onClick(() => { + this.onclick(); + }) + .bindSheet(this.isShow, AboutBuilder(), { + preferType: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? SheetType.BOTTOM : + SheetType.CENTER, + title: { title: $r('app.string.about') }, + height: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? + ((this.globalInfoModel.deviceHeight - this.globalInfoModel.decorHeight) * + CommonConstants.SHEET_HEIGHT_RATIO_XL) : SheetSize.LARGE, + width: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? CommonConstants.SHEET_WIDTH_XL : + undefined, + onWillDisappear: () => { + this.onClose(); + }, + }) + } +} \ No newline at end of file diff --git a/features/mine/src/main/ets/view/AboutView.ets b/features/mine/src/main/ets/view/AboutView.ets new file mode 100644 index 0000000000000000000000000000000000000000..1cb74024cc79f446f25765b0014790adc75928bc --- /dev/null +++ b/features/mine/src/main/ets/view/AboutView.ets @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 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 { CommonConstants } from '@ohos/common'; +import { AboutItemCard } from '../component/AboutItemCard'; +import { AboutVM, ViewRegistrationInfoEvent } from '../viewmodel/AboutVM'; + +@Component +export struct AboutView { + viewModel: AboutVM = AboutVM.getInstance(); + + build() { + Column() { + Column() { + Image($r('app.media.app_icon')) + .draggable(false) + .width($r('app.float.about_image_size')) + .height($r('app.float.about_image_size')) + .borderRadius($r('sys.float.corner_radius_level8')) + Text($r('app.string.app_name')) + .fontSize($r('sys.float.Title_S')) + .fontWeight(FontWeight.Bold) + .margin({ top: $r('sys.float.padding_level4') }) + AboutItemCard() + .margin({ top: $r('sys.float.padding_level16') }) + } + .margin({ top: $r('sys.float.padding_level8') }) + .padding({ left: $r('sys.float.padding_level8'), right: $r('sys.float.padding_level8') }) + + Column() { + Text($r('app.string.copyright1')) + .fontWeight(FontWeight.Medium) + .fontColor($r('sys.color.font_emphasize')) + .fontSize($r('sys.float.Body_S')) + .onClick(() => { + this.viewModel.sendEvent(new ViewRegistrationInfoEvent()); + }) + Text($r('app.string.copyright2')) + .fontColor($r('sys.color.font_secondary')) + .fontWeight(FontWeight.Regular) + .fontSize($r('sys.float.Body_S')) + } + .margin({ bottom: $r('sys.float.padding_level24') }) + } + .justifyContent(FlexAlign.SpaceBetween) + .width(CommonConstants.FULL_PERCENT) + .height(CommonConstants.FULL_PERCENT) + } +} + +@Builder +export function AboutBuilder() { + AboutView() +} \ No newline at end of file diff --git a/features/mine/src/main/ets/view/MineView.ets b/features/mine/src/main/ets/view/MineView.ets new file mode 100644 index 0000000000000000000000000000000000000000..699f3275242cedf1edc498c7a2383616e1be3366 --- /dev/null +++ b/features/mine/src/main/ets/view/MineView.ets @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2024 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 type { GlobalInfoModel } from '@ohos/common'; +import { BreakpointType, BreakpointTypeEnum, CommonConstants, TopNavigationView } from '@ohos/common'; +import { AboutBindSheetEvent, MinePageVM } from '../viewmodel/MinePageVM'; +import { MinePageState } from '../viewmodel/MinePageState'; +import { CardItem } from '../component/CardItem'; + +@Component +export struct MineView { + viewModel: MinePageVM = MinePageVM.getInstance(); + @State minePageState: MinePageState = this.viewModel.getState(); + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + + throttle(func: Function, interval: number) { + let lastTime = 0; + return () => { + const nowTime = Date.now(); + const remainTime = interval - (nowTime - lastTime); + if (remainTime <= 0) { + lastTime = nowTime; + func(); + } + }; + } + + build() { + Column() { + TopNavigationView({ topNavigationData: { + fontSize:new BreakpointType({ + sm: $r('sys.float.Title_M'), + md: $r('sys.float.Title_L'), + lg: $r('sys.float.Title_L'), + xl: $r('sys.float.Title_S'), + }).getValue(this.globalInfoModel.currentBreakpoint), + bgColor:$r('sys.color.background_secondary'), + title: $r('app.string.mine_title'), + isFullScreen: true, + }}); + + List() { + ListItemGroup() { + ListItem({ style: ListItemStyle.CARD }) { + CardItem({ + isShow: this.minePageState.aboutViewShow, + textContent: $r('app.string.about'), + symbolSrc: $r('sys.symbol.info_circle'), + onclick: () => { + this.viewModel.sendEvent(new AboutBindSheetEvent(true)); + }, + onClose: () => { + this.viewModel.sendEvent(new AboutBindSheetEvent(false)); + }, + }) + } + .height(undefined) + .width(CommonConstants.FULL_PERCENT) + } + .divider({ + strokeWidth: $r('app.float.mine_divider'), + color: $r('sys.color.comp_divider'), + startMargin: $r('sys.float.padding_level24'), + endMargin: $r('sys.float.padding_level6'), + }) + .margin({ top: $r('sys.float.padding_level6') }) + .padding($r('sys.float.padding_level2')) + .backgroundColor($r('sys.color.comp_background_primary')) + .borderRadius($r('sys.float.corner_radius_level8')) + } + .edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true }) + .width(CommonConstants.FULL_PERCENT) + .height(CommonConstants.FULL_PERCENT) + .padding(new BreakpointType({ + sm: { + left: $r('sys.float.padding_level8'), + right: $r('sys.float.padding_level8') + }, + md: { + left: $r('sys.float.padding_level12'), + right: $r('sys.float.padding_level12') + }, + lg: { + left: $r('sys.float.padding_level16'), + right: $r('sys.float.padding_level16') + }, + }).getValue(this.globalInfoModel.currentBreakpoint)) + .backgroundColor($r('sys.color.background_secondary')) + } + .width(CommonConstants.FULL_PERCENT) + .height(CommonConstants.FULL_PERCENT) + .padding({ + left: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG ? CommonConstants.TAB_BAR_WIDTH : 0, + }) + } +} \ No newline at end of file diff --git a/features/mine/src/main/ets/viewmodel/AboutState.ets b/features/mine/src/main/ets/viewmodel/AboutState.ets new file mode 100644 index 0000000000000000000000000000000000000000..b51b97ea0cef6fff159cdeaf267ee87844e459ef --- /dev/null +++ b/features/mine/src/main/ets/viewmodel/AboutState.ets @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 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 { BaseState } from '@ohos/common'; + +@Observed +export class AboutState extends BaseState { + public currentVersion: string = '1.0.0'; + public laterVersionExist: boolean = false; + public isLoadingUpdate: boolean = false; + + public constructor() { + super(); + } +} \ No newline at end of file diff --git a/features/mine/src/main/ets/viewmodel/AboutVM.ets b/features/mine/src/main/ets/viewmodel/AboutVM.ets new file mode 100644 index 0000000000000000000000000000000000000000..b8bf718b30fa17cb5cb36f7f560793c976e530dc --- /dev/null +++ b/features/mine/src/main/ets/viewmodel/AboutVM.ets @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 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 type { common, Want } from '@kit.AbilityKit'; +import type { BusinessError } from '@kit.BasicServicesKit'; +import type { BundleInfoData } from '@ohos/common'; +import { BaseVM, BaseVMEvent, ConfigMapKey, Logger, ResourceUtil, UpdateService } from '@ohos/common'; +import { AboutState } from './AboutState'; + +const TAG: string = '[AboutVM]'; + +export class AboutVM extends BaseVM { + private static instance: AboutVM; + private updateService = UpdateService.getInstance(); + + public static getInstance(): AboutVM { + if (!AboutVM.instance) { + AboutVM.instance = new AboutVM(); + } + return AboutVM.instance; + } + + private constructor() { + super(new AboutState()); + } + + sendEvent(event: BaseVMEvent) { + if (event instanceof CheckVersionEvent) { + this.checkVersion(); + } else if (event instanceof UpdateVersionEvent) { + this.updateVersion(); + } else if (event instanceof ViewRegistrationInfoEvent) { + const context: common.UIAbilityContext = getContext() as common.UIAbilityContext; + this.jumpToBrowser(context); + } + } + + private checkVersion(): void { + this.updateService.checkUpdate().then((existNewVersion: boolean) => { + this.state.laterVersionExist = existNewVersion; + this.state.currentVersion = AppStorage.get('BundleInfoData')?.versionName as string; + }); + } + + private updateVersion(): void { + this.state.isLoadingUpdate = true; + this.updateService.updateVersion().then(() => { + this.state.isLoadingUpdate = false; + }); + } + + private jumpToBrowser(context: common.UIAbilityContext): void { + const want: Want = { + action: 'ohos.want.action.viewData', + entities: ['entity.system.browsable'], + uri: ResourceUtil.getRawFileStringByKey(getContext(), ConfigMapKey.MIIT_URL), + }; + context.startAbility(want) + .then(() => { + Logger.info(TAG, 'Start browsableAbility successfully.'); + }) + .catch((err: BusinessError) => { + Logger.error(TAG, `Failed to startAbility. Code: ${err.code}, message: ${err.message}`); + }); + } +} + +export class CheckVersionEvent implements BaseVMEvent { +} + +export class UpdateVersionEvent implements BaseVMEvent { +} + +export class ViewRegistrationInfoEvent implements BaseVMEvent { +} \ No newline at end of file diff --git a/features/mine/src/main/ets/viewmodel/MinePageState.ets b/features/mine/src/main/ets/viewmodel/MinePageState.ets new file mode 100644 index 0000000000000000000000000000000000000000..c74df5dfd68654a1db51e8ce927dbfca9e41febc --- /dev/null +++ b/features/mine/src/main/ets/viewmodel/MinePageState.ets @@ -0,0 +1,22 @@ +/* +* Copyright (c) 2024 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 { BaseState, TopNavigationData } from '@ohos/common'; + +@Observed +export class MinePageState extends BaseState { + public feedbackViewShow: boolean = false; + public aboutViewShow: boolean = false; +} \ No newline at end of file diff --git a/features/mine/src/main/ets/viewmodel/MinePageVM.ets b/features/mine/src/main/ets/viewmodel/MinePageVM.ets new file mode 100644 index 0000000000000000000000000000000000000000..54e863db612d406eee96a8958f48c07fb2b79f59 --- /dev/null +++ b/features/mine/src/main/ets/viewmodel/MinePageVM.ets @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 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 { BaseVM, BaseVMEvent } from '@ohos/common'; +import { MinePageState } from './MinePageState'; + +export class MinePageVM extends BaseVM { + private static instance: MinePageVM; + + public static getInstance() { + if (!MinePageVM.instance) { + MinePageVM.instance = new MinePageVM(); + } + return MinePageVM.instance; + } + + private constructor() { + super(new MinePageState()); + } + + public sendEvent(event: BaseVMEvent): void { + if (event instanceof AboutBindSheetEvent) { + this.state.aboutViewShow = event.dataValue; + } else if (event instanceof FeedbackBindSheetEvent) { + this.state.feedbackViewShow = event.dataValue; + } + } +} + +export class AboutBindSheetEvent implements BaseVMEvent { + readonly dataValue: boolean; + + constructor(dataValue: boolean) { + this.dataValue = dataValue; + } +} + +export class FeedbackBindSheetEvent implements BaseVMEvent { + readonly dataValue: boolean; + + constructor(dataValue: boolean) { + this.dataValue = dataValue; + } +} \ No newline at end of file diff --git a/features/mine/src/main/module.json5 b/features/mine/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..d100bc809bdcbb4b02979f14ba65fa198726473e --- /dev/null +++ b/features/mine/src/main/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "mine", + "type": "har", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ] + } +} \ No newline at end of file diff --git a/features/mine/src/main/resources/base/element/color.json b/features/mine/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..bfc642033db0d5057535239c347894d13a97ff0c --- /dev/null +++ b/features/mine/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "about_badge_color", + "value": "#FA2A2D" + } + ] +} \ No newline at end of file diff --git a/features/mine/src/main/resources/base/element/float.json b/features/mine/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..c8f37dea4a82eceeb507c30ad600539a676cad0a --- /dev/null +++ b/features/mine/src/main/resources/base/element/float.json @@ -0,0 +1,28 @@ +{ + "float": [ + { + "name": "about_image_size", + "value": "72vp" + }, + { + "name": "about_item_card_height", + "value": "72vp" + }, + { + "name": "about_loadingProgress_size", + "value": "24vp" + }, + { + "name": "mine_listItem_height", + "value": "56vp" + }, + { + "name": "mine_divider", + "value": "1px" + }, + { + "name": "mine_font_size", + "value": "26fp" + } + ] +} \ No newline at end of file diff --git a/features/mine/src/main/resources/base/element/string.json b/features/mine/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..8a117c85dfa380dda927945bf6e5512c05b3a3f8 --- /dev/null +++ b/features/mine/src/main/resources/base/element/string.json @@ -0,0 +1,36 @@ +{ + "string": [ + { + "name": "mine_title", + "value": "Mine" + }, + { + "name": "about", + "value": "about" + }, + { + "name": "version_information", + "value": "Version information" + }, + { + "name": "updated_version", + "value": "To be updated" + }, + { + "name": "latest_version", + "value": "The latest version" + }, + { + "name": "app_name", + "value": "Sample in HarmonyOS" + }, + { + "name": "copyright1", + "value": "GuangdongA2-20044005-302A" + }, + { + "name": "copyright2", + "value": "Copyright © 2025 Huawei Device Co., Ltd. All rights reserved." + } + ] +} \ No newline at end of file diff --git a/features/mine/src/main/resources/base/media/app_icon.png b/features/mine/src/main/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..99bd6a94a18cb81bd8a0af3329eec20cb4748c7c Binary files /dev/null and b/features/mine/src/main/resources/base/media/app_icon.png differ diff --git a/features/mine/src/main/resources/en_US/element/string.json b/features/mine/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..8a117c85dfa380dda927945bf6e5512c05b3a3f8 --- /dev/null +++ b/features/mine/src/main/resources/en_US/element/string.json @@ -0,0 +1,36 @@ +{ + "string": [ + { + "name": "mine_title", + "value": "Mine" + }, + { + "name": "about", + "value": "about" + }, + { + "name": "version_information", + "value": "Version information" + }, + { + "name": "updated_version", + "value": "To be updated" + }, + { + "name": "latest_version", + "value": "The latest version" + }, + { + "name": "app_name", + "value": "Sample in HarmonyOS" + }, + { + "name": "copyright1", + "value": "GuangdongA2-20044005-302A" + }, + { + "name": "copyright2", + "value": "Copyright © 2025 Huawei Device Co., Ltd. All rights reserved." + } + ] +} \ No newline at end of file diff --git a/features/mine/src/main/resources/zh_CN/element/string.json b/features/mine/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..73815d877c4efea3dead221ef608dc8c8223b34c --- /dev/null +++ b/features/mine/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,36 @@ +{ + "string": [ + { + "name": "mine_title", + "value": "我的" + }, + { + "name": "about", + "value": "关于" + }, + { + "name": "version_information", + "value": "版本信息" + }, + { + "name": "updated_version", + "value": "待更新" + }, + { + "name": "latest_version", + "value": "已是最新版本" + }, + { + "name": "app_name", + "value": "HMOS代码工坊" + }, + { + "name": "copyright1", + "value": "粤A2-20044005号-302A" + }, + { + "name": "copyright2", + "value": "版权所有 © 2025 华为终端有限公司。保留一切权利。" + } + ] +} \ No newline at end of file diff --git a/features/mine/src/ohosTest/ets/test/Ability.test.ets b/features/mine/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..85c78f67579d6e31b5f5aeea463e216b9b141048 --- /dev/null +++ b/features/mine/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +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/features/mine/src/ohosTest/ets/test/List.test.ets b/features/mine/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..794c7dc4ed66bd98fa3865e07922906e2fcef545 --- /dev/null +++ b/features/mine/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/features/mine/src/ohosTest/module.json5 b/features/mine/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..eb64b5a3061fed82a1e090921ffeeb15f43c3287 --- /dev/null +++ b/features/mine/src/ohosTest/module.json5 @@ -0,0 +1,13 @@ +{ + "module": { + "name": "mine_test", + "type": "feature", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} \ No newline at end of file diff --git a/features/mine/src/test/List.test.ets b/features/mine/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..bb5b5c3731e283dd507c847560ee59bde477bbc7 --- /dev/null +++ b/features/mine/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/features/mine/src/test/LocalUnit.test.ets b/features/mine/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..165fc1615ee8618b4cb6a622f144a9a707eee99f --- /dev/null +++ b/features/mine/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +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/hvigor/hvigor-config.json5 b/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c8bc80925dedf78ebb304fa84b5f55dc2e28a640 --- /dev/null +++ b/hvigor/hvigor-config.json5 @@ -0,0 +1,32 @@ +{ + "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*/ + }, + "properties": { + // 配置为0,表示不启用内存缓存配置,默认为4,数值越低,内存中缓存数据越少 + "hvigor.pool.cache.capacity": 0, + // 默认配置为cpu核数-1, 包含ohos.arkCompile.maxSize4,值越小,占用内存越少 + "hvigor.pool.maxSize" : 5, + // 默认配置值为5, 值越小,占用内存越少 + "ohos.arkCompile.maxSize": 3, + // 默认配置值为true, 表示开启内存缓存,占用内存较多,配置为false,关闭内存缓存,占用内存较少 + "hvigor.enableMemoryCache": false + } +} diff --git a/hvigorfile.ts b/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3cb9f1a87a81687554a76283af8df27d8bda775 --- /dev/null +++ b/hvigorfile.ts @@ -0,0 +1,6 @@ +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/oh-package.json5 b/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..e87d89a18648e6bbce4c12f95795dffbe7015dab --- /dev/null +++ b/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "modelVersion": "5.0.0", + "description": "Please describe the basic information.", + "dependencies": {}, + "devDependencies": { + "@ohos/hypium": "1.0.19", + "@ohos/hamock": "1.0.0" + }, + "dynamicDependencies": {} +} \ No newline at end of file diff --git a/products/phone/.gitignore b/products/phone/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/products/phone/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/products/phone/build-profile.json5 b/products/phone/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..dd1b1c85bf21bb59bf502d2da10464c3e79259a0 --- /dev/null +++ b/products/phone/build-profile.json5 @@ -0,0 +1,41 @@ +{ + "apiType": "stageMode", + "buildOption": { + "arkOptions": { + "runtimeOnly": { + "packages": [ + "@ohos/componentlibrary", + "@ohos/devpractices", + "@ohos/exploration", + "@ohos/mine", + "@ohos/commonbusiness", + "@ohos/common", + "@ohos/exploration" + ] + } + }, + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/products/phone/hvigorfile.ts b/products/phone/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6edcd90486dd5a853cf7d34c8647f08414ca7a3 --- /dev/null +++ b/products/phone/hvigorfile.ts @@ -0,0 +1,6 @@ +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/products/phone/obfuscation-rules.txt b/products/phone/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..ae15d770fd622fb7f2d6829e523cf403e77a556a --- /dev/null +++ b/products/phone/obfuscation-rules.txt @@ -0,0 +1,26 @@ +# 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 + +# white list for PenKit Context file +-keep +./src/main/ets/util/ContextConfig.ts \ No newline at end of file diff --git a/products/phone/oh-package.json5 b/products/phone/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..800a9268e952c9e9a707b50bbbc6f8c2e0610d38 --- /dev/null +++ b/products/phone/oh-package.json5 @@ -0,0 +1,16 @@ +{ + "name": "phone", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "@ohos/componentlibrary": "file:../../features/componentlibrary", + "@ohos/devpractices": "file:../../features/devpractices", + "@ohos/exploration": "file:../../features/exploration", + "@ohos/mine": "file:../../features/mine", + "@ohos/commonbusiness": "file:../../features/commonbusiness", + "@ohos/common": "file:../../common" + } +} \ No newline at end of file diff --git a/products/phone/src/main/ets/component/CustomSideBar.ets b/products/phone/src/main/ets/component/CustomSideBar.ets new file mode 100644 index 0000000000000000000000000000000000000000..2fde41c6d51ddddb7fd780f55ce630e533d5ab39 --- /dev/null +++ b/products/phone/src/main/ets/component/CustomSideBar.ets @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024 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 { CommonConstants } from '@ohos/common'; +import { TabBarData, TABS_LIST } from '../model/TabBarModel'; + +@Component +export struct CustomSideBar { + @Prop @Require currentIndex: number; + @State focus: boolean = false; + sideBarChange: (index: number) => void = (index: number) => { + }; + + @Builder + BarItemBuilder(item: TabBarData) { + Row() { + SymbolGlyph(item.icon) + .fontSize($r('sys.float.Title_M')) + .fontColor(item.id === this.currentIndex ? [$r('sys.color.icon_emphasize')] : + [$r('sys.color.icon_secondary')]) + .renderingStrategy(SymbolRenderingStrategy.MULTIPLE_OPACITY) + .symbolEffect(new BounceSymbolEffect(EffectScope.LAYER, EffectDirection.UP), item.id === this.currentIndex) + .padding({ left: $r('sys.float.padding_level4'), right: $r('sys.float.padding_level4') }) + Text(item.title) + .fontSize($r('sys.float.Body_L')) + .fontWeight(FontWeight.Medium) + .fontColor(item.id === this.currentIndex ? $r('sys.color.interactive_active') : + $r('sys.color.font_tertiary')) + } + .alignItems(VerticalAlign.Center) + .backgroundColor(this.currentIndex !== item.id ? Color.Transparent : + this.focus ? $r('app.color.hmos_side_bar_background_color') : $r('sys.color.interactive_hover')) + .width('100%') + .height($r('app.float.side_bar_height')) + .margin({ + top: $r('sys.float.padding_level4'), + bottom: $r('sys.float.padding_level1'), + }) + .borderRadius($r('sys.float.corner_radius_level4')) + .onClick(() => { + if (this.currentIndex !== item.id) { + this.sideBarChange(item.id); + } + }) + } + + build() { + Column() { + Row() { + Image($r('app.media.ic_start_icon')) + .width($r('app.float.app_icon_width')) + .aspectRatio(1) + .borderRadius($r('sys.float.corner_radius_level4')) + .margin({ right: $r('sys.float.padding_level6') }) + Text($r('app.string.EntryAbility_label')) + .fontColor($r('sys.color.font_primary')) + .fontSize($r('sys.float.Body_L')) + } + .margin({ left: $r('sys.float.padding_level4'), right: $r('sys.float.padding_level4') }) + .height($r('app.float.side_bar_title_height')) + .width('100%') + + ForEach(TABS_LIST, (item: TabBarData) => { + this.BarItemBuilder(item) + }, (item: TabBarData) => JSON.stringify(item) + this.currentIndex) + } + .width(CommonConstants.SIDE_BAR_WIDTH) + .height('100%') + .padding({ + left: $r('sys.float.padding_level8'), + right: $r('sys.float.padding_level8'), + }) + .onHover((isHover: boolean) => { + this.focus = isHover; + }) + } +} \ No newline at end of file diff --git a/products/phone/src/main/ets/component/CustomTabBar.ets b/products/phone/src/main/ets/component/CustomTabBar.ets new file mode 100644 index 0000000000000000000000000000000000000000..924d4b9283270d6ee1d709ed36e4ac050a157100 --- /dev/null +++ b/products/phone/src/main/ets/component/CustomTabBar.ets @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2024 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 type { GlobalInfoModel } from '@ohos/common'; +import { BreakpointType, CommonConstants } from '@ohos/common'; +import type { TabBarData } from '../model/TabBarModel'; +import { TABS_LIST } from '../model/TabBarModel'; + +@Component +export struct CustomTabBar { + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @StorageProp('BlurRenderGroup') blurRenderGroup: boolean = false; + @Prop @Require currentIndex: number; + tabBarChange: (index: number) => void = (index: number) => { + }; + + @Builder + TabItemBuilder(tabBar: TabBarData) { + Column() { + SymbolGlyph(tabBar.icon) + .fontSize($r('sys.float.Title_M')) + .fontColor(tabBar.id === this.currentIndex ? [$r('sys.color.interactive_active')] : + [$r('sys.color.font_tertiary')]) + .renderingStrategy(SymbolRenderingStrategy.MULTIPLE_OPACITY) + .symbolEffect(new BounceSymbolEffect(EffectScope.LAYER, EffectDirection.UP), tabBar.id === this.currentIndex) + Text(tabBar.title) + .fontSize($r('sys.float.Caption_M')) + .margin({ top: $r('sys.float.padding_level1') }) + .fontWeight(FontWeight.Medium) + .fontColor(tabBar.id === this.currentIndex ? $r('sys.color.interactive_active') : $r('sys.color.font_tertiary')) + } + .width('100%') + .height('100%') + .onClick(() => { + if (this.currentIndex !== tabBar.id) { + this.tabBarChange(tabBar.id); + } + }) + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Center) + } + + build() { + Flex({ + direction: new BreakpointType({ + sm: FlexDirection.ColumnReverse, + md: FlexDirection.ColumnReverse, + lg: FlexDirection.Row, + }).getValue(this.globalInfoModel.currentBreakpoint), + alignItems: ItemAlign.Center, + }) { + Flex({ + direction: new BreakpointType({ + sm: FlexDirection.Row, + md: FlexDirection.Row, + lg: FlexDirection.Column, + }).getValue(this.globalInfoModel.currentBreakpoint), + alignItems: ItemAlign.Center, + justifyContent: FlexAlign.SpaceAround, + }) { + ForEach(TABS_LIST, (item: TabBarData) => { + this.TabItemBuilder(item) + }, (item: TabBarData) => JSON.stringify(item) + this.currentIndex) + } + .size(new BreakpointType({ + sm: { width: '100%', height: CommonConstants.TAB_BAR_HEIGHT }, + md: { width: '100%', height: CommonConstants.TAB_BAR_HEIGHT }, + lg: { width: CommonConstants.TAB_BAR_WIDTH, height: '50%' }, + }).getValue(this.globalInfoModel.currentBreakpoint)) + .margin({ + bottom: new BreakpointType({ + sm: this.globalInfoModel.naviIndicatorHeight, + md: this.globalInfoModel.naviIndicatorHeight, + lg: 0, + }).getValue(this.globalInfoModel.currentBreakpoint), + }) + + Divider() + .vertical(new BreakpointType({ + sm: false, + md: false, + lg: true, + }).getValue(this.globalInfoModel.currentBreakpoint)) + .color($r('sys.color.comp_divider')) + } + .size(new BreakpointType({ + sm: { width: '100%', height: CommonConstants.TAB_BAR_HEIGHT + this.globalInfoModel.naviIndicatorHeight }, + md: { width: '100%', height: CommonConstants.TAB_BAR_HEIGHT + this.globalInfoModel.naviIndicatorHeight }, + lg: { width: CommonConstants.TAB_BAR_WIDTH, height: '100%' }, + }).getValue(this.globalInfoModel.currentBreakpoint)) + .backgroundBlurStyle(BlurStyle.COMPONENT_THICK) + .renderGroup(this.blurRenderGroup) + } +} \ No newline at end of file diff --git a/products/phone/src/main/ets/entryability/EntryAbility.ets b/products/phone/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..9f252b274c777d08d714d0f66183cf1e7daf070b --- /dev/null +++ b/products/phone/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2024 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, Configuration, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { window } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { BundleManagerUtil, Logger, PageContext, WindowUtil, DynamicInstallManager, WebUtil } from '@ohos/common'; +import GlobalUIAbilityContext from '../util/ContextConfig'; + +const TAG = '[EntryAbility]'; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + Logger.info(TAG, 'Ability onCreate'); + this.checkAndHandleParams(want); + AppStorage.setOrCreate('systemColorMode', this.context.config.colorMode); + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + } + + onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { + Logger.info(TAG, `onNewWant`); + this.checkAndHandleParams(want); + } + + checkAndHandleParams(want: Want): void { + Logger.info(TAG, `checkAndHandleParams`); + try { + if (want === null || want.parameters === null || want === undefined || want.parameters === undefined) { + return; + } + } catch (err) { + Logger.error(TAG, `checkAndHandleParams failed: ${err}`); + } + } + + onDestroy(): void { + Logger.info(TAG, 'Ability onDestroy'); + DynamicInstallManager.unsubscribeDownloadProgress(); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + Logger.info(TAG, 'Ability onWindowStageCreate'); + // Use for penkit component + GlobalUIAbilityContext.setContext(this.context); + WindowUtil.requestFullScreen(windowStage, this.context); + WindowUtil.registerBreakPoint(windowStage); + BundleManagerUtil.getBundleInfo(); + AppStorage.setOrCreate('pageContext', new PageContext()); + AppStorage.setOrCreate('samplePageContext', new PageContext()); + AppStorage.setOrCreate('componentListPageContext', new PageContext()); + AppStorage.setOrCreate('explorationPageContext', new PageContext()); + windowStage.loadContent('page/SplashPage', (err: BusinessError) => { + WebUtil.initialize(windowStage); + if (err.code) { + Logger.error(TAG, `Failed to load the content. Cause: ${err.code} ${err.message}`); + return; + } + // Hide PC/2in1 title bar after loadContent success. + WindowUtil.hideTitleBar(windowStage); + Logger.info(TAG, 'Succeeded in loading the content.'); + }); + } + + onConfigurationUpdate(newConfig: Configuration): void { + const newColorMode: ConfigurationConstant.ColorMode = + newConfig.colorMode || ConfigurationConstant.ColorMode.COLOR_MODE_DARK; + const currentColorMode = AppStorage.get('systemColorMode'); + // When the system dark/light color mode is different form the app color mode,modify the app's color mode. + if (newColorMode !== currentColorMode) { + AppStorage.setOrCreate('systemColorMode', newColorMode); + } + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + Logger.info(TAG, 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + Logger.info(TAG, 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + Logger.info(TAG, 'Ability onBackground'); + } +} \ No newline at end of file diff --git a/products/phone/src/main/ets/entrybackupability/EntryBackupAbility.ets b/products/phone/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..35996991f75c748a58ca385571c3da60ea1bf6d3 --- /dev/null +++ b/products/phone/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 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 { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; +import { Logger } from '@ohos/common'; + +const TAG = '[EntryBackupAbility]'; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + Logger.info(TAG, 'onBackup ok'); + } + + async onRestore(bundleVersion: BundleVersion) { + Logger.info(TAG, `onRestore ok: ${JSON.stringify(bundleVersion)}`); + } +} \ No newline at end of file diff --git a/products/phone/src/main/ets/model/TabBarModel.ets b/products/phone/src/main/ets/model/TabBarModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..276700b7f6d849dfcfd5e4de7b9a0d474bbbb5f9 --- /dev/null +++ b/products/phone/src/main/ets/model/TabBarModel.ets @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 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 { TabBarType } from '@ohos/commonbusiness'; + +export interface TabBarData { + id: TabBarType; + title: ResourceStr; + icon: Resource; +} + +export const TABS_LIST: TabBarData[] = [ + { + id: TabBarType.HOME, + icon: $r('sys.symbol.archivebox_fill'), + title: $r('app.string.tab_home'), + }, + { + id: TabBarType.SAMPLE, + icon: $r('sys.symbol.scenes'), + title: $r('app.string.tab_sample'), + }, + { + id: TabBarType.PRACTICE, + icon: $r('sys.symbol.discover_fill'), + title: $r('app.string.tab_practice'), + }, + { + id: TabBarType.MINE, + icon: $r('sys.symbol.person_crop_circle_fill_1'), + title: $r('app.string.tab_mine'), + }, +] \ No newline at end of file diff --git a/products/phone/src/main/ets/page/MainPage.ets b/products/phone/src/main/ets/page/MainPage.ets new file mode 100644 index 0000000000000000000000000000000000000000..9b96778560db911566dc9ee30b8c910b2c0cee4c --- /dev/null +++ b/products/phone/src/main/ets/page/MainPage.ets @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2024 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 type { common } from '@kit.AbilityKit'; +import { ConfigurationConstant } from '@kit.AbilityKit'; +import { promptAction } from '@kit.ArkUI'; +import type { GlobalInfoModel } from '@ohos/common'; +import { BreakpointTypeEnum, CommonConstants, ProcessUtil, WindowUtil } from '@ohos/common'; +import { TAB_CONTENT_STATUSES, TabBarType } from '@ohos/commonbusiness'; +import { ComponentListView } from '@ohos/componentlibrary'; +import { PracticesView } from '@ohos/devpractices'; +import { ExplorationView } from '@ohos/exploration'; +import { MineView } from '@ohos/mine'; +import { CustomSideBar } from '../component/CustomSideBar'; +import { CustomTabBar } from '../component/CustomTabBar'; + +const PRESS_TIME = 1500; + +@Builder +export function MainPageBuilder() { + MainPage() +} + +@Component({ freezeWhenInactive: true }) +struct MainPage { + @StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!; + @StorageProp('systemColorMode') @Watch('handleColorModeChange') systemColorMode: ConfigurationConstant.ColorMode = + AppStorage.get('systemColorMode')!; + @State currentIndex: number = 0; + private tabController: TabsController = new TabsController(); + private backPressTime: number = 0; + private isShown: boolean = false; + + @Builder + TabComponent() { + Tabs({ controller: this.tabController, index: this.currentIndex }) { + TabContent() { + ComponentListView() + } + .height('100%') + + TabContent() { + PracticesView() + } + .height('100%') + + TabContent() { + ExplorationView() + } + .height('100%') + + TabContent() { + MineView() + } + .height('100%') + } + .onAttach(() => { + this.tabController.preloadItems([TabBarType.HOME, TabBarType.SAMPLE, TabBarType.PRACTICE]) + }) + .onAnimationStart((index: number, targetIndex: number) => { + this.changeTabStatus(targetIndex); + this.currentIndex = targetIndex; + }) + .animationMode(AnimationMode.NO_ANIMATION) + .barWidth(0) + .barHeight(0) + .scrollable(false) + .backgroundColor($r('sys.color.background_secondary')) + .height('100%') + .width('100%') + } + + aboutToAppear(): void { + AppStorage.setOrCreate('currentTabIndex', 0); + } + + onBackPress(): boolean { + const newTime = new Date().getTime(); + if (this.backPressTime && newTime - this.backPressTime < PRESS_TIME) { + ProcessUtil.moveAbilityToBackground(getContext(this) as common.UIAbilityContext); + return false; + } + this.backPressTime = newTime; + promptAction.showToast({ message: $r('app.string.back_toast'), duration: PRESS_TIME }); + return true; + } + + changeTabStatus(index: number) { + AppStorage.setOrCreate('currentTabIndex', index); + const selectedIndex = index; + const isSystemDark: boolean = (this.systemColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK); + if (selectedIndex === TabBarType.MINE || this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG || + this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL) { + WindowUtil.updateStatusBarColor(getContext(this), isSystemDark); + } else { + WindowUtil.updateStatusBarColor(getContext(this), isSystemDark || TAB_CONTENT_STATUSES[selectedIndex]); + } + } + + handleColorModeChange() { + if (this.isShown) { + this.changeTabStatus(this.currentIndex); + } + } + + build() { + NavDestination() { + SideBarContainer(SideBarContainerType.Embed) { + CustomSideBar({ + currentIndex: this.currentIndex, + sideBarChange: (currentIndex: number) => { + this.changeTabStatus(currentIndex); + this.currentIndex = currentIndex; + } + }); + Stack({ alignContent: Alignment.BottomStart }) { + this.TabComponent() + CustomTabBar({ + currentIndex: this.currentIndex, tabBarChange: (currentIndex: number) => { + this.changeTabStatus(currentIndex); + this.currentIndex = currentIndex; + } + }) + .visibility(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL ? Visibility.None : + Visibility.Visible) + } + } + .showSideBar(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.XL) + .showControlButton(false) + } + .transition(TransitionEffect.OPACITY) + .hideTitleBar(true) + .backgroundColor($r('sys.color.background_secondary')) + .onBackPressed(() => { + this.onBackPress(); + return true; + }) + .onShown(() => { + this.isShown = true; + this.handleColorModeChange(); + CommonConstants.PROMISE_WAIT(500).then(() => { + AppStorage.setOrCreate('BlurRenderGroup', false); + }) + }) + .onHidden(() => { + AppStorage.setOrCreate('BlurRenderGroup', true); + this.isShown = false; + }) + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/products/phone/src/main/ets/page/SplashPage.ets b/products/phone/src/main/ets/page/SplashPage.ets new file mode 100644 index 0000000000000000000000000000000000000000..f0f37c1c1a7e3a5d345cfa09a5b1bfa1bc9e2701 --- /dev/null +++ b/products/phone/src/main/ets/page/SplashPage.ets @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024 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 { ConfigurationConstant } from '@kit.AbilityKit'; +import { CommonConstants, Logger, PageContext, WindowUtil, } from '@ohos/common'; +import { SplashEventTypeEnum, SplashViewModel } from '../viewmodel/SplashViewModel'; + +const TAG: string = '[SplashPage]'; + +@Entry +@Component +struct SplashPage { + private pageContext: PageContext = AppStorage.get('pageContext') as PageContext; + private appPathInfo: NavPathStack = this.pageContext.navPathStack; + private viewModel: SplashViewModel = new SplashViewModel(); + + onPageHide() { + Logger.info(TAG, 'onPageHide'); + WindowUtil.updateStatusBarColor(getContext(this), + AppStorage.get('systemColorMode') === ConfigurationConstant.ColorMode.COLOR_MODE_DARK); + } + + aboutToAppear(): void { + WindowUtil.updateStatusBarColor(getContext(this), true); + this.viewModel.sendEvent(SplashEventTypeEnum.CHECK_FIRST_START); + animateTo({ + delay: CommonConstants.ANIMATION_DELAY, + duration: CommonConstants.ANIMATION_DURATION, + onFinish: () => { + this.viewModel.sendEvent(SplashEventTypeEnum.JUMP_TO_MAIN); + } + }, () => { + this.viewModel.sendEvent(SplashEventTypeEnum.PRELOAD_RESOURCES); + }) + } + + build() { + Navigation(this.appPathInfo) { + Column() + .width('100%') + .height('100%') + .backgroundColor($r('app.color.start_window_background')) + } + .hideTitleBar(true) + .mode(NavigationMode.Stack) + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/products/phone/src/main/ets/phoneformability/PhoneFormAbility.ets b/products/phone/src/main/ets/phoneformability/PhoneFormAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..31c8c13a089c070ba0ab4622bc03451b0414130c --- /dev/null +++ b/products/phone/src/main/ets/phoneformability/PhoneFormAbility.ets @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 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 type { Want } from '@kit.AbilityKit'; +import { formBindingData, FormExtensionAbility, formInfo } from '@kit.FormKit'; + +export default class PhoneFormAbility extends FormExtensionAbility { + onAddForm(want: Want) { + // Called to return a FormBindingData object. + const formData = ''; + return formBindingData.createFormBindingData(formData); + } + + onCastToNormalForm(formId: string) { + // Called when the form provider is notified that a temporary form is successfully + // converted to a normal form. + } + + onUpdateForm(formId: string) { + // Called to notify the form provider to update a specified form. + } + + onFormEvent(formId: string, message: string) { + // Called when a specified message event defined by the form provider is triggered. + } + + onRemoveForm(formId: string) { + // Called to notify the form provider that a specified form has been destroyed. + } + + onAcquireFormState(want: Want) { + // Called to return a {@link FormState} object. + return formInfo.FormState.READY; + } +}; \ No newline at end of file diff --git a/products/phone/src/main/ets/util/ContextConfig.ts b/products/phone/src/main/ets/util/ContextConfig.ts new file mode 100644 index 0000000000000000000000000000000000000000..1c832ef47a3b1e5d7601e74752190dcc19bff44b --- /dev/null +++ b/products/phone/src/main/ets/util/ContextConfig.ts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 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 type { common } from '@kit.AbilityKit'; + +declare namespace globalThis { + let _brushEngineContext: common.UIAbilityContext; +}; + +export default class GlobalUIAbilityContext { + public static getContext(): common.UIAbilityContext { + return globalThis._brushEngineContext; + } + + public static setContext(context: common.UIAbilityContext): void { + globalThis._brushEngineContext = context; + } +} \ No newline at end of file diff --git a/products/phone/src/main/ets/viewmodel/SplashViewModel.ets b/products/phone/src/main/ets/viewmodel/SplashViewModel.ets new file mode 100644 index 0000000000000000000000000000000000000000..d708ca64b0da3b89206694e13a19f2c26a9d618f --- /dev/null +++ b/products/phone/src/main/ets/viewmodel/SplashViewModel.ets @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024 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 type { BusinessError } from '@kit.BasicServicesKit'; +import { BaseState, BaseVM, Logger, PageContext, PreferenceManager } from '@ohos/common'; +import { DiscoverModel } from '@ohos/exploration'; +import { ComponentListModel } from '@ohos/componentlibrary'; +import { SampleModel } from '@ohos/devpractices'; + +const TAG: string = '[SplashViewModel]'; + +export class SplashViewModel extends BaseVM { + private pageContext: PageContext = AppStorage.get('pageContext') as PageContext; + private componentListModel: ComponentListModel = ComponentListModel.getInstance(); + private sampleModel: SampleModel = SampleModel.getInstance(); + private discoverModel: DiscoverModel = DiscoverModel.getInstance(); + private preferenceManager: PreferenceManager = PreferenceManager.getInstance(); + + public constructor() { + super(new BaseState()); + } + + public sendEvent(eventType: SplashEventTypeEnum): void { + if (eventType === SplashEventTypeEnum.JUMP_TO_MAIN) { + this.jumpToMainPage(); + } else if (eventType === SplashEventTypeEnum.PRELOAD_RESOURCES) { + this.preloadResources(); + } else if (eventType === SplashEventTypeEnum.CHECK_FIRST_START) { + this.checkIsFirstStart(); + } + } + + private jumpToMainPage(): void { + this.pageContext.replacePage({ + routerName: 'MainPage', + }); + } + + private preloadResources(): void { + this.componentListModel.preloadComponentData(); + this.sampleModel.preloadSamplePageData() + this.discoverModel.preloadDiscoveryData() + } + + private checkIsFirstStart(): void { + this.preferenceManager.hasValue('isFirstStart').then((hasResult: boolean) => { + if (hasResult) { + Logger.info(TAG, 'Not first startup.'); + } else { + Logger.info(TAG, 'First startup.'); + this.preferenceManager.setValue('isFirstStart', false).then(() => { + Logger.info(TAG, 'Put the value of startup Successfully.'); + }).catch((err: BusinessError) => { + Logger.error(TAG, `Put the value of startup Failed, err code: ${err.code}, message: ${err.message}`); + }); + } + }).catch((err: BusinessError) => { + Logger.error(TAG, `check startup Failed, err code: ${err.code}, message: ${err.message}`); + }); + } +} + +export enum SplashEventTypeEnum { + JUMP_TO_MAIN = 'jumpToMainPage', + PRELOAD_RESOURCES = 'preloadResources', + CHECK_FIRST_START = 'checkIsFirstStart', +} \ No newline at end of file diff --git a/products/phone/src/main/ets/widget/pages/WidgetCard.ets b/products/phone/src/main/ets/widget/pages/WidgetCard.ets new file mode 100644 index 0000000000000000000000000000000000000000..d0d5b69558372964f2e0bbd55aa737f1f94d0aa1 --- /dev/null +++ b/products/phone/src/main/ets/widget/pages/WidgetCard.ets @@ -0,0 +1,59 @@ +@Entry +@Component +struct WidgetCard { + /* + * The max lines. + */ + readonly MAX_LINES: number = 1; + /* + * The action type. + */ + readonly ACTION_TYPE: string = 'router'; + /* + * The ability name. + */ + readonly ABILITY_NAME: string = 'EntryAbility'; + /* + * The width percentage setting. + */ + readonly FULL_WIDTH_PERCENT: string = '100%'; + /* + * The height percentage setting. + */ + readonly FULL_HEIGHT_PERCENT: string = '100%'; + + build() { + FormLink({ + action: this.ACTION_TYPE, + abilityName: this.ABILITY_NAME, + }) { + Stack() { + Image($r('app.media.ic_widget')) + .width(this.FULL_WIDTH_PERCENT) + .height(this.FULL_HEIGHT_PERCENT) + Column() { + Text($r('app.string.title_immersive')) + .fontSize($r('sys.float.Subtitle_M')) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontColor($r('sys.color.font_on_primary')) + .fontWeight(FontWeight.Bold) + .maxLines(this.MAX_LINES) + Text($r('app.string.detail_immersive')) + .fontSize($r('sys.float.Body_S')) + .margin({ top: $r('sys.float.padding_level1') }) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .fontColor($r('sys.color.font_on_secondary')) + .fontWeight(FontWeight.Regular) + .maxLines(this.MAX_LINES) + } + .width(this.FULL_WIDTH_PERCENT) + .height(this.FULL_HEIGHT_PERCENT) + .alignItems(HorizontalAlign.Start) + .justifyContent(FlexAlign.Start) + .padding($r('sys.float.padding_level6')) + } + .width(this.FULL_WIDTH_PERCENT) + .height(this.FULL_HEIGHT_PERCENT) + } + } +} \ No newline at end of file diff --git a/products/phone/src/main/module.json5 b/products/phone/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..90cf00cc634520d3a01c5949cab013dfe7bc76f6 --- /dev/null +++ b/products/phone/src/main/module.json5 @@ -0,0 +1,108 @@ +{ + "module": { + "name": "phone", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "routerMap": "$profile:router_map", + "metadata": [ + { + "name": "client_id", + "value": "112510453", + } + ], + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:ic_start_icon_800", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "preferMultiWindowOrientation": "landscape_auto", + "minWindowWidth": 1440, + "minWindowHeight": 940, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ], + "domainVerify": true + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ] + }, + { + "name": "PhoneFormAbility", + "srcEntry": "./ets/phoneformability/PhoneFormAbility.ets", + "label": "$string:PhoneFormAbility_label", + "description": "$string:PhoneFormAbility_desc", + "type": "form", + "metadata": [ + { + "name": "ohos.extension.form", + "resource": "$profile:form_config" + } + ] + } + ], + "requestPermissions": [ + { + "name": "ohos.permission.INTERNET", + "reason": "$string:internet_reason", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "inuse" + } + }, + { + "name": "ohos.permission.GET_NETWORK_INFO", + "reason": "$string:network_reason", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "inuse" + } + }, + { + "name": "ohos.permission.VIBRATE", + "reason": "$string:vibrator_reason", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "inuse" + } + } + ] + } +} \ No newline at end of file diff --git a/products/phone/src/main/resources/base/element/color.json b/products/phone/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..669ff04d1e36c2b15873e6846b90100732e21251 --- /dev/null +++ b/products/phone/src/main/resources/base/element/color.json @@ -0,0 +1,12 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#0A59F7" + }, + { + "name": "hmos_side_bar_background_color", + "value": "#190A59F7" + } + ] +} \ No newline at end of file diff --git a/products/phone/src/main/resources/base/element/float.json b/products/phone/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..f07174a1450adb71af7f70d9eb31b865395d46dc --- /dev/null +++ b/products/phone/src/main/resources/base/element/float.json @@ -0,0 +1,28 @@ +{ + "float": [ + { + "name": "image_width_xs", + "value": "192vp" + }, + { + "name": "image_width_sm", + "value": "288vp" + }, + { + "name": "image_width_md", + "value": "432vp" + }, + { + "name": "side_bar_height", + "value": "40vp" + }, + { + "name": "side_bar_title_height", + "value": "56vp" + }, + { + "name": "app_icon_width", + "value": "24vp" + } + ] +} \ No newline at end of file diff --git a/products/phone/src/main/resources/base/element/string.json b/products/phone/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..98eb03e515534f4eb147bfe835788519f3afe19b --- /dev/null +++ b/products/phone/src/main/resources/base/element/string.json @@ -0,0 +1,80 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "Sample in HarmonyOS" + }, + { + "name": "splash_main_title", + "value": "Sample in HarmonyOS" + }, + { + "name": "splash_sub_title", + "value": "Welcome to HarmonyOS Developer's world" + }, + { + "name": "internet_reason", + "value": "You need to obtain your network permission to obtain network resource display data." + }, + { + "name": "network_reason", + "value": "You need to obtain your network status information to determine whether the current device is connected to the network." + }, + { + "name": "vibrator_reason", + "value": "You need to obtain your vibrator permission to provide vibrator feel" + }, + { + "name": "tab_home", + "value": "Component" + }, + { + "name": "tab_sample", + "value": "Sample" + }, + { + "name": "tab_practice", + "value": "Practice" + }, + { + "name": "tab_mine", + "value": "Me" + }, + { + "name": "PhoneFormAbility_desc", + "value": "form_description" + }, + { + "name": "PhoneFormAbility_label", + "value": "form_label" + }, + { + "name": "widget_desc", + "value": "Harmony Application Development Assistant" + }, + { + "name": "widget_display_name", + "value": "Sample in HarmonyOS" + }, + { + "name": "title_immersive", + "value": "Sample in HarmonyOS" + }, + { + "name": "detail_immersive", + "value": "Harmony Application Development Assistant" + }, + { + "name": "back_toast", + "value": "Swiper again to exit." + } + ] +} \ No newline at end of file diff --git a/products/phone/src/main/resources/base/media/background.png b/products/phone/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..f939c9fa8cc8914832e602198745f592a0dfa34d Binary files /dev/null and b/products/phone/src/main/resources/base/media/background.png differ diff --git a/products/phone/src/main/resources/base/media/foreground.png b/products/phone/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..4483ddad1f079e1089d685bd204ee1cfe1d01902 Binary files /dev/null and b/products/phone/src/main/resources/base/media/foreground.png differ diff --git a/products/phone/src/main/resources/base/media/ic_background.png b/products/phone/src/main/resources/base/media/ic_background.png new file mode 100644 index 0000000000000000000000000000000000000000..2c4ebb884d18fe3f81e83fbbe850e7ddee4dfee9 Binary files /dev/null and b/products/phone/src/main/resources/base/media/ic_background.png differ diff --git a/products/phone/src/main/resources/base/media/ic_foreground.png b/products/phone/src/main/resources/base/media/ic_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..633d7243b55c07163b416bef06b83b630a6696f4 Binary files /dev/null and b/products/phone/src/main/resources/base/media/ic_foreground.png differ diff --git a/products/phone/src/main/resources/base/media/ic_start_icon.png b/products/phone/src/main/resources/base/media/ic_start_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..99bd6a94a18cb81bd8a0af3329eec20cb4748c7c Binary files /dev/null and b/products/phone/src/main/resources/base/media/ic_start_icon.png differ diff --git a/products/phone/src/main/resources/base/media/ic_start_icon_800.png b/products/phone/src/main/resources/base/media/ic_start_icon_800.png new file mode 100644 index 0000000000000000000000000000000000000000..7ab86aafdf50a691abf20ba901398477170c0cb1 Binary files /dev/null and b/products/phone/src/main/resources/base/media/ic_start_icon_800.png differ diff --git a/products/phone/src/main/resources/base/media/ic_widget.png b/products/phone/src/main/resources/base/media/ic_widget.png new file mode 100644 index 0000000000000000000000000000000000000000..269b77c275193718149a9e5a8eca05943bda7725 Binary files /dev/null and b/products/phone/src/main/resources/base/media/ic_widget.png differ diff --git a/products/phone/src/main/resources/base/media/layered_image.json b/products/phone/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..ab180cdfef55bcd5248e3aef53bad8d621a6443a --- /dev/null +++ b/products/phone/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:ic_background", + "foreground" : "$media:ic_foreground" + } +} \ No newline at end of file diff --git a/products/phone/src/main/resources/base/media/startIcon.png b/products/phone/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/products/phone/src/main/resources/base/media/startIcon.png differ diff --git a/products/phone/src/main/resources/base/profile/backup_config.json b/products/phone/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a --- /dev/null +++ b/products/phone/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/products/phone/src/main/resources/base/profile/form_config.json b/products/phone/src/main/resources/base/profile/form_config.json new file mode 100644 index 0000000000000000000000000000000000000000..67ca29e97ab46bf23a27bcaf0a83f6b23794251f --- /dev/null +++ b/products/phone/src/main/resources/base/profile/form_config.json @@ -0,0 +1,25 @@ +{ + "forms": [ + { + "name": "widget", + "displayName": "$string:widget_display_name", + "description": "$string:widget_desc", + "src": "./ets/widget/pages/WidgetCard.ets", + "uiSyntax": "arkts", + "window": { + "designWidth": 720, + "autoDesignWidth": true + }, + "colorMode": "auto", + "isDynamic": false, + "isDefault": true, + "updateEnabled": false, + "scheduledUpdateTime": "10:30", + "updateDuration": 1, + "defaultDimension": "2*2", + "supportDimensions": [ + "2*2" + ] + } + ] +} \ No newline at end of file diff --git a/products/phone/src/main/resources/base/profile/main_pages.json b/products/phone/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..e07601d8de626360fa1aeeabbcbd7a06684b2271 --- /dev/null +++ b/products/phone/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "page/SplashPage" + ] +} diff --git a/products/phone/src/main/resources/base/profile/router_map.json b/products/phone/src/main/resources/base/profile/router_map.json new file mode 100644 index 0000000000000000000000000000000000000000..7ec73382106c0273e975c36eb527548ac7dc6838 --- /dev/null +++ b/products/phone/src/main/resources/base/profile/router_map.json @@ -0,0 +1,9 @@ +{ + "routerMap": [ + { + "name": "MainPage", + "pageSourceFile": "src/main/ets/page/MainPage.ets", + "buildFunction": "MainPageBuilder" + } + ] +} \ No newline at end of file diff --git a/products/phone/src/main/resources/dark/element/color.json b/products/phone/src/main/resources/dark/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..6214b163610833c06af0b86790df0a947018c731 --- /dev/null +++ b/products/phone/src/main/resources/dark/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "hmos_side_bar_background_color", + "value": "#19317AFA" + } + ] +} \ No newline at end of file diff --git a/products/phone/src/main/resources/en_US/element/string.json b/products/phone/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..98eb03e515534f4eb147bfe835788519f3afe19b --- /dev/null +++ b/products/phone/src/main/resources/en_US/element/string.json @@ -0,0 +1,80 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "Sample in HarmonyOS" + }, + { + "name": "splash_main_title", + "value": "Sample in HarmonyOS" + }, + { + "name": "splash_sub_title", + "value": "Welcome to HarmonyOS Developer's world" + }, + { + "name": "internet_reason", + "value": "You need to obtain your network permission to obtain network resource display data." + }, + { + "name": "network_reason", + "value": "You need to obtain your network status information to determine whether the current device is connected to the network." + }, + { + "name": "vibrator_reason", + "value": "You need to obtain your vibrator permission to provide vibrator feel" + }, + { + "name": "tab_home", + "value": "Component" + }, + { + "name": "tab_sample", + "value": "Sample" + }, + { + "name": "tab_practice", + "value": "Practice" + }, + { + "name": "tab_mine", + "value": "Me" + }, + { + "name": "PhoneFormAbility_desc", + "value": "form_description" + }, + { + "name": "PhoneFormAbility_label", + "value": "form_label" + }, + { + "name": "widget_desc", + "value": "Harmony Application Development Assistant" + }, + { + "name": "widget_display_name", + "value": "Sample in HarmonyOS" + }, + { + "name": "title_immersive", + "value": "Sample in HarmonyOS" + }, + { + "name": "detail_immersive", + "value": "Harmony Application Development Assistant" + }, + { + "name": "back_toast", + "value": "Swiper again to exit." + } + ] +} \ No newline at end of file diff --git a/products/phone/src/main/resources/rawfile/image/banner/banner_HMOS.png b/products/phone/src/main/resources/rawfile/image/banner/banner_HMOS.png new file mode 100644 index 0000000000000000000000000000000000000000..5f691c681e5f154220af5f9c94f56b6cb49b01ff Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/banner/banner_HMOS.png differ diff --git a/products/phone/src/main/resources/rawfile/image/banner/banner_ai.png b/products/phone/src/main/resources/rawfile/image/banner/banner_ai.png new file mode 100644 index 0000000000000000000000000000000000000000..e2c1ba3791852d0cf0fa89b3c64aebc0853fd691 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/banner/banner_ai.png differ diff --git a/products/phone/src/main/resources/rawfile/image/banner/banner_arkui.png b/products/phone/src/main/resources/rawfile/image/banner/banner_arkui.png new file mode 100644 index 0000000000000000000000000000000000000000..518546c49dfe41202694e4a25836d4f956a4d9dc Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/banner/banner_arkui.png differ diff --git a/products/phone/src/main/resources/rawfile/image/banner/banner_develop_design.png b/products/phone/src/main/resources/rawfile/image/banner/banner_develop_design.png new file mode 100644 index 0000000000000000000000000000000000000000..eb66da652af69a25b3530b589696039e2aa6c275 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/banner/banner_develop_design.png differ diff --git a/products/phone/src/main/resources/rawfile/image/banner/banner_enabling_kit.png b/products/phone/src/main/resources/rawfile/image/banner/banner_enabling_kit.png new file mode 100644 index 0000000000000000000000000000000000000000..4101e270ba678d6af2c4ffbf33d9cf8e4ae1f71c Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/banner/banner_enabling_kit.png differ diff --git a/products/phone/src/main/resources/rawfile/image/banner/banner_new_features.png b/products/phone/src/main/resources/rawfile/image/banner/banner_new_features.png new file mode 100644 index 0000000000000000000000000000000000000000..f3f81da8169abcdcdfef3e241f03cdd00d0f6927 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/banner/banner_new_features.png differ diff --git a/products/phone/src/main/resources/rawfile/image/banner/banner_quick_start.png b/products/phone/src/main/resources/rawfile/image/banner/banner_quick_start.png new file mode 100644 index 0000000000000000000000000000000000000000..940ca08f6d8714927b676fc9cca98b45ff7291fa Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/banner/banner_quick_start.png differ diff --git a/products/phone/src/main/resources/rawfile/image/banner/banner_ui_design.png b/products/phone/src/main/resources/rawfile/image/banner/banner_ui_design.png new file mode 100644 index 0000000000000000000000000000000000000000..a04f38c45a23c65cfdfb8bf30979bfeedb0b62ba Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/banner/banner_ui_design.png differ diff --git a/products/phone/src/main/resources/rawfile/image/common/hdc_tab_color.png b/products/phone/src/main/resources/rawfile/image/common/hdc_tab_color.png new file mode 100644 index 0000000000000000000000000000000000000000..b32b0ab683cdd1b11f11be6d9b1824e47ba61fa2 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/common/hdc_tab_color.png differ diff --git a/products/phone/src/main/resources/rawfile/image/common/hdc_tab_white.png b/products/phone/src/main/resources/rawfile/image/common/hdc_tab_white.png new file mode 100644 index 0000000000000000000000000000000000000000..d862404e800ddcfc71cebf347ba36d7c34a9631b Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/common/hdc_tab_white.png differ diff --git a/products/phone/src/main/resources/rawfile/image/common/sample_card_background.png b/products/phone/src/main/resources/rawfile/image/common/sample_card_background.png new file mode 100644 index 0000000000000000000000000000000000000000..483f9d38ba82d1107d51ef4de04e98d5a8c2e5a9 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/common/sample_card_background.png differ diff --git a/products/phone/src/main/resources/rawfile/image/common/sample_card_background_blue.png b/products/phone/src/main/resources/rawfile/image/common/sample_card_background_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..a1b6347b3cd7d846413980467cba0ddc99021b6a Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/common/sample_card_background_blue.png differ diff --git a/products/phone/src/main/resources/rawfile/image/common/sample_card_background_green.png b/products/phone/src/main/resources/rawfile/image/common/sample_card_background_green.png new file mode 100644 index 0000000000000000000000000000000000000000..27c4076289a9992ddb43361b87a91bddb6e139c6 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/common/sample_card_background_green.png differ diff --git a/products/phone/src/main/resources/rawfile/image/common/sample_card_background_pink.png b/products/phone/src/main/resources/rawfile/image/common/sample_card_background_pink.png new file mode 100644 index 0000000000000000000000000000000000000000..3d7eb6f2ae3bd50f8988000927b373480ef26031 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/common/sample_card_background_pink.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/card/ai_caption_component.png b/products/phone/src/main/resources/rawfile/image/component/card/ai_caption_component.png new file mode 100644 index 0000000000000000000000000000000000000000..ed64b45997a152f180cc8a94ec8b6aa5e7059e63 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/card/ai_caption_component.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/card/camerapicker.png b/products/phone/src/main/resources/rawfile/image/component/card/camerapicker.png new file mode 100644 index 0000000000000000000000000000000000000000..961c6af15b54a09cc8e4087da04a210d5c0cc7b0 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/card/camerapicker.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/card/common.png b/products/phone/src/main/resources/rawfile/image/component/card/common.png new file mode 100644 index 0000000000000000000000000000000000000000..17d562565a3fcc71874c699c4909b520b869842d Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/card/common.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/card/enable_analyzer.png b/products/phone/src/main/resources/rawfile/image/component/card/enable_analyzer.png new file mode 100644 index 0000000000000000000000000000000000000000..fc5727efec80c76d26196b50973eabfdae8cec2c Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/card/enable_analyzer.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/card/layout.png b/products/phone/src/main/resources/rawfile/image/component/card/layout.png new file mode 100644 index 0000000000000000000000000000000000000000..8a5dfe315e2342de7b53a3a13c76a92eb379e98d Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/card/layout.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/card/link.png b/products/phone/src/main/resources/rawfile/image/component/card/link.png new file mode 100644 index 0000000000000000000000000000000000000000..b4345d6efe6b80ad47accae1e101a19e5fa4e5fd Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/card/link.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/card/list.png b/products/phone/src/main/resources/rawfile/image/component/card/list.png new file mode 100644 index 0000000000000000000000000000000000000000..3f5e32864a57d1c1ecd86ea688e8db833fb68e93 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/card/list.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/card/pankit.png b/products/phone/src/main/resources/rawfile/image/component/card/pankit.png new file mode 100644 index 0000000000000000000000000000000000000000..b053f9e1d865684cdf2488c79aa11e0100693dcd Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/card/pankit.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/card/photopicker.png b/products/phone/src/main/resources/rawfile/image/component/card/photopicker.png new file mode 100644 index 0000000000000000000000000000000000000000..fb29a737d7ab8716e489ce4f8905d79e994381fa Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/card/photopicker.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/card/text.png b/products/phone/src/main/resources/rawfile/image/component/card/text.png new file mode 100644 index 0000000000000000000000000000000000000000..c3023ae8a072879f5c6c6ec026a5097aded6caef Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/card/text.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/card/texttospeech.png b/products/phone/src/main/resources/rawfile/image/component/card/texttospeech.png new file mode 100644 index 0000000000000000000000000000000000000000..a6a9eb7533722d1a87127ac878b38e1ed610754a Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/card/texttospeech.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/ai_caption_component.png b/products/phone/src/main/resources/rawfile/image/component/icon/ai_caption_component.png new file mode 100644 index 0000000000000000000000000000000000000000..49047ec9cf2c4860935501f8ec7759cb76327498 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/ai_caption_component.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/camerapicker.png b/products/phone/src/main/resources/rawfile/image/component/icon/camerapicker.png new file mode 100644 index 0000000000000000000000000000000000000000..e006bd3860c73134a01d2d76db81f0f58934b5b1 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/camerapicker.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/common/button.png b/products/phone/src/main/resources/rawfile/image/component/icon/common/button.png new file mode 100644 index 0000000000000000000000000000000000000000..f38be41bcb1dfb5741505af0727434f6e4945ad1 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/common/button.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/common/image.png b/products/phone/src/main/resources/rawfile/image/component/icon/common/image.png new file mode 100644 index 0000000000000000000000000000000000000000..416454176b833361556dfb9a1f73bc012cdcd283 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/common/image.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/common/progress.png b/products/phone/src/main/resources/rawfile/image/component/icon/common/progress.png new file mode 100644 index 0000000000000000000000000000000000000000..be37bc4d5b9831dfd0d2b364994b35e1979f60cf Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/common/progress.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/common/rating.png b/products/phone/src/main/resources/rawfile/image/component/icon/common/rating.png new file mode 100644 index 0000000000000000000000000000000000000000..8418386f83bc2407258a9b96c7c1a02fe132cd85 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/common/rating.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/common/toggle.png b/products/phone/src/main/resources/rawfile/image/component/icon/common/toggle.png new file mode 100644 index 0000000000000000000000000000000000000000..1d9b11c5598d36a2acf826fa679654dc4496f000 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/common/toggle.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/dialog/action_sheet.png b/products/phone/src/main/resources/rawfile/image/component/icon/dialog/action_sheet.png new file mode 100644 index 0000000000000000000000000000000000000000..54da111f35c211e3b48debf5d77028b15031b1ff Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/dialog/action_sheet.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/dialog/alert_dialog.png b/products/phone/src/main/resources/rawfile/image/component/icon/dialog/alert_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..0b9329b5b73d81ee4dfe6e09870283e9c5ac5cde Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/dialog/alert_dialog.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/dialog/custom_dialog.png b/products/phone/src/main/resources/rawfile/image/component/icon/dialog/custom_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..1efbdd0c93b8efd87b73f19d921ec6821546af12 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/dialog/custom_dialog.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/dialog/popup.png b/products/phone/src/main/resources/rawfile/image/component/icon/dialog/popup.png new file mode 100644 index 0000000000000000000000000000000000000000..dcedcbf4ea962cf98959704bfdd26a1d51681d4b Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/dialog/popup.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/dialog/text_dialog.png b/products/phone/src/main/resources/rawfile/image/component/icon/dialog/text_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..f5517252d8846e564ed4ace1d07b16829be32633 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/dialog/text_dialog.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/enable_analyzer.png b/products/phone/src/main/resources/rawfile/image/component/icon/enable_analyzer.png new file mode 100644 index 0000000000000000000000000000000000000000..3a2d5208b70d3ed6bfd35f245c0b5ba1b2bb76c4 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/enable_analyzer.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/layout/column.png b/products/phone/src/main/resources/rawfile/image/component/icon/layout/column.png new file mode 100644 index 0000000000000000000000000000000000000000..40dd4c3d97f71c8854954885f4d749608f092f3d Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/layout/column.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/layout/flex.png b/products/phone/src/main/resources/rawfile/image/component/icon/layout/flex.png new file mode 100644 index 0000000000000000000000000000000000000000..e81d8c29d52516f84d05af55166ce901f1538000 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/layout/flex.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/layout/row.png b/products/phone/src/main/resources/rawfile/image/component/icon/layout/row.png new file mode 100644 index 0000000000000000000000000000000000000000..d886ab460157fbe65b33f6bf318e5561259cdff8 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/layout/row.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/layout/stack.png b/products/phone/src/main/resources/rawfile/image/component/icon/layout/stack.png new file mode 100644 index 0000000000000000000000000000000000000000..08a18aa96fde5537fd7a01b91db687ae571d0738 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/layout/stack.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/link/calendar_picker.png b/products/phone/src/main/resources/rawfile/image/component/icon/link/calendar_picker.png new file mode 100644 index 0000000000000000000000000000000000000000..dbc003fbe1822febe56898d56ac927945560183b Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/link/calendar_picker.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/link/date_picker.png b/products/phone/src/main/resources/rawfile/image/component/icon/link/date_picker.png new file mode 100644 index 0000000000000000000000000000000000000000..3abd4384e5df6233949152c699d97ccd3ca52e32 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/link/date_picker.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/link/document_picker.png b/products/phone/src/main/resources/rawfile/image/component/icon/link/document_picker.png new file mode 100644 index 0000000000000000000000000000000000000000..c7bd9d6b5d35ab1f1816549e0df6d116bf493f27 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/link/document_picker.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/link/linking.png b/products/phone/src/main/resources/rawfile/image/component/icon/link/linking.png new file mode 100644 index 0000000000000000000000000000000000000000..ebfd42fa34936242ec38b575a7db9c86d3e25412 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/link/linking.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/list/grid.png b/products/phone/src/main/resources/rawfile/image/component/icon/list/grid.png new file mode 100644 index 0000000000000000000000000000000000000000..881549c119cbc9c1d3bce2fbfc5e6dcc44995331 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/list/grid.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/list/list.png b/products/phone/src/main/resources/rawfile/image/component/icon/list/list.png new file mode 100644 index 0000000000000000000000000000000000000000..f4be4f983f2eb285165a91084dc0d655806f2b58 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/list/list.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/list/swiper.png b/products/phone/src/main/resources/rawfile/image/component/icon/list/swiper.png new file mode 100644 index 0000000000000000000000000000000000000000..b7a1893d1ca275f93d626a6e0a6c9e471a9e0385 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/list/swiper.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/list/tabs.png b/products/phone/src/main/resources/rawfile/image/component/icon/list/tabs.png new file mode 100644 index 0000000000000000000000000000000000000000..7b592c6e1b45a7f635f4bb1ad7ad621a3c20c087 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/list/tabs.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/list/waterflow.png b/products/phone/src/main/resources/rawfile/image/component/icon/list/waterflow.png new file mode 100644 index 0000000000000000000000000000000000000000..ac35144a81a83ac2d1788250412157a157d59382 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/list/waterflow.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/penkit.png b/products/phone/src/main/resources/rawfile/image/component/icon/penkit.png new file mode 100644 index 0000000000000000000000000000000000000000..73840e882cacab8d836530a041cab06e68a834d0 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/penkit.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/photopicker.png b/products/phone/src/main/resources/rawfile/image/component/icon/photopicker.png new file mode 100644 index 0000000000000000000000000000000000000000..ae08dd255508557e4456f413e8b681353e618ab5 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/photopicker.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/text/text.png b/products/phone/src/main/resources/rawfile/image/component/icon/text/text.png new file mode 100644 index 0000000000000000000000000000000000000000..93d062cb82cc5713673d5a4367403368dfb9cc7f Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/text/text.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/text/textarea.png b/products/phone/src/main/resources/rawfile/image/component/icon/text/textarea.png new file mode 100644 index 0000000000000000000000000000000000000000..e835b130023ec07ae499e91e029e82ee0e88608b Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/text/textarea.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/text/textinput.png b/products/phone/src/main/resources/rawfile/image/component/icon/text/textinput.png new file mode 100644 index 0000000000000000000000000000000000000000..426f5b24997e9be8b9eaa1ef4ede39ac87f3df6e Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/text/textinput.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/text/textstyle.png b/products/phone/src/main/resources/rawfile/image/component/icon/text/textstyle.png new file mode 100644 index 0000000000000000000000000000000000000000..0c2dbb7f54161e7714566d747465f94d45f9ac79 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/text/textstyle.png differ diff --git a/products/phone/src/main/resources/rawfile/image/component/icon/texttospeech.png b/products/phone/src/main/resources/rawfile/image/component/icon/texttospeech.png new file mode 100644 index 0000000000000000000000000000000000000000..116885ad5ac296312c4c01a6121ce6cdd78c74ed Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/component/icon/texttospeech.png differ diff --git a/products/phone/src/main/resources/rawfile/image/practice/experienceDesign/ux_effect_design.png b/products/phone/src/main/resources/rawfile/image/practice/experienceDesign/ux_effect_design.png new file mode 100644 index 0000000000000000000000000000000000000000..c518dc93e6bf89c74c777c7cb5abe85773acb4d8 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/practice/experienceDesign/ux_effect_design.png differ diff --git a/products/phone/src/main/resources/rawfile/image/practice/experienceDesign/ux_experience.png b/products/phone/src/main/resources/rawfile/image/practice/experienceDesign/ux_experience.png new file mode 100644 index 0000000000000000000000000000000000000000..e0cb832b6ffd7fbe2b66b6ba4c72177d222b4c45 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/practice/experienceDesign/ux_experience.png differ diff --git a/products/phone/src/main/resources/rawfile/image/practice/experienceDesign/ux_multi.png b/products/phone/src/main/resources/rawfile/image/practice/experienceDesign/ux_multi.png new file mode 100644 index 0000000000000000000000000000000000000000..2ede7e9990277e909dcaffa10859294d36f40d53 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/practice/experienceDesign/ux_multi.png differ diff --git a/products/phone/src/main/resources/rawfile/image/practice/functionDevelopment/discovery_article_id_11.png b/products/phone/src/main/resources/rawfile/image/practice/functionDevelopment/discovery_article_id_11.png new file mode 100644 index 0000000000000000000000000000000000000000..9dc99d82a7c657e71b2a4ed306828fa4ce7497fd Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/practice/functionDevelopment/discovery_article_id_11.png differ diff --git a/products/phone/src/main/resources/rawfile/image/practice/functionDevelopment/discovery_article_id_12.png b/products/phone/src/main/resources/rawfile/image/practice/functionDevelopment/discovery_article_id_12.png new file mode 100644 index 0000000000000000000000000000000000000000..aa318a3e9f7b9165c8c7c334243e7ab8796fc1a6 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/practice/functionDevelopment/discovery_article_id_12.png differ diff --git a/products/phone/src/main/resources/rawfile/image/practice/functionDevelopment/discovery_article_id_13.png b/products/phone/src/main/resources/rawfile/image/practice/functionDevelopment/discovery_article_id_13.png new file mode 100644 index 0000000000000000000000000000000000000000..983eb925ef7afb4fe8ad195529d3d1a11d852b05 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/practice/functionDevelopment/discovery_article_id_13.png differ diff --git a/products/phone/src/main/resources/rawfile/image/practice/functionDevelopment/discovery_article_id_8.png b/products/phone/src/main/resources/rawfile/image/practice/functionDevelopment/discovery_article_id_8.png new file mode 100644 index 0000000000000000000000000000000000000000..3a19f0b25005fcb7599e3099f19e8d2b3769c925 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/practice/functionDevelopment/discovery_article_id_8.png differ diff --git a/products/phone/src/main/resources/rawfile/image/practice/latestAdvice/discovery_article_id_1.png b/products/phone/src/main/resources/rawfile/image/practice/latestAdvice/discovery_article_id_1.png new file mode 100644 index 0000000000000000000000000000000000000000..f66b678b536f1e027a125313ecfd33443fd0cd2f Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/practice/latestAdvice/discovery_article_id_1.png differ diff --git a/products/phone/src/main/resources/rawfile/image/practice/latestAdvice/discovery_article_id_2.png b/products/phone/src/main/resources/rawfile/image/practice/latestAdvice/discovery_article_id_2.png new file mode 100644 index 0000000000000000000000000000000000000000..555eaf6dd9a9af587d96d3bfcd30fe0a0c1e176c Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/practice/latestAdvice/discovery_article_id_2.png differ diff --git a/products/phone/src/main/resources/rawfile/image/practice/latestAdvice/discovery_article_id_3.png b/products/phone/src/main/resources/rawfile/image/practice/latestAdvice/discovery_article_id_3.png new file mode 100644 index 0000000000000000000000000000000000000000..88d4dfdc85fc0f73e218ce603e213523caf3c903 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/practice/latestAdvice/discovery_article_id_3.png differ diff --git a/products/phone/src/main/resources/rawfile/image/practice/latestAdvice/discovery_article_id_4.png b/products/phone/src/main/resources/rawfile/image/practice/latestAdvice/discovery_article_id_4.png new file mode 100644 index 0000000000000000000000000000000000000000..81a9415d9cea938e5436efa0c6ea7d8ae15a60e6 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/practice/latestAdvice/discovery_article_id_4.png differ diff --git a/products/phone/src/main/resources/rawfile/image/practice/latestAdvice/discovery_article_id_5.png b/products/phone/src/main/resources/rawfile/image/practice/latestAdvice/discovery_article_id_5.png new file mode 100644 index 0000000000000000000000000000000000000000..505a9e48703ee64817408ab48d0cf2e50b871fdf Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/practice/latestAdvice/discovery_article_id_5.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/arkui/effect/sample_animation_collection.png b/products/phone/src/main/resources/rawfile/image/sample/arkui/effect/sample_animation_collection.png new file mode 100644 index 0000000000000000000000000000000000000000..79e122b8fffc3651d062bfb0683d5942c3f60532 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/arkui/effect/sample_animation_collection.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/arkui/effect/sample_drag_framework.png b/products/phone/src/main/resources/rawfile/image/sample/arkui/effect/sample_drag_framework.png new file mode 100644 index 0000000000000000000000000000000000000000..025f7e51c94d289fe69baf3beb655839190b1332 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/arkui/effect/sample_drag_framework.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/arkui/effect/sample_transitions_collection.gif b/products/phone/src/main/resources/rawfile/image/sample/arkui/effect/sample_transitions_collection.gif new file mode 100644 index 0000000000000000000000000000000000000000..5b8df4019834469ac43b56ff2e5e82fd285764d1 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/arkui/effect/sample_transitions_collection.gif differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/arkui/layout/sample_component_stack.png b/products/phone/src/main/resources/rawfile/image/sample/arkui/layout/sample_component_stack.png new file mode 100644 index 0000000000000000000000000000000000000000..9f855d847410dc1470557eecc9ca7ed75ead5937 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/arkui/layout/sample_component_stack.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/arkui/layout/sample_grid_hybrid.png b/products/phone/src/main/resources/rawfile/image/sample/arkui/layout/sample_grid_hybrid.png new file mode 100644 index 0000000000000000000000000000000000000000..31237e3e680060fb1605c6d05919b8ce1c90b575 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/arkui/layout/sample_grid_hybrid.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/arkui/layout/sample_scroll_component_nested_sliding.png b/products/phone/src/main/resources/rawfile/image/sample/arkui/layout/sample_scroll_component_nested_sliding.png new file mode 100644 index 0000000000000000000000000000000000000000..cdbdb23ae3aeeecf7509b0aada9cb8e7245365d8 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/arkui/layout/sample_scroll_component_nested_sliding.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/arkui/layout/sample_water_flow.png b/products/phone/src/main/resources/rawfile/image/sample/arkui/layout/sample_water_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..9b23af1962d259ffd7d05643f27dc048451509e2 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/arkui/layout/sample_water_flow.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/arkui/list/sample_fluent_blog.png b/products/phone/src/main/resources/rawfile/image/sample/arkui/list/sample_fluent_blog.png new file mode 100644 index 0000000000000000000000000000000000000000..f9e604440a00972b8998b87f8ab53a26fd133d10 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/arkui/list/sample_fluent_blog.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/arkui/list/sample_list_exchange.png b/products/phone/src/main/resources/rawfile/image/sample/arkui/list/sample_list_exchange.png new file mode 100644 index 0000000000000000000000000000000000000000..88a2031526d121e61ffba1fd77408d1afc9a3e09 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/arkui/list/sample_list_exchange.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/arkui/list/sample_list_item_edit.png b/products/phone/src/main/resources/rawfile/image/sample/arkui/list/sample_list_item_edit.png new file mode 100644 index 0000000000000000000000000000000000000000..2ef0de92d72c8c0b32b4d634c178f9cc8bca3b68 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/arkui/list/sample_list_item_edit.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/arkui/scene/sample_image_comment.png b/products/phone/src/main/resources/rawfile/image/sample/arkui/scene/sample_image_comment.png new file mode 100644 index 0000000000000000000000000000000000000000..b2a567f3a3142d71a487e7f76c587781a890ae7b Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/arkui/scene/sample_image_comment.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/arkui/scene/sample_keyboard.png b/products/phone/src/main/resources/rawfile/image/sample/arkui/scene/sample_keyboard.png new file mode 100644 index 0000000000000000000000000000000000000000..9c082cf453cfac362249a69a260c90bd65557ca0 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/arkui/scene/sample_keyboard.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/arkui/scene/sample_verification_code_scenario.png b/products/phone/src/main/resources/rawfile/image/sample/arkui/scene/sample_verification_code_scenario.png new file mode 100644 index 0000000000000000000000000000000000000000..5d26ef9e6540dcacb90fe31662d4fc178d0c9801 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/arkui/scene/sample_verification_code_scenario.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/arkui/style/sample_custom_dialog_gathers.png b/products/phone/src/main/resources/rawfile/image/sample/arkui/style/sample_custom_dialog_gathers.png new file mode 100644 index 0000000000000000000000000000000000000000..7d3799a82b1287526fe046e34e8bb9937d91ebf0 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/arkui/style/sample_custom_dialog_gathers.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/arkui/style/sample_multi_tab_navigation.png b/products/phone/src/main/resources/rawfile/image/sample/arkui/style/sample_multi_tab_navigation.png new file mode 100644 index 0000000000000000000000000000000000000000..eb35961c3e148ee1e17e4f67c3268cddf0ff574c Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/arkui/style/sample_multi_tab_navigation.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/arkui/style/sample_text_effects.gif b/products/phone/src/main/resources/rawfile/image/sample/arkui/style/sample_text_effects.gif new file mode 100644 index 0000000000000000000000000000000000000000..24017c3decf5e6a7087365308ea61228a84749e6 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/arkui/style/sample_text_effects.gif differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/function/capacity/sample_location_service.png b/products/phone/src/main/resources/rawfile/image/sample/function/capacity/sample_location_service.png new file mode 100644 index 0000000000000000000000000000000000000000..087d9392fbe63f00c3a26a618c709e251bc25528 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/function/capacity/sample_location_service.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/function/capacity/sample_page_redirection.png b/products/phone/src/main/resources/rawfile/image/sample/function/capacity/sample_page_redirection.png new file mode 100644 index 0000000000000000000000000000000000000000..04caf6604ae70007d178912c18c1ec6d7366ec8d Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/function/capacity/sample_page_redirection.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/function/capacity/sample_web_pre_render.png b/products/phone/src/main/resources/rawfile/image/sample/function/capacity/sample_web_pre_render.png new file mode 100644 index 0000000000000000000000000000000000000000..6639c54a50785b93e12f8d20f8580bfc44b404d0 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/function/capacity/sample_web_pre_render.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/function/data/sample_picker.png b/products/phone/src/main/resources/rawfile/image/sample/function/data/sample_picker.png new file mode 100644 index 0000000000000000000000000000000000000000..c2cde3129e3a8b8cdc116c9b3408e0b3229017d2 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/function/data/sample_picker.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/function/data/sample_preferences.png b/products/phone/src/main/resources/rawfile/image/sample/function/data/sample_preferences.png new file mode 100644 index 0000000000000000000000000000000000000000..42be9b568114a43bb06d3b88c8de9f15500028c3 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/function/data/sample_preferences.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/function/media/sample_audio_interaction.png b/products/phone/src/main/resources/rawfile/image/sample/function/media/sample_audio_interaction.png new file mode 100644 index 0000000000000000000000000000000000000000..257d32dee1e545eedb5c52a001d465ca165e92fd Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/function/media/sample_audio_interaction.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/function/media/sample_multiple_image.png b/products/phone/src/main/resources/rawfile/image/sample/function/media/sample_multiple_image.png new file mode 100644 index 0000000000000000000000000000000000000000..cc32f047f15ce0deb69958f5e4c9339dc9936aed Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/function/media/sample_multiple_image.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/function/media/sample_short_video.png b/products/phone/src/main/resources/rawfile/image/sample/function/media/sample_short_video.png new file mode 100644 index 0000000000000000000000000000000000000000..384483afb5ab1c9a814e5ca34bb186f5414f0785 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/function/media/sample_short_video.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/function/media/sample_window_pip.png b/products/phone/src/main/resources/rawfile/image/sample/function/media/sample_window_pip.png new file mode 100644 index 0000000000000000000000000000000000000000..b536313f63f18fa2b4393711c7875934b88febbf Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/function/media/sample_window_pip.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/hdc/hmosworld_all_device.png b/products/phone/src/main/resources/rawfile/image/sample/hdc/hmosworld_all_device.png new file mode 100644 index 0000000000000000000000000000000000000000..415da584e6f848dfa0579d1310e452f490c53231 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/hdc/hmosworld_all_device.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/hdc/hmosworld_watch.png b/products/phone/src/main/resources/rawfile/image/sample/hdc/hmosworld_watch.png new file mode 100644 index 0000000000000000000000000000000000000000..d887e0b302f7e03ebf28e3eaca532e7b9a32af1d Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/hdc/hmosworld_watch.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/hdc/sample_business.png b/products/phone/src/main/resources/rawfile/image/sample/hdc/sample_business.png new file mode 100644 index 0000000000000000000000000000000000000000..664ad7c837de022192833e031b95e2ad6ae20eb7 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/hdc/sample_business.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/hdc/sample_continuepublic.png b/products/phone/src/main/resources/rawfile/image/sample/hdc/sample_continuepublic.png new file mode 100644 index 0000000000000000000000000000000000000000..1bc48f8211b49308dbfea771352e8c9b51b19b62 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/hdc/sample_continuepublic.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/hdc/sample_knockshare.png b/products/phone/src/main/resources/rawfile/image/sample/hdc/sample_knockshare.png new file mode 100644 index 0000000000000000000000000000000000000000..dc4f08ccea0b0d035b660f0e932c070c93e5b4d2 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/hdc/sample_knockshare.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/hdc/sample_liveviewlockscreen.png b/products/phone/src/main/resources/rawfile/image/sample/hdc/sample_liveviewlockscreen.png new file mode 100644 index 0000000000000000000000000000000000000000..f75ca51d38c1d35badf6387077e095839cfe10a9 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/hdc/sample_liveviewlockscreen.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/hdc/sample_videocast.png b/products/phone/src/main/resources/rawfile/image/sample/hdc/sample_videocast.png new file mode 100644 index 0000000000000000000000000000000000000000..3fa31fd691cd4a5684620833a182707d398e8b42 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/hdc/sample_videocast.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_business_office.png b/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_business_office.png new file mode 100644 index 0000000000000000000000000000000000000000..218935985cfc6ae3778999f113ebf6b17558c199 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_business_office.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_columns.png b/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_columns.png new file mode 100644 index 0000000000000000000000000000000000000000..28e809d56ff0b8c5ced4ee4d61a7dde14350a8b9 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_columns.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_convenient_life.png b/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_convenient_life.png new file mode 100644 index 0000000000000000000000000000000000000000..067fec87b974f1a22f29d51f2655481803c826d4 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_convenient_life.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_mobile_payment.png b/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_mobile_payment.png new file mode 100644 index 0000000000000000000000000000000000000000..3c15a6701355a50450e6be0353139cb35cc720e5 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_mobile_payment.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_nav_bar.png b/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_nav_bar.png new file mode 100644 index 0000000000000000000000000000000000000000..ffe659a3d1cef10ba3c1c85ba7c8c64f8c911b36 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_nav_bar.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_news_read.png b/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_news_read.png new file mode 100644 index 0000000000000000000000000000000000000000..979fd39ed35590e02fcf3c8b2cbe1c06796d540f Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_news_read.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_ticket_class.png b/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_ticket_class.png new file mode 100644 index 0000000000000000000000000000000000000000..9e78d077cd3ee428285a6f9c09016bf626f802d9 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_ticket_class.png differ diff --git a/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_travel_accommodation.png b/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_travel_accommodation.png new file mode 100644 index 0000000000000000000000000000000000000000..af322f698d294a6fbe48b865c126a514c48e1e74 Binary files /dev/null and b/products/phone/src/main/resources/rawfile/image/sample/multidevice/sample_multi_travel_accommodation.png differ diff --git a/products/phone/src/main/resources/rawfile/router_config.json b/products/phone/src/main/resources/rawfile/router_config.json new file mode 100644 index 0000000000000000000000000000000000000000..ace54b5e9d5e10e12ae410687e7f800df06ca51a --- /dev/null +++ b/products/phone/src/main/resources/rawfile/router_config.json @@ -0,0 +1,53 @@ +{ + "config": [ + { + "routerName": "ComponentDetailPage", + "routerInfo": { + "libraryName": "@ohos/componentlibrary", + "className": "ComponentDetailModel" + } + }, + { + "routerName": "CodeLabDetailPage", + "routerInfo": { + "libraryName": "@ohos/componentlibrary", + "className": "CodeLabDetailModel" + } + }, + { + "routerName": "SampleDetailPage", + "routerInfo": { + "libraryName": "@ohos/devpractices", + "className": "SampleDetailModel" + } + }, + { + "routerName": "SampleLoadingPage", + "routerInfo": { + "libraryName": "@ohos/devpractices", + "className": "SampleDetailModel" + } + }, + { + "routerName": "DiscoverModule", + "routerInfo": { + "libraryName": "@ohos/exploration", + "className": "DiscoverModel" + } + }, + { + "routerName": "MainModule", + "routerInfo": { + "libraryName": "phone", + "className": "MainModel" + } + }, + { + "routerName": "FeedbackPage", + "routerInfo": { + "libraryName": "@ohos/mine", + "className": "FeedbackModel" + } + } + ] +} \ No newline at end of file diff --git a/products/phone/src/main/resources/zh_CN/element/string.json b/products/phone/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..cff753a8ade6411a19cbdf8a3e84c64fc78fba2b --- /dev/null +++ b/products/phone/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,80 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "HMOS代码工坊" + }, + { + "name": "splash_main_title", + "value": "HarmonyOS代码工坊" + }, + { + "name": "splash_sub_title", + "value": "欢迎来到HarmonyOS开发者世界" + }, + { + "name": "internet_reason", + "value": "需要获取您的网络权限,用于获取网络资源展示数据" + }, + { + "name": "network_reason", + "value": "需要获取您的网络状态信息,用于判断当前设备是否联网" + }, + { + "name": "vibrator_reason", + "value": "需要获取震动权限来提供震动感" + }, + { + "name": "tab_home", + "value": "组件" + }, + { + "name": "tab_sample", + "value": "样例" + }, + { + "name": "tab_practice", + "value": "实践" + }, + { + "name": "tab_mine", + "value": "我的" + }, + { + "name": "PhoneFormAbility_desc", + "value": "form_description" + }, + { + "name": "PhoneFormAbility_label", + "value": "form_label" + }, + { + "name": "widget_desc", + "value": "鸿蒙应用开发助手" + }, + { + "name": "widget_display_name", + "value": "HMOS代码工坊" + }, + { + "name": "title_immersive", + "value": "HMOS代码工坊" + }, + { + "name": "detail_immersive", + "value": "鸿蒙应用开发助手" + }, + { + "name": "back_toast", + "value": "再滑一次退出" + } + ] +} \ No newline at end of file diff --git a/products/phone/src/ohosTest/ets/test/Ability.test.ets b/products/phone/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..85c78f67579d6e31b5f5aeea463e216b9b141048 --- /dev/null +++ b/products/phone/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +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/products/phone/src/ohosTest/ets/test/List.test.ets b/products/phone/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..794c7dc4ed66bd98fa3865e07922906e2fcef545 --- /dev/null +++ b/products/phone/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/products/phone/src/ohosTest/module.json5 b/products/phone/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..bd4a1a2cfcfd43628c11daf310012859a1c0ca72 --- /dev/null +++ b/products/phone/src/ohosTest/module.json5 @@ -0,0 +1,13 @@ +{ + "module": { + "name": "phone_test", + "type": "feature", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} \ No newline at end of file diff --git a/products/phone/src/test/List.test.ets b/products/phone/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..bb5b5c3731e283dd507c847560ee59bde477bbc7 --- /dev/null +++ b/products/phone/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/products/phone/src/test/LocalUnit.test.ets b/products/phone/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..165fc1615ee8618b4cb6a622f144a9a707eee99f --- /dev/null +++ b/products/phone/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +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/products/wearable/.gitignore b/products/wearable/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/products/wearable/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/products/wearable/build-profile.json5 b/products/wearable/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..4d611879c7913fb0610c686e2399258ab3a6dad1 --- /dev/null +++ b/products/wearable/build-profile.json5 @@ -0,0 +1,28 @@ +{ + "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/products/wearable/hvigorfile.ts b/products/wearable/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6edcd90486dd5a853cf7d34c8647f08414ca7a3 --- /dev/null +++ b/products/wearable/hvigorfile.ts @@ -0,0 +1,6 @@ +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/products/wearable/obfuscation-rules.txt b/products/wearable/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/products/wearable/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/products/wearable/oh-package.json5 b/products/wearable/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..dfd2e33d1f275cd6bbcdc9eb5c6afce208c66af8 --- /dev/null +++ b/products/wearable/oh-package.json5 @@ -0,0 +1,14 @@ +{ + "name": "wearable", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "@ohos/devpractices": "file:../../features/devpractices", + "@ohos/commonbusiness": "file:../../features/commonbusiness", + "@ohos/common": "file:../../common" + } +} + diff --git a/products/wearable/src/main/ets/component/HomepageComponent.ets b/products/wearable/src/main/ets/component/HomepageComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..8433bb5142a5c02d3a714dbb4bfa29e4f8d9e071 --- /dev/null +++ b/products/wearable/src/main/ets/component/HomepageComponent.ets @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 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. + */ + +@Component +export struct HomePageComponent { + build() { + Column() { + Text($r('app.string.homePage_title')) + .fontSize($r('app.float.title_text_font_size')) + .fontWeight(FontWeight.Bold) + .fontColor($r('sys.color.font_on_primary')) + .lineHeight($r('app.float.title_text_line_height')) + Text($r('app.string.homePage_description')) + .fontSize($r('app.float.description_text_font_size')) + .fontWeight(FontWeight.Regular) + .fontColor($r('sys.color.font_on_secondary')) + .lineHeight($r('app.float.description_text_line_height')) + Image($r('app.media.hmos_image')) + .height($r('app.float.homepage_image_height')) + } + .height('100%') + .width('100%') + .padding({ + top: $r('sys.float.padding_level12'), + }) + } +} \ No newline at end of file diff --git a/products/wearable/src/main/ets/component/SampleListComponent.ets b/products/wearable/src/main/ets/component/SampleListComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..7d5b8e010629a0d7184633ed783719804fd6144c --- /dev/null +++ b/products/wearable/src/main/ets/component/SampleListComponent.ets @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2024 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 { common } from '@kit.AbilityKit'; +import { ArcList, ArcListAttribute, ArcListItem, LengthMetrics, ComponentContent } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { Logger } from '@ohos/common'; +import { MediaTypeEnum } from '@ohos/commonbusiness'; +import { WearableSampleData } from '../model/WearableSampleData'; + +const ITEM_SPACE: number = 6; +const ITEM_WIDTH: string = '88%'; +const TAG: string = '[SampleListComponent]'; + +@Builder +function SampleListHeader() { + Column() { + Text($r('app.string.sample_library')) + .fontSize($r('app.float.item_title_size')) + .fontWeight(FontWeight.Bold) + .fontColor($r('sys.color.font_on_primary')) + .padding({ + top: $r('sys.float.padding_level3'), + bottom: $r('sys.float.padding_level2'), + }) + Text($r('app.string.watch_development_sample')) + .fontSize($r('sys.float.Subtitle_M')) + .fontWeight(FontWeight.Regular) + .fontColor($r('sys.color.font_on_secondary')) + } +} + +@Component +export struct SampleListComponent { + context: UIContext = this.getUIContext(); + sampleListHeader: ComponentContent<[]> = new ComponentContent(this.context, wrapBuilder(SampleListHeader)); + @State wearableSampleList: WearableSampleData[] = []; + + @Builder + SampleListItem(wearableSampleData: WearableSampleData) { + Row() { + if (wearableSampleData.mediaType === MediaTypeEnum.SYMBOL) { + Button({ type: ButtonType.Circle }) { + SymbolGlyph($r(wearableSampleData.mediaUrl)) + .fontSize($r('app.float.icon_size')) + .fontColor([$r('sys.color.font_on_primary')]) + } + .width($r('app.float.button_size')) + .height($r('app.float.button_size')) + .backgroundColor(wearableSampleData.symbolGlyphColor) + } else if (wearableSampleData.mediaType == MediaTypeEnum.IMAGE) { + Image($r(wearableSampleData.mediaUrl)) + .width($r('app.float.button_size')) + .height($r('app.float.button_size')) + } + + Column() { + Text(wearableSampleData.title) + .fontWeight(FontWeight.Medium) + .fontColor($r('sys.color.font_on_primary')) + .fontSize($r('app.float.item_title_size')) + Text(wearableSampleData.desc) + .fontWeight(FontWeight.Regular) + .fontColor($r('sys.color.font_on_secondary')) + .fontSize($r('app.float.item_description_size')) + } + + SymbolGlyph($r('sys.symbol.chevron_forward')) + .fontSize($r('app.float.icon_size')) + .fontColor([$r('sys.color.icon_secondary')]) + } + .justifyContent(FlexAlign.SpaceBetween) + .width(ITEM_WIDTH) + .borderRadius($r('app.float.item_border_radius')) + .padding({ + top: $r('sys.float.padding_level6'), + bottom: $r('sys.float.padding_level5'), + left: $r('sys.float.padding_level3'), + right: $r('sys.float.padding_level3'), + }) + .backgroundColor($r('app.color.item_background')) + .onClick(() => { + const ctx: common.UIAbilityContext = getContext() as common.UIAbilityContext; + const moduleAbility: string = wearableSampleData.abilityName; + try { + ctx.startAbility({ + bundleName: ctx.abilityInfo.bundleName, + abilityName: moduleAbility + }).then(() => { + Logger.info(TAG, `start ${moduleAbility} success}`); + }).catch((error: BusinessError) => { + Logger.error(TAG, `start ${moduleAbility} failed, message is ${error.message}`); + }); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(TAG, `startAbility failed, message is ${err.message}`); + } + }) + } + + build() { + ArcList({ header: this.sampleListHeader }) { + ForEach(this.wearableSampleList, (item: WearableSampleData) => { + ArcListItem() { + this.SampleListItem(item) + } + }, (item: WearableSampleData) => item.id.toString()) + } + .space(LengthMetrics.vp(ITEM_SPACE)) + .focusable(true) + .focusOnTouch(true) + .defaultFocus(true) + } +} \ No newline at end of file diff --git a/products/wearable/src/main/ets/model/WearableSampleData.ets b/products/wearable/src/main/ets/model/WearableSampleData.ets new file mode 100644 index 0000000000000000000000000000000000000000..e33dbff914e2e99f8bed704dc11d881f4f96e496 --- /dev/null +++ b/products/wearable/src/main/ets/model/WearableSampleData.ets @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 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 { SingleSampleData } from '@ohos/devpractices'; + +@Observed +export class WearableSampleData extends SingleSampleData { + public symbolGlyphColor: string = ''; +} \ No newline at end of file diff --git a/products/wearable/src/main/ets/pages/MainPage.ets b/products/wearable/src/main/ets/pages/MainPage.ets new file mode 100644 index 0000000000000000000000000000000000000000..a345fe548fc25e68de08e49b484ec6dc3d6d25aa --- /dev/null +++ b/products/wearable/src/main/ets/pages/MainPage.ets @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 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 { ArcSwiper, ArcSwiperAttribute, ArcSwiperController } from '@kit.ArkUI'; +import { MockRequest } from '@ohos/common'; +import { HomePageComponent } from '../component/HomepageComponent'; +import { SampleListComponent } from '../component/SampleListComponent'; +import { WearableSampleData } from '../model/WearableSampleData'; + +const WEARABLE_SAMPLE_TRIGGER: string = 'wearable-sample-list'; + +@Entry +@Component +struct MainPage { + private wearableSwiperController: ArcSwiperController = new ArcSwiperController(); + private wearableSampleList: WearableSampleData[] = []; + + aboutToAppear(): void { + MockRequest.call(WEARABLE_SAMPLE_TRIGGER) + .then((result: WearableSampleData[]) => { + this.wearableSampleList = result; + }) + } + + build() { + ArcSwiper(this.wearableSwiperController) { + HomePageComponent() + .onClick(() => { + this.wearableSwiperController.showNext(); + }) + SampleListComponent({ wearableSampleList: this.wearableSampleList }) + } + .indicator(false) + .onDigitalCrown((event: CrownEvent) => { + event.stopPropagation(); + }) + } +} \ No newline at end of file diff --git a/products/wearable/src/main/ets/wearableability/WearableAbility.ets b/products/wearable/src/main/ets/wearableability/WearableAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..e71f0d9b22348741f108ee4a099810723b31cbfc --- /dev/null +++ b/products/wearable/src/main/ets/wearableability/WearableAbility.ets @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 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 { window } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { Logger } from '@ohos/common'; + +const TAG = '[WearableAbility]'; + +export default class WearableAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + Logger.info(TAG, 'Ability onCreate'); + } + + onDestroy(): void { + Logger.info(TAG, 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + Logger.info(TAG, 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/MainPage', (err: BusinessError) => { + if (err.code) { + Logger.error(TAG, `Failed to load the content. Cause: ${err.code} ${err.message}`); + return; + } + Logger.info(TAG, 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + Logger.info(TAG, 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + Logger.info(TAG, 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + Logger.info(TAG, 'Ability onBackground'); + } +} diff --git a/products/wearable/src/main/ets/wearablebackupability/WearableBackupAbility.ets b/products/wearable/src/main/ets/wearablebackupability/WearableBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..37a2f2a3626bb39ee7cb5f6d3dd3ebe9a740a9ef --- /dev/null +++ b/products/wearable/src/main/ets/wearablebackupability/WearableBackupAbility.ets @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 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 { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; +import { Logger } from '@ohos/common'; + +const TAG = '[WearableBackupAbility]'; + +export default class WearableBackupAbility extends BackupExtensionAbility { + async onBackup() { + Logger.info(TAG, 'onBackup ok'); + await Promise.resolve(); + } + + async onRestore(bundleVersion: BundleVersion) { + Logger.info(TAG, `onRestore ok: ${JSON.stringify(bundleVersion)}`); + await Promise.resolve(); + } +} \ No newline at end of file diff --git a/products/wearable/src/main/module.json5 b/products/wearable/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..f9baafa2b386db220f14037c428b24c241753d56 --- /dev/null +++ b/products/wearable/src/main/module.json5 @@ -0,0 +1,50 @@ +{ + "module": { + "name": "wearable", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "WearableAbility", + "deviceTypes": [ + "wearable" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "WearableAbility", + "srcEntry": "./ets/wearableability/WearableAbility.ets", + "description": "$string:WearableAbility_desc", + "icon": "$media:layered_image", + "label": "$string:WearableAbility_label", + "startWindowIcon": "$media:ic_start_icon_800", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "WearableBackupAbility", + "srcEntry": "./ets/wearablebackupability/WearableBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ], + } + ] + } +} \ No newline at end of file diff --git a/products/wearable/src/main/resources/base/element/color.json b/products/wearable/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..149c4e92e280ffe269921d35bf17814aedde662d --- /dev/null +++ b/products/wearable/src/main/resources/base/element/color.json @@ -0,0 +1,12 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + }, + { + "name": "item_background", + "value": "#19FFFFFF" + } + ] +} \ No newline at end of file diff --git a/products/wearable/src/main/resources/base/element/float.json b/products/wearable/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..1d63aecc8214fcee435192a1225e9d7e9e4303ec --- /dev/null +++ b/products/wearable/src/main/resources/base/element/float.json @@ -0,0 +1,44 @@ +{ + "float": [ + { + "name": "title_text_font_size", + "value": "24fp" + }, + { + "name": "title_text_line_height", + "value": "32vp" + }, + { + "name": "description_text_font_size", + "value": "20fp" + }, + { + "name": "description_text_line_height", + "value": "28vp" + }, + { + "name": "homepage_image_height", + "value": "158vp" + }, + { + "name": "icon_size", + "value": "24vp" + }, + { + "name": "item_border_radius", + "value": "140vp" + }, + { + "name": "button_size", + "value": "40vp" + }, + { + "name": "item_title_size", + "value": "19fp" + }, + { + "name": "item_description_size", + "value": "15fp" + } + ] +} diff --git a/products/wearable/src/main/resources/base/element/string.json b/products/wearable/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..782ea7e798439d570d1c7d1a167028f091d453a5 --- /dev/null +++ b/products/wearable/src/main/resources/base/element/string.json @@ -0,0 +1,32 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "WearableAbility_desc", + "value": "description" + }, + { + "name": "WearableAbility_label", + "value": "Sample in HarmonyOS" + }, + { + "name": "homePage_title", + "value": "Sample in HarmonyOS" + }, + { + "name": "homePage_description", + "value": "Development Demonstration Application" + }, + { + "name": "sample_library", + "value": "Sample Library" + }, + { + "name": "watch_development_sample", + "value": "Watch Development Sample" + } + ] +} \ No newline at end of file diff --git a/products/wearable/src/main/resources/base/media/background.png b/products/wearable/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/products/wearable/src/main/resources/base/media/background.png differ diff --git a/products/wearable/src/main/resources/base/media/foreground.png b/products/wearable/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97014d3e10e5ff511409c378cd4255713aecd85f Binary files /dev/null and b/products/wearable/src/main/resources/base/media/foreground.png differ diff --git a/products/wearable/src/main/resources/base/media/hmos_image.png b/products/wearable/src/main/resources/base/media/hmos_image.png new file mode 100644 index 0000000000000000000000000000000000000000..0250458842da73de08c8228c855f318b2aa18291 Binary files /dev/null and b/products/wearable/src/main/resources/base/media/hmos_image.png differ diff --git a/products/wearable/src/main/resources/base/media/ic_background.png b/products/wearable/src/main/resources/base/media/ic_background.png new file mode 100644 index 0000000000000000000000000000000000000000..4c4671724ec24a20eaad33c37800194a808cbfbb Binary files /dev/null and b/products/wearable/src/main/resources/base/media/ic_background.png differ diff --git a/products/wearable/src/main/resources/base/media/ic_foreground.png b/products/wearable/src/main/resources/base/media/ic_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..c17d456e4bb7aebc3ad21b4ac87bf8ffdd8eef41 Binary files /dev/null and b/products/wearable/src/main/resources/base/media/ic_foreground.png differ diff --git a/products/wearable/src/main/resources/base/media/ic_start_icon_800.png b/products/wearable/src/main/resources/base/media/ic_start_icon_800.png new file mode 100644 index 0000000000000000000000000000000000000000..7ab86aafdf50a691abf20ba901398477170c0cb1 Binary files /dev/null and b/products/wearable/src/main/resources/base/media/ic_start_icon_800.png differ diff --git a/products/wearable/src/main/resources/base/media/layered_image.json b/products/wearable/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..ab180cdfef55bcd5248e3aef53bad8d621a6443a --- /dev/null +++ b/products/wearable/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:ic_background", + "foreground" : "$media:ic_foreground" + } +} \ No newline at end of file diff --git a/products/wearable/src/main/resources/base/media/startIcon.png b/products/wearable/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/products/wearable/src/main/resources/base/media/startIcon.png differ diff --git a/products/wearable/src/main/resources/base/media/wearable_sample_map.png b/products/wearable/src/main/resources/base/media/wearable_sample_map.png new file mode 100644 index 0000000000000000000000000000000000000000..e53560c42e062802fb513a9711ade2f5edc1ba77 Binary files /dev/null and b/products/wearable/src/main/resources/base/media/wearable_sample_map.png differ diff --git a/products/wearable/src/main/resources/base/media/wearable_sample_music.png b/products/wearable/src/main/resources/base/media/wearable_sample_music.png new file mode 100644 index 0000000000000000000000000000000000000000..3f49d3d3c2eb1b66a58544717d39dfe20e0d20d2 Binary files /dev/null and b/products/wearable/src/main/resources/base/media/wearable_sample_music.png differ diff --git a/products/wearable/src/main/resources/base/profile/backup_config.json b/products/wearable/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a --- /dev/null +++ b/products/wearable/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/products/wearable/src/main/resources/base/profile/main_pages.json b/products/wearable/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..f4241ef2f72e78047b4c449d92bd1a92accc39fb --- /dev/null +++ b/products/wearable/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/MainPage" + ] +} diff --git a/products/wearable/src/main/resources/zh_CN/element/string.json b/products/wearable/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..191796d4fa77af2a108ec178c819af82d4391e86 --- /dev/null +++ b/products/wearable/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,32 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "WearableAbility_desc", + "value": "description" + }, + { + "name": "WearableAbility_label", + "value": "HMOS代码工坊" + }, + { + "name": "homePage_title", + "value": "HMOS代码工坊" + }, + { + "name": "homePage_description", + "value": "示例代码合集" + }, + { + "name": "sample_library", + "value": "样例库" + }, + { + "name": "watch_development_sample", + "value": "手表开发样例" + } + ] +} \ No newline at end of file diff --git a/products/wearable/src/ohosTest/ets/test/Ability.test.ets b/products/wearable/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..85c78f67579d6e31b5f5aeea463e216b9b141048 --- /dev/null +++ b/products/wearable/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +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/products/wearable/src/ohosTest/ets/test/List.test.ets b/products/wearable/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..794c7dc4ed66bd98fa3865e07922906e2fcef545 --- /dev/null +++ b/products/wearable/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/products/wearable/src/ohosTest/module.json5 b/products/wearable/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..b09edc7eba9d375046dde1420c4910a57c3d7f0d --- /dev/null +++ b/products/wearable/src/ohosTest/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "wearable_test", + "type": "feature", + "deviceTypes": [ + "car" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/products/wearable/src/test/List.test.ets b/products/wearable/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..bb5b5c3731e283dd507c847560ee59bde477bbc7 --- /dev/null +++ b/products/wearable/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/products/wearable/src/test/LocalUnit.test.ets b/products/wearable/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..165fc1615ee8618b4cb6a622f144a9a707eee99f --- /dev/null +++ b/products/wearable/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +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